مقدمة
merql مكتبة PHP خالصة تنفّذ عمليات دمج ثلاثية لحالة قاعدة البيانات. تلتقط لقطات للجداول، وتحسب مجموعات التغييرات بين اللقطات، وتدمج مجموعتي تغييرات استنادًا إلى أساس مشترك، وتكشف التعارضات على مستوى الأعمدة. إنها تستخدم الخوارزمية نفسها التي يستخدمها git لملفات المصدر، لكن على الصفوف والأعمدة بدلًا من الأسطر.
لا حاجة إلى أدوات خارجية. يدعم MySQL وSQLite عبر محوّلات اتصال ومشغّلات لهجات قابلة للتوصيل.
مثال سريع
يتطلب الدمج ثلاث حالات للبيانات نفسها: الأساس الذي بدأت منه النسختان، والنسختان بعد تغيّرهما بشكل مستقل. في هذا المثال، نُسخت بيئة اختبار من قاعدة البيانات المباشرة، وأُجريت تعديلات على كلتيهما منذ ذلك الحين.
use Merql\Connection;
use Merql\Merql;
$live = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');
// Base: live at the moment the sandbox was copied from it.
Merql::init($live);
Merql::snapshot('base');
// ... copy live.sqlite to sandbox.sqlite, then both databases change ...
// Ours: the sandbox with our edits. Theirs: live with its own changes.
Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');
$result = Merql::merge('base', 'ours', 'theirs');
if ($result->isClean()) {
foreach ($result->operations() as $operation) {
echo "{$operation->type} {$operation->table} #{$operation->rowKey} ({$operation->source})\n";
}
} else {
echo "Conflicts: {$result->conflictCount()}\n";
}
تُلتقط كل لقطة حالة من قاعدة البيانات التي تحتفظ بتلك الحالة. التقاط اللقطات الثلاث كلها من قاعدة بيانات واحدة بالتتابع لا يصف نسختين متباعدتين؛ إذ ستكون نسختهم قد تضمنت تغييرات نسختنا مسبقًا، وسيُبلّغ عن كل صف أدرجته نسختنا باعتباره تعارض insert_insert.
تسرد النتيجة العمليات التي تحوّل الأساس إلى الحالة المدمجة من كلا الجانبين، مع تحديد مصدر كل عملية (ours أو theirs أو merged). لكتابة تغييرات بيئة الاختبار في قاعدة البيانات المباشرة، طبّق العمليات التي لا تتضمنها قاعدة البيانات المباشرة بعد باستخدام التطبيق المحروس.
أو باستخدام CLI، الذي يتصل بقاعدة البيانات المسماة في MERQL_DB_DSN لكل snapshot:
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
# ... copy live.sqlite to sandbox.sqlite, then both databases change ...
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
لماذا merql
تعمل معظم أدوات قواعد البيانات التي تقارن الحالات على مستوى الصفوف: فإذا اختلف أي عمود في صف بين نسختين، عُدّ الصف بأكمله متعارضًا. وينتج عن ذلك تعارضات زائفة عندما يعدّل شخصان عمودين مختلفين في الصف نفسه.
ينفّذ merql الدمج على مستوى الأعمدة. فإذا غيّر أحد الجانبين title وغيّر الجانب الآخر status، اندمج التغييران بسلاسة من دون تعارض. ولا يُثار تعارض إلا إذا غيّر الجانبان العمود نفسه إلى قيمتين مختلفتين.
بالنسبة إلى أعمدة TEXT وJSON، يستطيع merql التعمق مستوى إضافيًا باستخدام الدمج على مستوى الخلية. يمكن دمج تعديلين في سطرين مختلفين من عمود نصي، أو في مفتاحين مختلفين من عمود JSON، بسلاسة باستخدام الخوارزمية الثلاثية نفسها. الدمج على مستوى الخلية اختياري: مرّر CellMergeConfig إلى ThreeWayMerge. تقتصر واجهة Merql ومحوّل CLI على الدمج على مستوى الأعمدة. راجع الدمج على مستوى الخلية.
نموذج الدمج
Base (snapshot at fork time)
/ \
Ours (one copy's changes) Theirs (the other copy's changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
ثلاث حالات، كما في git. الأساس هو السلف المشترك. ونسختنا ونسختهم هما النسختان بعد تباعدهما، وتُلتقط كل منهما من قاعدة بياناتها. تتعامل قواعد الدمج مع الجانبين على قدم المساواة. تظهر التسميات في source لكل عملية، وفي أنواع التعارضات (يعني update_delete أن نسختنا حدّثت بينما حذفت نسختهم)، وفي سياسات الحل OursWins وTheirsWins. من دون أساس مشترك، لا يمكن التمييز بين «مضاف» و«لم يتغير».
لا يكتب الدمج أي شيء بحد ذاته. تحوّل عملياته الأساس إلى الحالة المدمجة، لذا لا يشغّل Merql::apply() جميعها بنجاح إلا على قاعدة بيانات ما زالت في الحالة الأساسية. لإدخال تغييرات أحد الجانبين إلى قاعدة بيانات تتضمن تغييرات الجانب الآخر بالفعل، مثل ترقية تعديلات بيئة الاختبار إلى قاعدة البيانات المباشرة، طبّق العمليات الناقصة فيها فقط؛ راجع التطبيق المحروس.
المتطلبات
- PHP 8.2 أو أحدث.
ext-mbstringوext-zlib، وهما مطلوبان من تبعيةpitmaster/pitmasterالتي توفر مقارنة النصوص.- امتداد قاعدة البيانات الخاص بمحوّلك:
ext-pdoمعext-pdo_sqliteأوext-pdo_mysql، أوext-mysqli. يكتفيcomposer.jsonفي merql باقتراحext-pdoوext-mysqli، لذا لا يتحقق Composer من وجودهما. - قاعدة بيانات MySQL أو SQLite. تتطلب المحركات الأخرى مشغّلًا مخصصًا.
ما ليس عليه
merql ليس أداة لترحيل المخططات. ولا ينسخ قواعد البيانات أو يزامنها في الوقت الفعلي. إنه يحل مشكلة محددة: إنتاج حالة رابعة مدمجة مع تحديد التعارضات انطلاقًا من ثلاث حالات لقاعدة البيانات.
التوثيق
- البدء -- التثبيت، ولقطة الحالة الأولى، والدمج الأول.
- مرجع CLI -- مرجع كامل لأمر
vendor/bin/merql. - مرجع PHP API -- توثيق API كامل لجميع الفئات.
- خطط الدمج -- معرّفات عمليات ثابتة، ومجموعات تغييرات، واختيارات.
- التطبيق المحروس -- عمليات كتابة محددة مع شروط مسبقة للصفوف المباشرة.
- التراجع -- عناصر التراجع وفحوصات الانحراف.