جدول المحتويات

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

مقدمة

يمكن استعمال ربط تطبيقات عكاشة مع قواعد البيانات مثل sqlite (دعمها مدمج في بايثون) أو mysql عبر حزم بايثون لهذا الغرض (مثل pymysql)

لكن في هذه الحالة يكون عليك متابعة عدد من الأمور مثل:

لذا هناك بديل لهذه الطريقة اسمه SQLAlchemy وحوله بُني الإكسير لتسهيله.

الإكسير عبارة عن ORM أي Object-relational mapping حيث تتحوّل الجداول والصفوف وعلاقاتها إلى كائنات.

تدعم SQLAlchemy عددا كبيرا من قواعد البيانات من بينها:

إنّ الإكسير يقوم بالكثير من التحسينات فهو مثلا يعمل اتصال واحد مع قاعدة البيانات إلاّ في حالة Sqlite يقوم بعمل اتصال لكل thread

الإكسير من خارج عكاشة

عمل المخطط

لنفترض أن لدينا قاعدة بيانات للكتب تتكوّن من جدول للمؤلفين وجدول للكتب وجدول للناشرين وجدول للتصنيفات من خلال keywords.

جدول الكتب يتكوّن من الحقول التالية:

جدول المؤلفون حيث يتكوّن من الحقول التالية:

جدول الناشرون

جدول التصنيفات بالكلمات المفتاحية

تنفيذ المخطط

لنكتب وحدة module تُعرّف هذه القاعدة كما يلي ونحفظها في ملف باسم bookstoreModel.py

from elixir import *
 
class Book(Entity):
  using_options(tablename="book")
  n = Field(Integer, primary_key=True, autoincrement=True)
  title = Field(Unicode(30), index=True)
  description = Field(UnicodeText)
  year = Field(Integer)
  isbn = Field(String(16), index=True, unique=True)
  authors = ManyToMany('Author', lazy=False)
  publisher = ManyToOne('Publisher', lazy=False)
  keywords = ManyToMany('Keywords', lazy=False)
 
class Author(Entity):
  using_options(tablename="author")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  details = Field(UnicodeText)
  year_of_birth = Field(Integer, index=True)
  year_of_death = Field(Integer, index=True, default=0)
  books = ManyToMany('Book', lazy=True)
 
class Publisher(Entity):
  using_options(tablename="publisher")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  books = OneToMany('Book', lazy=True)
 
class Keywords(Entity):
  using_options(tablename="keywords")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  books = ManyToMany('Book', lazy=True)

في نفس المجلد افتح بايثون التفاعلي واكتب فيه

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()
elixir.create_all()

الأمر الأخير create_all يعمل على إنشاء قاعدة البيانات (نستدعيه مرة واحدة حيث لاحقا نكتفي ب setup_all) وهي في مثالنا نتحدث مع قاعدة بيانات من نوع sqlite وهي موجودة في الملف db.sqlite لاحظ سطر bind

يمكنك طبعا استعمال mysql وتقديم اسم مستخدم وكلمة سر واسم القاعدة …إلخ مثلا

elixir.metadata.bind='mysql://user:pass@localhost/mytestdb'

لكن دعونا نتابع مستخدمين ملف sqlite وليس mysql. الآن اخرج من مفسر بايثون التفاعلي وانظر محتويات ملف db.sqlite عبر الأمر التالي:

sqlite3 db.sqlite .dump | less

ستلاحظ مخطط قاعدة البيانات schema دون مدخلات لأنها لا تزال خالية.

لنعد إلى مفسر بايثون التفاعلي ونفتح قاعدة البيانات بالأوامر

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()

في مراحل الاختبار يمكنك إظهار عمليات SQL حتى تراها بعينك وذلك بكتابة السطر

elixir.metadata.bind.echo = True

إضافة المدخلات

لنقم الآن بإدخال بعض الحقول في قواعد البيانات. ولنبدأ بعمل حقل في جدول الكلمات المفتاحية وذلك عبر الأمر

kw_s=Keywords(name=u"science")
kw_a=Keywords(name=u"art")
p1=Publisher(name=u"My Press")
p2=Publisher(name=u"Nashir2")
elixir.session.commit()

السطر الأول يعمل كائن من نوع Keywords ويعين خصائصه والتي هي name ويعطيها قيمة science وهذا يعني عمل صف في جدول keywords قيمة العمود name فيه هي science. يتم حفظ هذا الكائن في المتغير kw_s وهذا غير إلزامي لكنني فعلته لأني قد استخدمه لاحقا.

السطر الثاني عمل كائنا آخر وبالتالي صف آخر بقيمة أخرى. ثم علمنا ناشرين بنفس الطريقة. ولو قمت الآن بفتح قاعدة البيانات لن تجد فيها شيء ذلك أن الإكسير يجمع العمليات إلى أن تطلب منه إرسالها محسنة عبر الدالة elixir.session.commit.

لنضف الآن عدد من المؤلفين

a1=Author(name=u"Oqlah ibn Khalaf", details=u"short story pioneer in Balama", year_of_birth=1975)
a2=Author(name=u"John Random Hacker", details=u"fictional author from cyberspace", year_of_birth=1980)
elixir.session.commit()

لنضف الآن أحد الكتب

b1=Book(title=u"The twilight of Balama", description=u"a short story.", year=2010, isbn="0-1234-5678-9", publisher=p1)
b1.authors.append(a1)
b1.keywords.append(kw_a)
 
b2=Book(title=u"Facts you don't know", description=u"true stories from a fake author", year=2010, isbn="1-1234-5678-9", publisher=p2)
b2.authors.append(a2)
b2.keywords.append(kw_s)
 
elixir.session.commit()

لاحظ استخدام append لإضافة العلاقات. طبعا يمكنك استخدام الاستعلامات لتوليد الكائنات التي تمررها إلى append عوضا عن حفظ الكائنات في متغيرات

الاستعلام والتعديل

أغلق جلسة بايثون التفاعلي السابقة ولنبدأ واحدة جديدة ونكتب بدايتنا التقليدية

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()

لنستعلم عن شيء له معرف فريد أو فهرس فريد مثلا

b=Book.get_by(isbn="1-1234-5678-9")
print b.title

لاحظ استخدام get_by على الفئة نفسها دون كائن منها (يعرف هذا class method أو static method) مع تمرير لها معرف isbn للحصول على كائن الكتاب. وباملناسبة يمكننا التعديل على الكائن مباشرة حيث تعكس تلك التعديلات على قاعدة البيانات فور عمل commit مثلا لنفرض أني أريد زيادة علامة تعجب في نهاية العنوان

b.title+=u"!"
elixir.session.commit()

إن العلاقات تعمل بشكل تلقائي يعني لو استعلمنا عن الكتب تحت تصنيف art يمكننا طباعة أسماءها وأسماء مؤلفيها

kw=Keywords.get_by(name="art")
for b in kw.books:
  print "book: ",b.title,", authors: "
  for a in b.authors:
    print b.name,
  print "."

الاستعلام والفلترة

بطريقة مشابهة لنفرض أننا نريد كل الكتب التي طبعت بعد 1960

books=Book.query.filter(Book.year>1960)
for b in books: print b.title, "@", b.year

يمكننا أيضا تركيب الفلاتر وراء بعضها كذلك يمكننا ترتيب النتائج وأخذ أول كذا نتيجة

books=Book.query.filter(Book.year>1960).order_by(elixir.sqlalchemy.desc(Book.year)).limit(10)
for b in books: print b.title, "@", b.year

مثال على ربطها مع عكاشة