PgGraph PostgreSQL ۾ ٽيبل جي انحصار کي آرڪائيو ڪرڻ ۽ ڳولڻ لاءِ هڪ افاديت آهي

PgGraph PostgreSQL ۾ ٽيبل جي انحصار کي آرڪائيو ڪرڻ ۽ ڳولڻ لاءِ هڪ افاديت آهي
اڄ مان Habr پڙهندڙن کي پيش ڪرڻ چاهيان ٿو هڪ يوٽيلٽي سان گڏ Python ۾ لکيل آهي PostgreSQL DBMS ۾ ٽيبل انحصار سان ڪم ڪرڻ لاءِ.

يوٽيلٽي جو API سادو آهي ۽ ٽن طريقن تي مشتمل آهي:

  • آرڪائيو_ٽيبل - بار بار آرڪائيو ڪرڻ / قطار کي ختم ڪرڻ مخصوص پرائمري ڪيز سان
  • get_table_references - جدول لاءِ انحصار لاءِ ڳولھيو (مقرر ڪيل ھڪڙي جي حوالي سان ٽيبل ڏيکاريندو ۽ جيڪي ان جو حوالو ڏين ٿا)
  • get_rows_references - ٻين جدولن ۾ قطارن جي ڳولا ڪريو جيڪي مطلوب جدول ۾ مخصوص قطارن جو حوالو ڏين ٿيون

prehistory

منهنجو نالو اوليگ بورزوف آهي، مان ڊومڪلڪ ۾ گروي قرض ڏيڻ واري مينيجرز لاءِ CRM ٽيم ۾ ڊولپر آهيان.

اسان جي CRM سسٽم جو بنيادي ڊيٽابيس ڪمپني ۾ حجم جي لحاظ کان سڀ کان وڏو آهي. اھو پڻ ھڪڙو پراڻو آھي: اھو پراجيڪٽ جي شروعات تي ظاهر ٿيو، جڏھن وڻ وڏا ھئا، ڊوملڪ ھڪڙو شروعاتي ھو، ۽ ھڪڙي مائڪرو سروس جي بدران ھڪڙي فيشن پٿن جي غير مطابقت واري فريم ورڪ تي PHP ۾ ھڪڙو وڏو monolith ھو.

PHP کان پٿون جي منتقلي تمام ڊگهي هئي ۽ ٻنهي سسٽم جي هڪ ئي وقت جي مدد جي ضرورت هئي، جنهن ڊيٽابيس جي ڊيزائن کي متاثر ڪيو.

نتيجي طور، اسان وٽ ھڪڙو ڊيٽابيس آھي وڏي تعداد ۾ تمام گھڻي ڳنڍيل ۽ وڏي جدولن سان مختلف قسمن جي سوالن لاءِ انڊيڪسس جي ھڪڙي گروپ سان. هي سڀ منفي طور تي ڊيٽابيس جي ڪارڪردگي تي اثر انداز ڪري ٿو: وڏي جدولن جي ڪري ۽ انهن جي وچ ۾ لاڳاپن جو هڪ گروپ، سوالن جي پيچيدگي مسلسل وڌي رهي آهي، جيڪا خاص طور تي تمام گهڻي لوڊ ٿيل جدولن لاء اهم آهي.

ڊيٽابيس تي لوڊ کي گھٽائڻ لاءِ، اسان ھڪ اسڪرپٽ لکڻ جو فيصلو ڪيو آھي جيڪو پراڻن رڪارڊن کي تمام گھڻي ۽ لوڊ ٿيل جدولن مان آرڪائيو ٿيل جدولن ڏانھن منتقل ڪندو (مثال طور، کان task в task_archive).

هي ڪم جدولن جي وچ ۾ لاڳاپن جي وڏي تعداد جي ڪري پيچيده آهي: بس قطارن تان منتقل ڪريو task в task_archive ڪافي نه آهي، انهي کان اڳ توهان کي انهن سڀني حوالن سان گڏ ساڳيو ڪم ڪرڻو پوندو task ٽيبل

مان هڪ مثال سان بيان ڪندس سائيٽ کان ڊيمو ڊيٽابيس postgrespro.ru:

PgGraph PostgreSQL ۾ ٽيبل جي انحصار کي آرڪائيو ڪرڻ ۽ ڳولڻ لاءِ هڪ افاديت آهي
اچو ته اسان کي ٽيبل تان رڪارڊ ختم ڪرڻ جي ضرورت آهي Flights. Postgres اسان کي ائين ڪرڻ جي اجازت نه ڏيندو صرف انهي وانگر: اسان کي پهريان سڀني ريفرنسنگ جدولن مان رڪارڊ کي حذف ڪرڻ جي ضرورت آهي، ۽ انهي تي بار بار هيٺ ڏنل جدولن تي جيڪي ڪنهن جي حوالي نه ڪيا ويا آهن.

اسان جي مثال ۾ Flights حوالو Ticket_flights۽ هن تي - Boarding_passes.

تنهن ڪري، توهان کي هن ترتيب ۾ ان کي ختم ڪرڻ جي ضرورت آهي:

  1. اسان حاصل ڪندا آھيون پرائمري ڪيز (PK) جون قطارون ان ۾ Ticket_flights، جنهن ۾ حذف ٿيڻ لاءِ قطارن جو حوالو ڏيو Flights.
  2. اسان PK قطارون حاصل ڪندا آهيون Boarding_passes، جنهن جو حوالو ڏيو Ticket_flights.
  3. اسان ٽيبل ۾ قدم 2 مان PK ذريعي قطار کي ختم ڪريون ٿا Boarding_passes.
  4. قدم 1 ۾ PK پاران لائينون ختم ڪريو Ticket_flights.
  5. مان لڪيرون ڪڍڻ Flights.

نتيجو هڪ افاديت هئي PgGraph، جنهن کي اسان اوپن سورس ٺاهڻ جو فيصلو ڪيو.

ڪيئن استعمال ڪجي

افاديت استعمال جي ٻن طريقن کي سپورٽ ڪري ٿو:

  • ڪمانڊ لائن کان ڪال (pggraph …).
  • پٿون ڪوڊ ۾ استعمال (ڪلاس PgGraphApi).

انسٽاليشن ۽ ترتيب ڏيڻ

پهرين توهان کي Pypi مخزن مان يوٽيلٽي انسٽال ڪرڻ جي ضرورت آهي:

pip3 install pggraph

پوءِ ٺاھيو config.ini فائل مقامي مشين تي ڊيٽابيس جي ترتيب ۽ آرڪائيو اسڪرپٽ سان:

[db]
host = localhost
port = 5432
user = postgres
password = postgres
dbname = postgres
schema = public ; Необязательный параметр, указано значение по умолчанию

[archive]  ; Данный раздел заполнять необязательно, ниже указаны значения по умолчанию
is_debug = false
chunk_size = 1000
max_depth = 20
to_archive = true
archive_suffix = 'archive'

ڪنسول کان هلايو

حراست ۾

$ pggraph -h
usage: pggraph action [-h] --table TABLE [--ids IDS] [--config_path CONFIG_PATH]
positional arguments:
  action        required action: archive_table, get_table_references, get_rows_references

optional arguments:
  -h, --help                    show this help message and exit
  --table TABLE                 table name
  --ids IDS                     primary key ids, separated by comma, e.g. 1,2,3
  --config_path CONFIG_PATH     path to config.ini
  --log_path LOG_PATH           path to log dir
  --log_level LOG_LEVEL         log level (debug, info, error)

موقف جا دليل:

  • action - گهربل عمل: archive_table, get_table_references يا get_rows_references.

نامزد ٿيل دليل:

  • --config_path - ترتيب واري فائل ڏانهن رستو؛
  • --table - هڪ ٽيبل جنهن سان توهان کي عمل ڪرڻ جي ضرورت آهي؛
  • --ids - ڪاما سان الڳ ٿيل سڃاڻپ جي فهرست، مثال طور، 1,2,3 (اختياري پيٽرول)؛
  • --log_path - لاگز لاءِ فولڊر ڏانھن رستو (اختياري پيٽرول، ڊفالٽ طرفان - گھر فولڊر)؛
  • --log_level - لاگنگ سطح (اختياري پيٽرولر، ڊفالٽ INFO آهي).

حڪم جا مثال

ٽيبل آرڪائيو ڪرڻ

يوٽيلٽي جو بنيادي ڪم ڊيٽا آرڪائيو ڪرڻ آهي، يعني. مکيه ٽيبل کان آرڪائيو ٽيبل تي قطارون منتقل ڪرڻ (مثال طور، ٽيبل تان ڪتابن в book_archive).

آرڪائيو ڪرڻ کان سواءِ حذف ڪرڻ پڻ سپورٽ ڪئي وئي آهي: ان لاءِ توهان کي config.ini ۾ پيٽرول سيٽ ڪرڻ جي ضرورت آهي to_archive = غلط).

گهربل معيار - config_path، ٽيبل ۽ ids.

لانچ کان پوء، رڪارڊ ٻيهر ختم ٿي ويندا ids ميز ۾ table ۽ سڀني جدولن ۾ جيڪي ان جو حوالو ڏين ٿا.

$ pggraph archive_table --config_path config.hw.local.ini --table flights --ids 1,2,3
2020-06-20 19:27:44 INFO: flights - START
2020-06-20 19:27:44 INFO: flights - start archive_recursive 3 rows (depth=0)
2020-06-20 19:27:44 INFO:       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:       ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 19:27:44 INFO:               START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:       ticket_flights - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: flights - archive_by_ids 3 rows by id
2020-06-20 19:27:44 INFO: flights - END

ھڪڙي مخصوص ٽيبل لاء انحصار ڳوليو

ھڪڙي مخصوص جدول جي انحصار کي ڳولڻ لاء فنڪشن table. گهربل معيار - config_path и table.

لانچ کان پوء، هڪ لغت اسڪرين تي ڏيکاري ويندي، جتي:

  • in_refs - ڏنل جدولن جي هڪ لغت جنهن ۾ ڏنل هڪ حوالي ڪيو ويو آهي، جتي ڪنجي ٽيبل جو نالو آهي، قدر آهي پرڏيهي اهم شين جي فهرست (pk_main - مکيه ٽيبل ۾ بنيادي ڪي، pk_ref - ريفرنسنگ ٽيبل ۾ پرائمري ڪيچ، fk_ref - ڪالمن جو نالو جيڪو ماخذ ٽيبل جي غير ملڪي ڪنجي آهي؛
  • out_refs - جدولن جي هڪ لغت جنهن جو حوالو ڏنو ويو آهي.

$ pggraph get_table_references --config_path config.hw.local.ini --table flights
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='ticket_no, flight_id', fk_ref='flight_id')]},
 'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
              'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
                           ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}

مخصوص پرائمري ڪيئي سان اسٽرنگ جا حوالا ڳولڻ

ٻين جدولن ۾ قطارن کي ڳولهڻ جو فنڪشن جيڪو فارين ڪيئي ذريعي قطارن جو حوالو ڏئي ٿو ids ٽيبل table. گهربل معيار - config_path, table и ids.

لانچ ڪرڻ کان پوء، هيٺ ڏنل ڍانچي سان هڪ لغت اسڪرين تي ڏيکاري ويندي:

{
	pk_id_1: {
		reffering_table_name_1: {
			foreign_key_1: [
				{row_pk_1: value, row_pk_2: value},
				...
			], 
			...
		},
		...
	},
	pk_id_2: {...},
	...
}

مثال ڪال:

$ pggraph get_rows_references --config_path config.hw.local.ini --table flights --ids 1,2,3
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
                                       'ticket_no': '0005432816945'},
                                      {'flight_id': 1,
                                       'ticket_no': '0005432816941'}]}},
 2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
                                       'ticket_no': '0005433101832'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005433101864'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005432919715'}]}},
 3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
                                       'ticket_no': '0005432817560'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817568'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817559'}]}}}

ڪوڊ ۾ استعمال

ان کان علاوه ان کي ڪنسول ۾ هلائڻ لاءِ، لائبريري پٿون ڪوڊ ۾ استعمال ڪري سگهجي ٿي. iPython انٽرايڪٽو ماحول ۾ ڪالن جا مثال هيٺ ڏجن ٿا.

ٽيبل آرڪائيو ڪرڻ

>>> from pg_graph.main import setup_logging
>>> setup_logging(log_level='DEBUG')
>>> from pg_graph.api import PgGraphApi
>>> api = PgGraphApi('config.hw.local.ini')
>>> api.archive_table('flights', [4,5])
2020-06-20 23:12:08 INFO: flights - START
2020-06-20 23:12:08 INFO: flights - start archive_recursive 2 rows (depth=0)
2020-06-20 23:12:08 INFO: 	START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 	ticket_flights - ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')
2020-06-20 23:12:08 DEBUG: 	SQL('SELECT flight_id, ticket_no FROM bookings.ticket_flights WHERE (flight_id) IN (%s, %s)')
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 3 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 3 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 3 rows
2020-06-20 23:12:08 INFO: 	END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: flights - archive_by_ids 2 rows by flight_id
2020-06-20 23:12:09 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.flights_archive (LIKE bookings.flights)')
2020-06-20 23:12:09 DEBUG: DELETE FROM flights by flight_id - 2 rows
2020-06-20 23:12:09 DEBUG: INSERT INTO flights_archive - 2 rows
2020-06-20 23:12:09 INFO: flights - END

ھڪڙي مخصوص ٽيبل لاء انحصار ڳوليو

>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> res = api.get_table_references('flights')
>>> pprint(res)
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')]},
 'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
              'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
                           ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}

مخصوص پرائمري ڪيئي سان اسٽرنگ جا حوالا ڳولڻ

>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> rows = api.get_rows_references('flights', [1,2,3])
>>> pprint(rows)
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
                                       'ticket_no': '0005432816945'},
                                      {'flight_id': 1,
                                       'ticket_no': '0005432816941'}]}},
 2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
                                       'ticket_no': '0005433101832'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005433101864'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005432919715'}]}},
 3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
                                       'ticket_no': '0005432817560'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817568'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817559'}]}}}

لائبريري جو سورس ڪوڊ موجود آهي GitHub MIT لائسنس تحت، گڏو گڏ مخزن ۾ پيئي.

مان تبصرو، ڪمن ۽ تجويزون لاء خوش ٿيندس.

مان ڪوشش ڪندس ته سوالن جا جواب پنهنجي بهترين صلاحيت سان هتي ۽ مخزن ۾.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو