قواعد البيانات الأوراكل والبايثون –كيف نتعامل مع الاوراكل من كود بايثون

0 3٬884

قواعد البيانات الأوراكل هي أحد أقوى قواعد البيانات، وتعتمد عليها الكثير من الشركات عالميًا. تُعد الأوراكل قواعد بيانات شاملة، حيث تحتوي الكثير من الإمكانات والخصائص على مستوى الأمان، الحوسبة السحابية، معالجة البيانات الضخمة، الذكاء الاصطناعي، تحليل البيانات وغيرها الكثير من المتطلبات التقنية للأنظمة الرقمية الحديثة. أهلا بكم في بايثونات ومقال جديد نتناول فيه موضوع فريد من نوعه، وهو كيفية التعامل مع قواعد البيانات الأوراكل من خلال لغة البرمجة بايثون.

ما هي قواعد البيانات الأوراكل ؟

الأوراكل هو اسم مشهور يُطلق غالبا على قاعدة البيانات الأوراكل التي أنتجتها شركة أوراكل الأمريكية. تقع شركة أوراكل في كالفورنيا، وتُعد أحد أكبر عمالقة التكنولوجيا على مستوى العالم، وهي شركة تقنية تقدم العديد من الحلول الرقمية، ومنها بالطبع قاعدة البيانات الأوراكل. إذًا، في الحقيقة، كلمة أوراكل ترتبط باسم الشركة الأم، وباسم قاعدة البيانات التي أنتجتها هذه الشركة، وهنا يجب أن نعرف أي منهما المقصود في الحديث، والذي يتم في الغالب فهمه من خلال سياق الكلام. في هذا المقال، سياق الحديث هنا هو عن قاعدة البيانات، وليس الشركة بحد ذاتها.

شعار شركة أوراكل
شعار شركة أوراكل

بشكل عام، قاعدة البيانات هي مجموعة من البيانات المُهيكلة والمرتبة بطريقة معينة ويتم حفظها على جهاز حاسوب، ويتم إدارتها من قبل نظام أو برنامج يُسمى Database Management System. الأوراكل هي أحد أنواع أنظمة إدارة قواعد البيانات التي تشمل قاعدة بيانات.

منذ اختراع الحاسوب، وعلى مدار عقود من التطور والتقدم، أُستخدمت الحواسيب لحفظ البيانات ومعالجتها، وكان الشكل الأول لطريقة حفظ البيانات هو استخدام الملفات Flat Files. افتقدت هذه الطريقة للكثير من الخصائص، واحتوت على العديد من المشاكل، مثل تكرار البيانات، وعدم المقدرة على ربطها بعلاقات، وعدم القدرة على إدارتها بطريقة تتمتع بالكفاءة.

في عام 1970، أطلق العالم Dr. Ted Codd ورقته البحثية التي غيرت معالم قواعد البيانات، حيث استطاع العالم Codd من خلال ورقته البحثية تقديم نموذج مُبتكر لإدارة البيانات وتمثيلها بطريقة تُسهل من العمل معها وإدارتها دون وجود للمشاكل والعيوب الموجودة في طريقة Flat Files. كان المميز في الورقة البحيثة، هي تقديم نموذج لإدارة البيانات يستند إلى مفهوم العلاقات بين النماذج models، حيث يُمكن ربط نموذجين بعلاقة تُساعد في الوصول لأي نموذج من خلال الآخر.

سُميت أنظمة قواعد البيانات التي تعتمد على نموذج Dr. Ted Codd بأنظمة قواعد البيانات العلاقية Relational Datanase Managment Systems والتي تُعد الأوراكل أشهرها.

اقرأ أيضًا: قواعد البيانات SQLite والبايثون – كيف تُنشئ قاعدة بيانات وتتعامل معها

خصائص قواعد البيانات الأوراكل

  1. تعدد المنصات: الأوراكل هي قاعدة بيانات متعددة المنصات يُمكن تنصيبها واستخدامها بسهولة في العديد من البيئات التشغيلية مثل وندوز، يونكس، لينكس، أنظمة تشغيل السيرفرات.
  2. العمل من خلال الشبكة: الأوراكل مناسبة لمفهوم تعدد الطبقات، وهو فصل التطبيق عن قاعدة البيانات عبر الشبكة، ويُمكن لتطبيق في نظام وندوز الوصول لقاعدة بيانات أوراكل موجودة في خادم يستخدم نظام لينكس كنظام تشغيلي.
  3. الهيكلية المنطقية: هيكلية قواعد البيانات الأوراكل هي هيكلية منطقية Logical data structure في تخزين البيانات والتي من خلالها نتعامل مع البيانات دون الاهتمام بمكان تخزينها أو وجودها.
  4. التقسيم: تعتمد الأوراكل على تقسيم الجداول الكبيرة إلى بيانات محفوظة في عدة ملفات، وذلك للوصول إلى سُرعة وكفاءة عالية في معالجة البيانات.
  5. نظام قوي للنُسخ الاحتياطية: تتميز الأوراكل بتقديم إمكانات لحفظ وإدارة النُسخ الاحتياطية واسترجاعها من خلال أدوات وأنظمة خاصة بذلك، مما يزيد من درجة حفظ البيانات وعدم ضياعها.
  6. الحوسبة السحابية: تدعم الأوراكل التقنيات الحديثة بشكل عام، ومن أشكال ذلك قدرتها في حفظ البيانات عبر الإنترنت ومن خلال السحابة وتقديم الأدوات والبرامج اللازمة لهذا الغرض.

هذه بعض من الخصائص والمميزات التي تتمتع بها قواعد البيانات الأوراكل.

إصدارات قواعد البيانات الأوراكل

يوجد 4 اصدارات عامة ومختلفة للأوراكل بخصائص مختلفة، وذلك حسب طبيعة الإستخدام والهدف المُراد منه استخدام الأوراكل، وبالطبع، طريقة الترخيص. الإصدارات كالتالي:

  1. Enterprise Edition: وهي الإصدار الأغلى ثمنًا ويُعد خيار الشركات الكُبرى التي تحتاج إلى خصائص متقدمة في قواعد البيانات. يتضمن هذا النوع من قواعد بيانات الأوراكل كافة الخصائص المتاحة والمُقدمة من الشركة مثل حلول الأمان العالي والمعالجة ذات الأداء العالي وغيرها من الخصائص.
  2. Standard Edition: هذا الإصدار تستخدمه الشركات والجهات التي لا تحتاج إلى القدرات المتقدمة في إصدار Enterprise.
  3. Personal Edition: هذا الإصدار هو للإستخدام بغرض التطوير ولا يسمح بأكثر من مستخدم واحد.
  4. Xpress Edition: يُمكن أن نُطلق على هذا الإصدار بأنه “إصدار الدعاية” والذي تستخدمه شركة أوراكل في تقديم نموذج لقواعد بياناتها يُمكن من خلاله تثبيته، إدارته، وتطوير التطبيقات باستخدامه بسهولة.

في هذا المقال، سنستخدم Xpress Edition (يُرمز لها اختصارًا بـ XE) برقم اصدار 11.2. يُمكنك الذهاب لموقع oracle.com وتحميل النسخة التي ترغب بها من هذا الإصدار وتثبيتها على جهاز الحاسوب لديك.

بايثون والأوراكل

الان لنأتي للموضوع الأهم في هذا المقال، وهو كيف يُمكننا تطوير تطبيقات، برامج، سكريبتات بايثون تستطيع أن تتخاطب مع الأوراكل؟ الإجابة هي: من خلال وحدة البايثون cx_oracle، وهي وحدة يُمكن باستخدامها الوصول لقاعدة بيانات أوراكل. تتوافق هذه المكتبة مع محددات Python database API 2.0 ولديها العديد من الإصدارات المختلفة التي تتناسب مع إصدارات البايثون والأواركل.

هيكيلة وحدة cx_oracle
هيكيلة وحدة cx_oracle [المصدر]

كيف نُثبت cx_oracle ؟

  1. في البداية، أُفضل دائمًا أن نُنشئ بيئة بايثون افتراضية. أستخدم لذلك أداة virtualenvwrapper (خياري المُفضل). سأسمي البيئة الافتراضية باسم cxOra:
    mkvirtualenv cxOra
  2. باستخدام مدير حزم البايثون pip، نثبت وحدة cx_oracle:
    pip install cx_oracle
  3. قبل البدء بالتخاطب مع قاعدة بيانات أوراكل، يجب أن يكون Oracle Client Library مثبتة، وهذا في حال أردنا الاتصال بقاعدة بيانات من غير نوع Xpress Edition. الصفحة التالية تتضمن مجموعة من إصدارات Oracle Client حسب نوع نظام التشغيل:
    تثبيت Oracle Clientيجب عليك أن تختار أحد إصدارات Client المناسبة لك وتثبيته على جهاز الحاسوب لديك. في هذا المقال، ولأننا استخدمنا Xpress Edition، لن نحتاج لتثبيت Client، حيث تأتي قاعدة البيانات XE متضمنة لها.

    اذا لم تستخدم XE ولم تقم بتثبيت Oracle Client، وحاولت الاتصال بقاعدة بيانات الأوراكل، سيظهر لك الخطأ التالي عند تشغيل كود بايثون باستخدام وحدة cx_oracle:

     

    cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: “The specified module could not be found”. See https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html for help

  4. للتأكد من عمل قاعدة البيانات ووحدة cx_oracle، نكتب الشيفرة البرمجية التالية في ملف بايثون وننفذه، مع مراعاة تغيير اسم قاعدة البيانات لتكون XE بدلًا من orclpdb1 اذا كنت تستخدم XE:
    import cx_Oracle

    # Connect as user "hr" with password "welcome" to the "orclpdb1" service running on this computer.
    connection = cx_Oracle.connect("hr", "welcome", "localhost/orclpdb1")

    cursor = connection.cursor()
    cursor.execute("""
    SELECT first_name, last_name
    FROM employees
    WHERE department_id = :did AND employee_id > :eid""",
    did = 50,
    eid = 190)
    for fname, lname in cursor:
    print("Values:", fname, lname)

    اذا ظهرت النتائج كما يلي، فهذا يعني أن شيفرة البايثون استطاعت الوصول لقاعدة البيانات وجلب البيانات منها (هذه نتائج حسب قاعدة بيانات XE المستخدمة في هذا المقال):

    Values: Randall Perkins
    Values: Sarah Bell
    Values: Britney Everett
    Values: Samuel McCain
    Values: Vance Jones
    Values: Alana Walsh
    Values: Kevin Feeney
    Values: Donald OConnell
    Values: Douglas Grant

    أما اذا ظهرت رسالة خطأ، فعليك بمراجعة خطواتك السابقة والتأكد منها. لمزيد من التفاصيل يُمكنك الرجوع للتوثيق الرسمي لمكتبة cx_oracle.

اقرأ أيضًا: قواعد البيانات SQLite والبايثون – كيف تُنشئ قاعدة بيانات وتتعامل معها

أمثلة لاستخدامات cx_oracle

بعد تثبيت cx_oracle والتأكد من نجاح الاتصال بين البايثون وقاعدة بيانات الاوراكل XE، سنحتاج الأن إلى التعرف على بعض المواضيع المهمة التي لها علاقة بكيفية استخدام وحدة cx_oracle والبدء بالتفاعل مع قاعدة البيانات.

تتضمن أوراكل XE مجموعة من schemas التي تحتوي جداول وبيانات جاهزة بغرض التدريب والتعلم وفحص الامكانات التي تقدمها الأوراكل. سنستخدم هنا schema الخاصة بإدارة الموظفين والمُسماة HR. من خلال الأمثلة التالية، سنتعرف على:

  • كيفية الاتصال بقاعدة البيانات ومعرفة الإصدار الخاص بها.
  • الاستعلام عن بيانات جدول ما.
  • تنفيذ أوامر CRUD.
  • تنفيذ دوال ووظائف أوراكل.

كيفية الاتصال بقاعدة البيانات ومعرفة الإصدار الخاص بها

في البداية نستورد وحدة cx_oracle، ثم نستخدم الدالة connect ونُمرر لها اسم المستخدم HR وكلمة المرور (التلقائية هي welcome)، ثم اسم قاعدة البيانات. تُرجع الدالة connect كائن من نوع <class ‘cx_Oracle.Connection’> يُمكن من خلاله الحصول على رقم اصدار قاعدة البيانات التي نتصل بها:

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")

print("Database Version: ", connection.version)

النتيجة ستكون مشابهة لما يلي:

Database Version:  11.2.0.2.0

يُمكننا التعرف على جميع خصائص الكائن connection من خلال استدعاء الدالة dir وتمرير الكائن لها:

>>> dir(connection)
['DataError', 'DatabaseError', 'Error', 'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError', 'OperationalError', 'ProgrammingError', 'Warning', '__class__', '__delattr__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'action', 'autocommit', 'begin', 'callTimeout', 'cancel', 'changepassword', 'client_identifier', 'clientinfo', 'close', 'commit', 'createlob', 'current_schema', 'cursor', 'dbop', 'deq', 'deqoptions', 'dsn', 'edition', 'encoding', 'enq', 'enqoptions', 'external_name', 'getSodaDatabase', 'gettype', 'handle', 'inputtypehandler', 'internal_name', 'ltxid', 'maxBytesPerCharacter', 'module', 'msgproperties', 'nencoding', 'outputtypehandler', 'ping', 'prepare', 'queue', 'rollback', 'shutdown', 'startup', 'stmtcachesize', 'subscribe', 'tag', 'tnsentry', 'unsubscribe', 'username', 'version']

الاستعلام عن بيانات جدول

يُوجد في HR Schema الجداول التالية:

COUNTRIES
DEPARTMENTS
EMPLOYEES
EMP_DETAILS_VIEW
JOBS
JOB_HISTORY
LOCATIONS
REGIONS

للاستعلام عن أسماء جميع الأقسام الموجودة في جدول Departments، نُعرف في البداية cursor وهو عبارة عن الكائن المسؤول عن تنفيذ جُمل SQL وإدارة مجموعة السجلات الناتجة عن التنفيذ. بعد ذلك نستدعي الدالة execute ونُمرر لها جملة SQL الخاصة بالاستعلام عن اسماء الأقسام في الجدول وهي select * from dempartments.

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")

cur = connection .cursor()
cur.execute("select * from DEPARTMENTS")
result_set = cur.fetchall()
for row in result_set:
print(row)

بعد ذلك نُعرف متغير باسم result_set والذي سيحتوي على مجموعة النتائج المُرجعة من تنفيذ جملة الاستعلام. يمكن الحصول على السجلات المرجعة من خلال دالة fetchall. نُعرف جملة for للمرور على السجلات وطباعتها. ستكون النتيجة كما يلي:

(10, 'Administration', 200, 1700)
(20, 'Marketing', 201, 1800)
(30, 'Purchasing', 114, 1700)
(40, 'Human Resources', 203, 2400)
(50, 'Shipping', 121, 1500)
(60, 'IT', 103, 1400)
(70, 'Public Relations', 204, 2700)
(80, 'Sales', 145, 2500)
(90, 'Executive', 100, 1700)
(100, 'Finance', 108, 1700)
(110, 'Accounting', 205, 1700)
(120, 'Treasury', None, 1700)
(130, 'Corporate Tax', None, 1700)
(140, 'Control And Credit', None, 1700)
(150, 'Shareholder Services', None, 1700)
(160, 'Benefits', None, 1700)
(170, 'Manufacturing', None, 1700)
(180, 'Construction', None, 1700)
(190, 'Contracting', None, 1700)
(200, 'Operations', None, 1700)
(210, 'IT Support', None, 1700)
(220, 'NOC', None, 1700)
(230, 'IT Helpdesk', None, 1700)
(240, 'Government Sales', None, 1700)
(250, 'Retail Sales', None, 1700)
(260, 'Recruiting', None, 1700)
(270, 'Payroll', None, 1700)

تنفيذ أوامر CRUD

بما أنه يُمكننا تنفيذ جُمل SQL ببساطة من خلال تمريرها كجملة نصية إلى الدالة excute، سيكون من السهل علينا إجراء أوامر CRUD في برنامجنا الذي نبرمجه بالبايثون. قبل البدء بعرض أمثلة على CRUD، يجب علينا أن نتعلم أمر مُهم سيساعدنا على كتابة الشيفرة البرمجية بطريقة سهلة وبكفاءة أفضل وأمان أعلى، هذا الأمر هو Data Binding.

يُمكننا Data Binding من إعادة تنفيذ جُمل SQL بتغيير قيم البيانات من دون إعادة كتابة جملة SQL. يُحسن ذلك من قراءة الشيفرة البرمجية، ويجعل البرنامج أكثر استقرارًا ويُجنبنا الوقوع ضحيةً لهجمات SQL injection.  المثال التالي يُوضح كيفية استخدام Data Binding في الاستعلام عن تفاصيل أقسام في جدول Departments حسب رقم القسم، وباستخدام جُملة استعلام واحدة:

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cur = connection .cursor()

sql_statement = "select * from DEPARTMENTS where department_id = :id"


cur.execute(sql_statement, id= 10)
result_set = cur.fetchall()
print(result_set)
#> [(10, 'Administration', 200, 1700)]

cur.execute(sql_statement, id= 60)
result_set = cur.fetchall()
print(result_set)
#> [(60, 'IT', 103, 1400)]

أضافة بيانات جديدة إلى جدول Countries

في المثال التالي، نعمل على اضافة مجموعة دُول جديدة إلى جدول countries كمثال على كيفية تنفيذ أمر insert متعدد السجلات من خلال البايثون ووحدة cx_oracle:

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cursor = connection.cursor()

data =[
('PS', 'Palestine', 4),
('SA', 'Saudi Arabia', 4),
('VI', 'Vitnam', 3)
]

sql_statement = "insert into countries values (:country_id, :country_name, :region_id)"

cursor.executemany(sql_statement, data)

# حفظ الإدخال الجديد على الجدول في قاعدة البيانات
connection.commit()

# الاستعلام للتأكد من اضافة السجلات الجديدة
cursor.execute('select * from countries')
result = cursor.fetchall()
print(result)

تعديل سجلات في جدول الموظفين Employees

في المثال التالي، نعمل على تعديل رواتب بعض الموظفين بحيث يتم زيادة الرواتب التي تقل عن 6000 بنسبة 10% لكل موظف. في البداية نستعلم عن الرواتب ونطبعها، ثم نُعدل، ثم نطبع الرواتب مرة أخرى للتأكد من التعديلات الجديدة:

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cursor = connection.cursor()

sql_statement = "select employee_id, salary from employees"
update_statement = "update employees set salary = salary * 1.1 where salary < 6000"



cursor.execute(sql_statement)
result = cursor.fetchall()

print("------------- Before update -------------")
for row in result:
print(row)

cursor.execute(update_statement)

cursor.execute(sql_statement)
result = cursor.fetchall()

print("------------- After update -------------")
for row in result:
print(row)

نلاحظ بعد طباعة النتائج وجود تغير على رواتب الموظفين حسب الشرط المُحدد. لتأكيد التعديلات على مستوى قاعدة البيانات، يجب أن نُنفذ امر commit من خلال دالة connection.commit.

حذف سجلات موظفين من جدول Employees

من خلال المثال التالي، سنعمل على حذف سجلات بيانات الموظفين أصحاب الأرقام: 202 و 107 و 206. في البداية، نتأكد من وجود سجلات الموظفين المعنيين، ثم نحذف الموظف الأول على حدة، وبعدها نحذف الأخرين من خلال جُملة واحدة وباستخدام Data Binding، ثم نتأكد من خلال جملة استعلام عدم وجود السجلات:

import cx_Oracle

connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cursor = connection.cursor()

delete_statement_1 = "delete from employees where employee_id = 202"
delete_statement_2 = "delete from employees where employee_id = :id"
sql_statement = "select employee_id, first_name from employees where employee_id in (202, 107, 206)"

cursor.execute(sql_statement)
result = cursor.fetchall()
print("------------- Before delete -------------")
for row in result:
print(row)


cursor.execute(delete_statement_1)

data = [(206, ), (107, )]
cursor.executemany(delete_statement_2, data)


cursor.execute(sql_statement)
result = cursor.fetchall()
print("------------- After delete -------------")
for row in result:
print(row)

تنفيذ دوال ووظائف أوراكل

يُمكن من خلال وحدة cx_oracle أن ننفذ دوال Functions ووظائف Procedures الموجودة في سكيما أوراكل، وذلك بكل سهولة. يُمكن تنفيذ دوال اوراكل من خلال استدعاء دالة callfunc الموجودة في كائن cursor، أما بالنسبة للوظائف، فيمكن تنفيذها من خلال دالة callproc الموجودة أيضًا في كائن cursor.

تنفيذ Procedure

وظائف الأواركل عبارة عن شيفرة برمجية بلغة PL/SQL لها اسم معين وتقوم بتنفيذ مهمة ما دون إرجاع أي نتيجة. تتضمن سكيما HR التي نعمل من خلالها في قاعدة بيانات XE على وظيفة باسم add_job_history يُمكن من خلالها اضافة سجل إلى جدول job_history. يجب أن نُمرر لهذه الوظيفة رقم الموظف، وتاريخ بدء عمله في قسم ما، وتاريخ انتهاء العمل، ورقم الوظيفة التي عمل فيها خلال تلك المدة، ورقم الدائرة. في المثال التالي نعمل على استدعاء هذه الوظيفة من خلال دالة callproc مع تعريف البيانات اللازمة مثل start_date و end_date:

import cx_Oracle
import datetime


connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cursor = connection.cursor()

start_date = datetime.datetime(2019, 1, 15)
end_date = datetime.datetime(2020, 8, 31)

cursor.callproc('add_job_history', (104, start_date, end_date, 'SA_REP', 60))
connection.commit()

نُلاحظ أن البيانات تم تمريرها لدالة callproc ككائن من نوع tuple.

تنفيذ Function

دوال الأوراكل عبارة عن شيفرة برمجية بلغة PL/SQL لها اسم معين وتقوم بتنفيذ مهمة ما وإرجاع قيمة. لا تتضمن سكيما HR دالة، لذا سنعمل على إنشاء هذه الوظيفة بأنفسنا. من خلال أداة sqlplus أو أي أداة مثل sqldeveloper أو Oracle Toad ننفذ شيفرة PL/SQL التالية:

create or replace function getSalaryTax(employee_id_in in number) return number as
v_salary number;
begin
select salary into v_salary from employees where employee_id = employee_id_in;

return v_salary * 0.05;
end;

بعد تنفيذ الشيفرة السابقة، نكون قد أنشأنا في قاعدة البيانات دالة باسم getSalaryTax يُسند لها رقم الموظف وتعيد لنا ضريبة راتبه بافتراض أن نسبة الضريبة هي 5%. لإستدعاء الدالة من خلال البايثون نقوم بالتالي:

import cx_Oracle
import datetime


connection = cx_Oracle.connect("HR", "welcome", "localhost/xe")
cursor = connection.cursor()

emp_id = 100

result = cursor.callfunc('getSalaryTax', int, (emp_id, ) )

print("Salary tax of employee is: ", result)

نُلاحظ أننا أسندنا لدالة callfunc كل من اسم دالة الاوراكل، ونوع البيانات المُرجعة، وقيمة المعامل الذي تستقبله الدالة وهو هنا رقم الموظف.

إلى هنا نكون قد انتهينا من شرح وحدة cx_oracle وعرجنا على كيفية تثبيت الوحدة وإجراء العمليات اللازمة للتعامل مع قاعدة البيانات وكيفية تنفيذ دوال ووظائف أوراكل من خلال البايثون. نتمنى أن نكون وُفقنا في شرح هذا الموضوع، واذا نال إعجابكم، سنسعد في بايثونات بمشاركتكم للمقال عبر منصات التواصل الاجتماعي ومتابعتنا عبر حساباتنا أيضً.

 

اترك ردًا

Your email address will not be published.