جدول المحتويات
هيئة الملفات ونموذج البيانات
هيئة الملفات
بعد ملاحظة سرعة وفعالية sqlite3 في ثواب 2 تبين لنا أنها الحل الأفضل
وهي قاعدة بيانات RDBM مضمنة لا تحتاج إلى خادم بل تعتمد على الملفات انظر مقالة ويكيبيديا عنها
أطلقت Sqlite عام 1421 هـ (2001 م) بعد قيام مؤلفها بأبحاث لصالح البحرية للتحكم بالمدمرات الصاروخية.
وهي تدعم معايير SQL-92 بشكل كبير.
أغلب عيوبها لا تعني مشروع ثواب بل إنها تعتبر مزايا لها. لكن عيوبها التي تعني مشروعنا هي تغيير في هيئة الملف في الإصدارات الجديدة انظر
بعد التحدث مع مطوري sqlite فإن ذلك لم يحدث بين 3.5 و 3.6 حيث أن التغيير كان في بعض أجزاء الدوال وليس في هيئة الملفات
لكن حتى لو تغيّرت هيئة الملف يمكننا أن نضع ملفات .jar التي تتعامل مع الإصدار الذي نريد.
نموذج البيانات
بعد القيام بالعديد من الاختبارات الداخلية على عدد من الأمثلة إليكم نموذج البيانات الذي توصلنا له.
يجوز أن تتوزع الملفات في عدة مجلدات وتحديدا يعنينا أن يكون هناك على الأقل مكانين واحد عام للنظام لا يملك المستخدم الذي يشغل البرنامج صلاحيات التعديل فيه وآخر يفترض أن يكون له صلاحيات الكتابة حتى يحتفظ بالفهارس وأي معلومات خبيئة cached.
نموذج بيانات الميتا
يحتوي كل كتاب على معلومات عن الكتاب وليست منه تسمى ميتا. ومخطط جدول الميتا في كل كتاب هو:
CREATE TABLE "meta" ( "cache_hash" TEXT, "repo" TEXT, "lang" TEXT, "kitab" TEXT, "version" TEXT, "releaseMajor" INTEGER, "releaseMinor" INTEGER, "type" INTEGER, "author" TEXT, "year" INTEGER, "originalAuthor" TEXT, "originalYear" INTEGER, "originalKitab" TEXT, "originalVersion" TEXT, "classification" TEXT );
ولا يوجد فهارس لهذا الجدول في ملف الكتاب لأنه يتكون من صف واحد. يتم تجميع هذا الصف من كل الكتب ضمن إدارة البرنامج في جدول الميتا داخل النسخة الخبيئة مع إضافة الحقول التالية uri و mtime و flags والنسخة الخبيئة تحتوي على فهارس لجدول الميتا وذلك عبر
CREATE INDEX MetaURIIndex ON meta (uri); CREATE INDEX MetaRepoIndex ON meta (repo); CREATE INDEX MetaLangIndex ON meta (lang); CREATE INDEX MetaKitabIndex ON meta (kitab); CREATE INDEX MetaKitabVersionIndex ON meta (repo,kitab,version); CREATE INDEX MetaAuthorIndex ON meta (author); CREATE INDEX MetaYearIndex ON meta (YEAR); CREATE INDEX MetaOriginalAuthorIndex ON meta (originalAuthor); CREATE INDEX MetaOriginalYearIndex ON meta (originalYear); CREATE INDEX MetaClassificationIndex ON meta (classification);
أما معاني هذه الحقول فهي
الحقل | النوع | المعنى |
---|---|---|
cache_hash | TEXT | مقطع لبصمة مميزة إن تغيرت وجب تحديث الخبيئة والفهارس |
repo | TEXT | من أي مستودع هذا الكتاب |
lang | TEXT | اللغة الغالبة في الكتاب |
kitab | TEXT | معرف نصي لاسم الكتاب |
version | TEXT | الإصدارة وهي كلما زاد يعني نسخة أحدث ومنقحة ضمن نفس المستودع وهو يزاد عند التعديلات التي تكسر الروابط |
releaseMajor | INTEGER | رقم الإطلاق الكبير وهو يزاد كلما حدث تعديلات في المحتوى لكنها لا تكسر الروابط |
releaseMinor | INTEGER | رقم الإطلاق الصغير وهو يزاد كلما حدث تعديل خارج المحتوي بل في الوسوم أو في الميتا |
type | INTEGER | النوع: 0 كتاب عادي. 1 تفسير. 2. معجم أو تراجم |
author | TEXT | معرف نصي لأول مؤلف للنص المفرغ هنا والتفاصيل تكون من جدول المؤلفين الخاص بالمستودع |
year | INTEGER | العام الهجري لوفاته، 0 إن كان على قيد الحياة |
originalAuthor | TEXT | معرف مؤلف الكتاب الأول بصورته الاولى في حال الكتب المترجمة |
originalYear | INTEGER | العام الهجري لوفاة المؤلف الأول |
originalKitab | TEXT | معرف الكتاب الذي هذا ترجمته - يترك خاليا إن لم يكن مترجما |
originalVersion | TEXT | إصدار الكتاب الأصلي وحيث أن الروابط لا تختلف عند نفس الإصدار لا يهم من أي إطلاق |
classification | TEXT | قائمة بالتصنيفات تفصل بين الفروع علامة / وتفصل بين التصنيفات علامة : |
حقول في النسخة الخبيئة فقط | ||
uri | TEXT | العنوان الفريد غالبا هو مجرد مسار لملف الكتاب |
mtime | FLOAT | وقت آخر تعديل على ملف الكتاب كما يعرضه نظام التشغيل |
flags | INTEGER | خيارات - حاليا 2 إن تمت الفهرسة بشكل صحيح 0 إن لم يكن هناك فهارس و 1 إن كان هناك فهرس غير صالح |
تم تصميم هذه البيانات كي تترتب حسب تاريخ وفاة المؤلف وإن كان حيا فإن كان تاريخ الوفاة غير معروف تترتب تلك الكتب في آخر القائمة هجائيا. ويعود للمستخدم الاختيار بين المؤلف الأصلي أم المترجم إن كان الكتاب مترجما فإن لم يكن الكتاب مترجما يفترض أن تكون القيمتين ذاتها.
نموذج بيانات الكتاب
يتكون أي كتاب من 3 جداول أساسية إضافة إلى جدول الميتا. هذه الجداول هي:
- جدول العقد nodes وهي عناصر محتويات الكتاب
- جدول الوسوم tags وصفوفه هي كل الوسوم التي يمكن أن تطبق على أي عقدة
- جدول ربط هذه بتلك nodetags وصفوفه تربط أي عقدة بعدد من الوسوم وتعين قيمها إن كانت تأخذ قيم.
وبلغة SQL المخطط هو
CREATE TABLE "nodes" ( "idNum" INTEGER PRIMARY KEY NOT NULL, "content" TEXT, "parent" INTEGER, "globalOrder" INTEGER, "depth" INTEGER NOT NULL ); CREATE TABLE "tags" ( "idNum" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" VARCHAR NOT NULL, "flags" INTEGER NOT NULL, "comment" VARCHAR, "parent" INTEGER, "relation" INTEGER ); CREATE TABLE "nodesTags" ( "tagIdNum" INTEGER NOT NULL, "nodeIdNum" INTEGER NOT NULL, "param" VARCHAR, PRIMARY KEY ("tagIdNum", "nodeIdNum") ); CREATE INDEX NodesParentIndex ON nodes (parent); CREATE INDEX NodesNodesGlobalOrderIndex ON nodes (globalOrder); CREATE INDEX NodesDepthIndex ON nodes (depth); CREATE INDEX NodesTagTagIdNumIndex ON nodesTags(tagIdNum); CREATE INDEX NodesTagNodeIdNumIndex ON nodesTags(nodeIdNum); CREATE INDEX NodesTagParamIndex ON nodesTags(param); CREATE INDEX TagsName ON tags (name);
تلك الحقول تعني ما يلي:
جدول العقد nodes | ||
---|---|---|
idNum | INTEGER | معرف عددي فريد تلقائي |
content | TEXT | محتوى العقدة |
parent | INTEGER | معرف والد العقدة في شجرة الهرمية |
globalOrder | INTEGER | ترتيب الصف عند سبر الشجرة بطريقة العمق أولا |
depth | INTEGER | عمق العقدة - الجذر صفر وأول أولاده 1 |
جدول الوسوم tags | ||
idNum | INTEGER | معرف عددي فريد تلقائي |
name | TEXT | اسم الوسم |
flags | INTEGER | علامات تحدد نوع الوسم وسلوكه |
comment | TEXT | تعليق يشرح للمحررين كيفية استعمال هذا الوسم |
parent | INTEGER | والد هذا الوسم |
relation | INTEGER | علاقة الوسم |
العلامات هي
TAG_FLAGS_EXTERNAL_SOURCE | محتويات خارجية أي تجلب من خارج الكتاب |
---|---|
TAG_FLAGS_BYBOT | هذه العقدة وأحفادها مولدة آليا أي تعديلات فيها ستفقد عندما يعاد توليدها |
TAG_FLAGS_HEADER | هذه العقدة تمثل تبويب أي يجب أن يفهرس هو وأحفاده في حقل وثيقة فهرسة مستقلة ثم يتم استهلاك المحتويات بعد الفهرسة |
TAG_FLAGS_IX_TAG | هذه العقدة تحمل وسم بحث |
TAG_FLAGS_IX_FIELD | حقل فهرسة منفصل: أي أن هذه العقدة وأحفادها تفهرس مرة أخرى لكن في وثيقة فهرسة مستقلة أي لا يتم استهلاك المحتويات بعد الفهرسة |
TAG_FLAGS_IX_SKIP | تجاهل فهرسة محتويات العقدة وأحفادها |
TAG_FLAGS_PAD_CONTENT | تحتاج هذه العقدة أن تعزل عن سياقها بمسافة خالية whitespace إما مسافة أو سطر جديد في حالة TAG_FLAGS_FLOW_MASK |
TAG_FLAGS_FLOW_BLOCK | العقدة تكون في صندوق منفصل عن سير فقرة السياق |
TAG_FLAGS_FLOW_FLOAT | عقدة طافية |
TAG_FLAGS_FLOW_FOOTER | عقدة تذيل كالحواشي |
TAG_FLAGS_FLOW_HIDDEN | عقدة خفية. تعليق مثلا |
ملاحظات
- لا يجوز تطبيق TAG_FLAGS_HEADER على حفيد ل TAG_FLAGS_IX_FIELD لأن الباب أوسع من الحقل
- لا يجوز تطبيق إهمال الفهرسة TAG_FLAGS_IX_SKIP و الفهرسة في حقل في نفس الوفت.
- الهدف من حقل الفهرسة TAG_FLAGS_IX_FIELD هو تمكين البحث عن معلومة منفصلة عن بقية النص مثلا إن كنت تريد البحث عن كلمة “لهيعة” بشرط أن تكون في اسم الرواي وليس في سياق الكلام العادي فلا تخرج لك نتائج ترد فيها كلمة لهيعة في السياق.
- حقل TAG_FLAGS_IX_FIELD يجب أن لا يطبق على تبويب مباشرة بل على أحفاده لأنها إن طبقت على التبويب نفسه تؤدي إلى فهرسة التبويب مرتين.
اختبار النموذج
حجم النموذج وصلاحيته
تم اختباره عبر عمل سكربت يحول نص بطريقة تشبه الويكي إلى قاعدة بيانات بالنموذج المختار. وكانت النتيجة كما يلي:
الملف | الحجم كملف نصي | حجم النموذج | في الشاملة | في ثواب 2 |
---|---|---|---|---|
موطأ الإمام مالك | 2213 ك.ب | 2679 ك.ب | 12870 ك.ب | 2732 ك.ب |
صحيح البخاري | 9296 ك.ب | 11275 ك.ب | - | 11291 ك.ب |
طبعا تم تحديد الأبواب والنص دون وسم السند والمتن وشجرة الرواة …إلخ.
سرعة النموذج
استخراج جدول المحتويات كاملا لصحيح البخاري (11 ميغا) يستغرق أقل من جزء من عشرة من الثانية. علما أن الصفوف ليست مخزنة بشكل متسلسل!
time sqlite3 bukhari.db \ 'SELECT * FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE tags.name="header"; ' >/dev/null
على فرض أن كتاب الإيمان يحمل المعرف node.id=3 وبه حوالي 20 باب لكل باب صفين في قاعدة البيانات عقدة تحمل وسم التبويب header وأخرى متفرعة عنها تحمل المحتويات. لاستخراج باب الإيمان كله وأحفاده
time sqlite3 bukhari.db 'SELECT nodes.id, nodes.content FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE nodes.l2=3; ' >/dev/null real 0m0.016s user 0m0.001s sys 0m0.003s
أي أنها أقل من جزء من مئة جزء من الثانية.
ولنفرض أن الصف رقم 31 يحتوي على باب الحياء شعبة من الإيمان لاستخراج محتوياته
time sqlite3 bukhari.db 'SELECT nodes.id, nodes.content FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE nodes.l3=31; '
وقد كان الوقت أقل من جزء من ألف من الثانية.
لاحظ أننا نستخدم l2 أو l3 … بحسب عمق العقدة.
وقمت بتجربة الوقت اللازم لاستيراد كتاب بسيط من هيئة wiki النصية إلى نموذج البيانات 10 مرات ثم تم تكرار التجربة على صحيح البخاري 10 مرات هو الآخر.
بأمر يشبه ما يلي:
time for i in `seq 1 10`; do groovy source/Wiki2Db.groovy samples/test.txt; done
فكانت النتيجة
الطريقة | زمن الكتاب البسيط | زمن صحيح البخاري |
---|---|---|
بايثون | 0.477 | 27.857 |
جروفي | 11.259 | 151.007 |
جافا .class | 8.869 | 187.752 |
جروفي التكرار داخلها* | 1.900 | 178.089 |
جافا . class التكرار داخلها* | 1.632 | 180.584 |
لكن يبدو أن سبب تأخر جافا هو الوقت اللازم لتحميل جافا فلو وضعنا الحلقة التكرارية للاستيراد 10 مرات داخل الكود يكون الزمن أقل بكثير ومع أن هذا ظلم لبايثون حيث أننا قمنا بتحميل مفسر بايثون 10 مرات إلا انها ظلت متفوقة ويمكن ملاحظة التفوق عند النظر إلى الزمن عند استيراد ملف كبير مثل صحيح البخاري حيث أصبح زمن تحميل جروفي هامشيا.
وسبب تفوق بايثون إما استعمالها لمكتبة سي الخاصة ب sqlite مباشرة أو بسبب اغلاقي للخيار isolation_level في بايثون دون جروفي
cn=sqlite3.connect(fn, isolation_level=None)
طرق بديلة
بما أن xml لا تحقق أهداف التصميم لمشروعنا حيث لا يمكن التعامل معها بطريقة تزايدية أو جزئية ومن الصعب القفز فيها. لذا علينا درساة طرق بديلة ل xml ما يهما بشكل أساسي هو عملية Serialization لعدة أسباب منها
- لتوفير الذاكرة انظر هذا النقاش في القائمة البريدية http://mail.gnome.org/archives/xml/2009-February/msg00033.html.
- التمكن من تمثيل أجزاء غير نصية كالصور والوسائط المتعددة.
-
- cryptix-asn1 - java
- python-pyasn1 - python
- perl-Convert-ASN1 - perl
-
-
- libs11n and c11n
ملاحظات على كل منها
- حزم ASN1 متوفرة في مستودعات فيدورا وأوبنتو
- لا يوجد أي حزمة متعلقة ب hessian لا في فيدورا ولا في أوبنتو
- حزم protobuf متوفرة في فيدورا وأوبنتو
- مشاريع أخرى متعلقة ب document oriented databases اقترحها محمد التلاوي: (للنظر فيها لاحقا)