Към съдържанието

Въведение

merql е чиста библиотека за PHP, която извършва тристранно сливане на състояния на база данни. Тя създава снимки на таблиците, изчислява набори от промени между снимките, слива два набора от промени спрямо обща база и открива конфликти на ниво колона. Използва същия алгоритъм, който git използва за файлове с изходен код, приложен към редове и колони вместо към редове текст.

Без външни инструменти. Поддържа MySQL и SQLite чрез адаптери за връзка и драйвери за диалекти, които могат да се добавят.

Кратък пример

За сливането са нужни три състояния на едни и същи данни: базата, от която са започнали двете копия, и двете копия след независимите им промени. В този пример тестовата база е копирана от работната, а след това и двете са редактирани.

PHP
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). За да запишете промените от тестовата база в работната, приложете операциите, които още липсват в работната, с Guarded Apply.

Или от 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() изпълнява всички тях без проблем само върху база данни, която все още е в базовото състояние. За да прехвърлите промените от едната страна в база данни, в която вече са налични промените от другата, например редакции от тестовата среда към работната, приложете само операциите, които липсват там; вижте Guarded 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 за всички класове.
  • Планове за сливане -- стабилни идентификатори на операции, групи промени и избиране.
  • Guarded Apply -- избрани записи с предварителни условия за текущите редове.
  • Връщане назад -- артефакти за връщане назад и проверки за отклонения.