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

Трипосочно сливане

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

PHP
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts

Трите състояния

Всяко сливане изисква точно три моментни снимки:

  • Base -- състоянието на базата данни в момента, когато двата клона са се разделили.
  • Ours -- единият клон след промените си, например редактирано копие в изолирана среда.
  • Theirs -- другият клон след промените си, например активната база данни, която е продължила да работи.

Ours и theirs трябва да бъдат заснети от базата данни, която съдържа съответния клон. Правилата за сливане третират двете страни еднакво; имената определят source на всяка операция, имената на типовете конфликти (update_delete означава, че ours е актуализирал, а theirs е изтрил) и коя страна предпочитат ConflictPolicy::OursWins и TheirsWins.

Код
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

Без обща база не можете да различите „добавено“ от „непроменено“. Двустранното сравнение показва, че страна A има стойност, а страна B няма, но не може да определи дали A я е добавила, или B я е изтрила. Базата дава отговора.

Как се изчисляват наборите от промени

Differ сравнява всяка моментна снимка с базата, за да създаде два набора от промени. Всеки набор съдържа три вида операции:

Base Текущо Операция
Редът съществува Редът съществува, същият отпечатък Без промяна
Редът съществува Редът съществува, различен отпечатък UPDATE (разлика по колони)
Редът съществува Редът липсва DELETE
Редът липсва Редът съществува INSERT

Отпечатъците (SHA-256 хешове на нормализираното съдържание на реда) осигуряват бърз път. Ако два отпечатъка съвпадат, редът не е променен и не е нужно сравнение на ниво колона.

PHP
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);

Правила за сливане

След това двата набора от промени се сливат операция по операция. Тази таблица определя резултата за всяка комбинация:

Ours Theirs Резултат
Без промяна Без промяна Запазва се base
Без промяна Актуализирано Приема се theirs
Актуализирано Без промяна Приема се ours
Актуализирано (същата стойност) Актуализирано (същата стойност) Приема се (съгласие и от двете страни)
Актуализирано (различна стойност) Актуализирано (различна стойност) Конфликт
Без промяна Изтрито Приема се изтриването
Изтрито Без промяна Приема се изтриването
Изтрито Изтрито Приема се изтриването (съгласие и от двете страни)
Актуализирано Изтрито Конфликт
Изтрито Актуализирано Конфликт
Вмъкнато (само от ours) -- Приема се вмъкването
-- Вмъкнато (само от theirs) Приема се вмъкването
Вмъкнато (същия PK) Вмъкнато (същия PK) Конфликт

Когато и двете страни актуализират един и същ ред, merql не обявява веднага конфликт. Преминава към сливане на ниво колона и сравнява всяка колона поотделно. Вижте Сливане на ниво колона за подробности.

Използване на фасадата

Фасадата Merql работи с именувани моментни снимки, запазени на диска. Извикайте Merql::init() с базата данни, която съдържа всяко състояние, преди да направите снимката му:

PHP
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

$result = Merql::merge('base', 'ours', 'theirs');

Не правете и трите моментни снимки последователно от една база данни. Тогава theirs ще съдържа и промените на ours, така че всеки ред, вмъкнат от ours, ще изглежда вмъкнат и от двете страни и ще бъде отчетен като конфликт insert_insert.

Merql::merge(), Merql::patch() и Merql::plan() създават ThreeWayMerge без CellMergeConfig, затова сливат само на ниво колона. За сливане на ниво клетка заредете моментните снимки и използвайте класа директно, както е показано по-долу.

Директно използване на класовете

За повече контрол създайте компонентите сами. Snapshotter::capture() не съхранява нищо, затова пазете моментните снимки в паметта или ги запишете с SnapshotStore::save():

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);

Пряк път с кръпка

Когато само едната страна има промени (без едновременни редакции), използвайте patch(). Това е двустранно сливане, при което ours е равно на base, затова конфликти са невъзможни:

PHP
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.

През фасадата:

PHP
$result = Merql::patch('base', 'changes');

patch() е еквивалентно на merge($base, $base, $changes). Тъй като ours никога не се е отклонявал от base, всяка промяна от theirs се приема без конфликт.

Откриване на несъответствия в схемата

Ако между моментните снимки към таблица са добавени или премахнати колони, сливането продължава, но записва несъответствията. Типовете на променените колони не се сравняват, а преименуваната колона се показва като една премахната и една добавена; вижте Проверка на схемата.

PHP
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}

merql не мигрира схемите автоматично. Отговорността за промените в схемата е на извикващия код. Сливането ги отчита, за да можете да решите как да продължите.

MergeResult

Обектът с резултата съдържа всичко необходимо:

PHP
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge

Всяка MergeOperation има тип (insert, update или delete), име на таблица, ключ на реда, стойности на колоните и източник (ours, theirs или merged). merged обозначава ред, актуализиран и от двете страни (с обединяване на ниво колона), или изтрит и от двете страни.

Къде се прилага резултатът от сливането

Резултатът от сливането описва как base се превръща в обединеното състояние. Операциите му съдържат промените и от двете страни: вмъкнатият от ours ред и вмъкнатият от theirs ред се показват като вмъквания.

  • Merql::apply() (или Applier) изпълнява всяка операция спрямо свързаната база данни. Прилага се чисто към база данни, която все още съдържа base, например ново копие на базовото състояние. При прилагане към база данни, която вече съдържа промените на едната страна, вмъкване на съществуващ ред ще се провали и транзакцията ще бъде отменена.
  • За да прехвърлите промените на ours в база данни, която вече съдържа промените на theirs, например при прехвърляне на редакциите от изолирана среда към активната система, приложете само операциите, които липсват в тази база: тези с източник ours и актуализациите merged. Използвайте план за сливане, за да ги изберете, и Защитено прилагане, за да ги запишете с предварителни условия за активните редове.