انتقل إلى المحتوى

SQL انعكاس

يدفع SqlMirror مجموعات storh إلى جداول SQLite أو MySQL لتتمكن من إجراء عمليات الربط والترتيب عبر الجداول والبحث عن السلاسل الفرعية وإعداد التقارير في SQL، مع بقاء الملفات هي المصدر المعتمد. اعتبره فهرسًا ثانويًا يتخذ هيئة قاعدة بيانات: فالنسخة المرآتية مشتقة ويمكن التخلص منها وإعادة بنائها في أي وقت. لا يلزم ext-pdo مع برنامج التشغيل sqlite أو mysql إلا عند استخدامه؛ وتبقى الحزمة الأساسية بلا اعتماديات.

الإعداد

PHP
use Storh\DocStore;
use Storh\Schema;
use Storh\SqlMirror;

$schema = Schema::collection('pages')
    ->string('slug')->unique()
    ->string('kind')->index()
    ->int('publishedAt')->range();

$pages  = new DocStore($root, 'pages', schema: $schema);
$events = new Storh\SegmentedLog($root, 'events');

$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'), 'app_');
$mirror->collection($pages, 'pages', $schema);
$mirror->collection($events, 'events');

$mirror->install();
$mirror->push();

يمكن إنشاء نسخة مرآتية لكلا المحركين: مجموعات DocStore وتدفقات SegmentedLog (تُدفع السجلات النشطة فقط). تمرير Schema اختياري ومستقل عن المخزن: تصبح الحقول المعلنة string/int/float/bool أعمدة مفهرسة ذات أنواع محددة؛ وتبقى بقية الحقول قابلة للاستعلام في عمود data JSON. لا يمكن تحديد الحقول mixed للفهرسة.

الاتصالات

يقبل المُنشئ مقبض PDO (برنامج تشغيل sqlite أو mysql، ويُكتشف تلقائيًا)، أو مقبض mysqli، أو أي تطبيق لـ SqlMirrorConnection:

PHP
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'));
$mirror = new SqlMirror(new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', $user, $password));
$mirror = new SqlMirror(new mysqli('127.0.0.1', $user, $password, 'app'), 'wp_storh_');

يعمل مسار mysqli مع كلا وضعي الإبلاغ عن الأخطاء في mysqli، لذا يمكن إعادة استخدام مقبض موجود من تطبيق مضيف (مثل $wpdb->dbh لدى WordPress) كما هو. السلوك متماثل عبر برامج التشغيل؛ إذ تؤدي حالات فشل الدفع إلى التراجع عن معاملة المجموعة في جميعها.

يحصل كل جدول على id (المفتاح الأساسي)، وhash (يُستخدم لإجراء عمليات الدفع التدريجي)، والأعمدة ذات الأنواع المحددة، وdata (JSON السجل الكامل).

المزامنة

PHP
$mirror->push();                    // reconcile: insert, update, delete as needed
$mirror->push('pages');             // reconcile a single collection
$mirror->flush('pages', $ids);      // push or remove just these ids
$mirror->pull();                    // write mirror rows back into the store
$mirror->verify();                  // report missing, stale, and orphaned rows
$mirror->rebuild();                 // clear all rows and push from scratch
$mirror->uninstall();               // drop the mirror tables

يقارن push() تجزئات السجلات بالنسخة المرآتية ويكتب كل مجموعة ضمن معاملة واحدة، لذا لا تترك عملية الدفع المتعطلة أو الفاشلة دفعةً مطبقة جزئيًا. لا تكلّف السجلات التي لم تتغير سوى عملية SELECT واحدة بالإضافة إلى المرور على السجلات. ويحافظ تشغيل push() دوري (مثلًا عند كل نبضة من cron) على تقارب النسخة المرآتية؛ واستدعِ flush() مع المعرّفات التي كتبتها أثناء الطلب عندما يحتاج القراء إلى رؤية كتاباتهم مباشرةً في النسخة المرآتية.

الاستعلام

استعلم عن النسخة المرآتية باستخدام SQL العادي. يعرض table() اسم الجدول المنسوخ:

PHP
$pdo->query(
    'SELECT p.slug, COUNT(*) AS sales
     FROM ' . $mirror->table('purchases') . ' o
     INNER JOIN ' . $mirror->table('products') . ' p ON p.id = o.product_id
     GROUP BY p.slug
     ORDER BY sales DESC'
);

استعادة الملفات من النسخة المرآتية

يقرأ pull() صفوف النسخة المرآتية ويكتبها في المخزن المسجّل، حسب ترتيب المعرّف. وهو يدعم حالتين:

  • الاستعادة: اختفت الملفات لكن النسخة المرآتية ما زالت موجودة. سجّل مخزنًا جديدًا بالاسم نفسه للمجموعة ثم اسحب البيانات.
  • التهيئة الأولية: تتحول الصفوف التي حمّلتها أدوات خارجية إلى جدول النسخة المرآتية إلى سجلات storh. يجب أن تحمل الصفوف معرّفات UUIDv7 صالحة وكائنًا في العمود data؛ ويُتجاهل عند السحب محتوى الأعمدة ذات الأنواع المحددة، لأن data هو السجل بأكمله.
PHP
$restored = new Storh\DocStore($root, 'pages');
$mirror   = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull();          // ['written' => n, 'unchanged' => m]

تُكتب البيانات المسحوبة عبر آليات المخزن المعتادة، لذا ينطبق التحقق من المخطط والفهارس الفريدة، ويُكتب كل سجل بشكل دائم. لا يحذف pull() السجلات المحلية أبدًا، ويتجاوز السجلات التي تطابق بياناتها صف النسخة المرآتية بالفعل. بعد تهيئة الصفوف المدرجة يدويًا، شغّل push() مرة واحدة لإعادة كتابة هذه الصفوف بالتنسيق المعتمد كي تتطابق تجزئاتها.

الشروط والحدود

  • الملفات هي المصدر المعتمد. النسخة المرآتية مخرجات للقراءة فقط؛ وكل ما يُكتب فيها يدويًا سيُستبدل أو يُحذف في عملية push() التالية.
  • الأعمدة الفريدة في النسخة المرآتية بمثابة خط دفاع إضافي، وليست آلية إنفاذ وقت الكتابة: إذ يؤدي التكرار الذي سمحت به الملفات إلى فشل عملية الدفع والتراجع عنها بالكامل. استخدم الفهارس الفريدة في storh لإنفاذ التفرد وقت الكتابة.
  • قد تتأخر النسخة المرآتية عن الملفات بمقدار فاصل دفع واحد، باستثناء المعرّفات التي تحددها صراحةً باستخدام flush().
  • لا تضيف النسخ المرآتي معاملات إلى storh نفسه. تظل الكتابات الذرية متعددة السجلات من مهام قاعدة بيانات حقيقية تكون مصدر السجلات المعتمد.
  • تُطبّق تغييرات المخطط بحذف النسخة المرآتية وإعادة تثبيتها: uninstall()، install()، push().