PgGraph - پوسٹگری ایس کیو ایل میں ٹیبل انحصار کو آرکائیو کرنے اور تلاش کرنے کی افادیت

PgGraph - پوسٹگری ایس کیو ایل میں ٹیبل انحصار کو آرکائیو کرنے اور تلاش کرنے کی افادیت
آج میں ہابر کے قارئین کو پوسٹگری ایس کیو ایل ڈی بی ایم ایس میں ٹیبل پر انحصار کے ساتھ کام کرنے کے لیے ازگر میں لکھی گئی یوٹیلیٹی کے ساتھ پیش کرنا چاہتا ہوں۔

یوٹیلیٹی کا API آسان ہے اور تین طریقوں پر مشتمل ہے:

  • آرکائیو_ٹیبل - مخصوص پرائمری کیز کے ساتھ بار بار آرکائیو کرنا/حذف کرنے والی قطاریں۔
  • حاصل_ٹیبل_حوالہ جات - کسی ٹیبل کے لیے انحصار تلاش کریں (مخصوص ٹیبلز اور اس کا حوالہ دینے والوں کے حوالے سے ٹیبلز دکھائیں گے)
  • حاصل_قطار_حوالہ جات - دیگر جدولوں میں قطاریں تلاش کریں جو مطلوبہ جدول میں مخصوص قطاروں کا حوالہ دیتی ہیں۔

پس منظر

میرا نام اولیگ بورزوف ہے، میں ڈومکلک میں رہن قرض دینے والے مینیجرز کے لیے CRM ٹیم میں ایک ڈویلپر ہوں۔

ہمارے CRM سسٹم کا مرکزی ڈیٹا بیس کمپنی میں حجم کے لحاظ سے سب سے بڑا ڈیٹا بیس ہے۔ یہ بھی قدیم ترین میں سے ایک ہے: یہ پروجیکٹ کے بالکل آغاز کے وقت نمودار ہوا، جب درخت بڑے تھے، ڈومکلک ایک اسٹارٹ اپ تھا، اور فیشن ایبل ازگر کے غیر مطابقت پذیر فریم ورک پر مائیکرو سروس کے بجائے پی ایچ پی میں ایک بہت بڑا یک سنگی تھا۔

پی ایچ پی سے ازگر میں منتقلی بہت طویل تھی اور اس کے لیے دونوں سسٹمز کی بیک وقت مدد کی ضرورت تھی، جس نے ڈیٹا بیس کے ڈیزائن کو متاثر کیا۔

نتیجے کے طور پر، ہمارے پاس ایک ڈیٹابیس ہے جس میں بہت زیادہ مربوط اور بڑی میزیں ہیں جن میں مختلف قسم کے سوالات کے لیے اشاریہ جات کا ایک گروپ ہے۔ یہ سب ڈیٹا بیس کی کارکردگی کو منفی طور پر متاثر کرتا ہے: بڑی میزوں اور ان کے درمیان رشتوں کے ایک گروپ کی وجہ سے، سوالات کی پیچیدگی مسلسل بڑھ رہی ہے، جو خاص طور پر سب سے زیادہ بھری ہوئی میزوں کے لیے اہم ہے۔

ڈیٹا بیس پر بوجھ کو کم کرنے کے لیے، ہم نے ایک اسکرپٹ لکھنے کا فیصلہ کیا جو پرانے ریکارڈز کو سب سے زیادہ بھاری اور بھری ہوئی جدولوں سے محفوظ شدہ دستاویزات میں منتقل کرے گا (مثال کے طور پر، سے task в task_archive).

یہ کام جدولوں کے درمیان رشتوں کی بڑی تعداد کی وجہ سے پیچیدہ ہے: بس سے قطاریں منتقل کریں۔ task в task_archive کافی نہیں ہے، اس سے پہلے آپ کو ان تمام حوالہ جات کے ساتھ بار بار ایسا کرنے کی ضرورت ہے۔ task میزیں

میں ایک مثال کے ساتھ مظاہرہ کروں گا۔ سائٹ postgrespro.ru سے ڈیمو ڈیٹا بیس:

PgGraph - پوسٹگری ایس کیو ایل میں ٹیبل انحصار کو آرکائیو کرنے اور تلاش کرنے کی افادیت
ہم کہتے ہیں کہ ہمیں ٹیبل سے ریکارڈز کو حذف کرنے کی ضرورت ہے۔ Flights. پوسٹگریس ہمیں ایسا کرنے کی اجازت نہیں دے گا: ہمیں سب سے پہلے تمام حوالہ جات کی میزوں سے ریکارڈز کو حذف کرنے کی ضرورت ہے، اور اسی طرح بار بار ان میزوں پر جو کسی کی طرف سے حوالہ نہیں دیا گیا ہے۔

ہماری مثال میں 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'}]}}}

کوڈ میں استعمال

اسے کنسول میں چلانے کے علاوہ، لائبریری کو Python کوڈ میں بھی استعمال کیا جا سکتا ہے۔ 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

نیا تبصرہ شامل کریں