Przejdź do treści

Scalanie trójstronne

Scalanie trójstronne porównuje dwa zestawy zmian ze wspólnym przodkiem i łączy je w jeden wynik. merql stosuje ten algorytm do wierszy bazy danych zamiast wierszy plików.

PHP
use Merql\Merge\ThreeWayMerge;

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

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

Trzy stany

Każde scalanie wymaga dokładnie trzech zrzutów:

  • Baza -- stan bazy danych w chwili rozgałęzienia obu gałęzi.
  • Nasza strona -- jedna z gałęzi po wprowadzeniu zmian, na przykład edytowana kopia piaskownicy.
  • Druga strona -- druga gałąź po wprowadzeniu zmian, na przykład działająca baza danych, która nadal była używana.

Naszą stronę i drugą stronę należy zarejestrować każdą z bazy danych zawierającej daną gałąź. Reguły scalania traktują obie strony jednakowo; nazwy określają źródło każdej operacji source, sposób nazywania typów konfliktów (update_delete oznacza, że nasza strona zaktualizowała, a druga usunęła wiersz) oraz to, której stronie sprzyjają ConflictPolicy::OursWins i TheirsWins.

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

Bez wspólnej bazy nie da się odróżnić elementu „dodano” od „bez zmian”. Porównanie dwóch stron pokazuje, że strona A ma wartość, a strona B jej nie ma, ale nie pozwala ustalić, czy strona A ją dodała, czy strona B usunęła. Odpowiedź daje baza.

Jak obliczane są zestawy zmian

Differ porównuje każdy zrzut z bazą, aby utworzyć dwa zestawy zmian. Każdy zestaw obejmuje trzy rodzaje operacji:

Baza Bieżący stan Operacja
Wiersz istnieje Wiersz istnieje, ten sam odcisk Bez zmian
Wiersz istnieje Wiersz istnieje, inny odcisk UPDATE (różnice w poszczególnych kolumnach)
Wiersz istnieje Brak wiersza DELETE
Brak wiersza Wiersz istnieje INSERT

Odciski (SHA-256 skróty znormalizowanej zawartości wiersza) zapewniają szybką ścieżkę. Jeśli dwa odciski są identyczne, wiersz pozostaje bez zmian i nie trzeba porównywać poszczególnych kolumn.

PHP
use Merql\Diff\Differ;

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

Reguły scalania

Następnie oba zestawy zmian są scalane operacja po operacji. Ta tabela określa wynik dla każdej kombinacji:

Nasze Ich Wynik
Bez zmian Bez zmian Zachowaj bazę
Bez zmian Zaktualizowano Przyjmij ich zmiany
Zaktualizowano Bez zmian Przyjmij nasze zmiany
Zaktualizowano (ta sama wartość) Zaktualizowano (ta sama wartość) Przyjmij (obie strony są zgodne)
Zaktualizowano (inna wartość) Zaktualizowano (inna wartość) Konflikt
Bez zmian Usunięto Przyjmij usunięcie
Usunięto Bez zmian Przyjmij usunięcie
Usunięto Usunięto Przyjmij usunięcie (obie strony są zgodne)
Zaktualizowano Usunięto Konflikt
Usunięto Zaktualizowano Konflikt
Wstawiono (tylko po naszej stronie) -- Przyjmij wstawienie
-- Wstawiono (tylko po ich stronie) Przyjmij wstawienie
Wstawiono (ten sam PK) Wstawiono (ten sam PK) Konflikt

Gdy obie strony aktualizują ten sam wiersz, merql nie zgłasza od razu konfliktu. Przechodzi do scalania na poziomie kolumn i porównuje każdą kolumnę osobno. Szczegóły znajdziesz w sekcji Scalanie na poziomie kolumn.

Korzystanie z fasady

Fasada Merql działa na nazwanych migawkach zapisanych na dysku. Przed utworzeniem migawki wywołaj Merql::init(), przekazując bazę danych zawierającą dany stan:

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');

Nie twórz po kolei wszystkich trzech migawek z jednej bazy danych. Migawka ich zmian zawierałaby wtedy również nasze zmiany, więc każdy wiersz dodany przez nas pojawiłby się jako dodany po obu stronach i zostałby zgłoszony jako insert_insert konflikt.

Merql::merge(), Merql::patch() i Merql::plan() tworzą ThreeWayMerge bez CellMergeConfig, więc scalają wyłącznie na poziomie kolumn. Aby scalać na poziomie komórek, wczytaj migawki i użyj bezpośrednio klasy, jak poniżej.

Bezpośrednie użycie klas

Aby uzyskać większą kontrolę, utwórz instancje komponentów samodzielnie. Snapshotter::capture() niczego nie zapisuje, więc przechowuj migawki w pamięci lub zapisz je za pomocą 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'),
);

Skrót patch

Gdy zmiany ma tylko jedna strona (brak równoczesnych modyfikacji), użyj patch(). To scalanie dwukierunkowe, w którym nasze zmiany są takie same jak baza, więc konflikty nie są możliwe:

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

Za pośrednictwem fasady:

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

patch() jest równoważne merge($base, $base, $changes). Ponieważ nasze zmiany nie odbiegły od bazy, wszystkie zmiany po ich stronie są przyjmowane bez konfliktu.

Wykrywanie niezgodności schematu

Jeśli między migawkami do tabeli dodano lub z niej usunięto kolumny, scalanie nadal przebiega, ale odnotowuje niezgodności. Zmienione typy kolumn nie są porównywane, a zmieniona nazwa kolumny jest wykazywana jako jedna usunięta i jedna dodana; zobacz sekcję Walidacja schematu.

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

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

merql nie przeprowadza automatycznej migracji schematów. Za zmiany schematu odpowiada wywołujący. Wynik scalania je zgłasza, aby można było zdecydować, co dalej.

Obiekt MergeResult

Obiekt wyniku zawiera wszystko, czego potrzebujesz:

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

Każdy MergeOperation ma typ (insert, update lub delete), nazwę tabeli, klucz wiersza, wartości kolumn i źródło (ours, theirs lub merged). merged oznacza wiersz zaktualizowany po obu stronach (połączony kolumna po kolumnie) lub usunięty po obu stronach.

Zastosowanie wyniku scalania

Wynik scalania opisuje, jak przekształcić bazę w stan po scaleniu. Jego operacje obejmują zmiany z obu stron: wstawienie wykonane przez nas i wstawienie wykonane przez nich są uwzględniane jako wstawienia.

  • Merql::apply() (lub Applier) wykonuje każdą operację na połączonej bazie danych. Działa poprawnie na bazie, która nadal zawiera stan bazowy, na przykład na świeżej kopii bazy. W przypadku bazy zawierającej już zmiany jednej ze stron wstawienie istniejącego wiersza kończy się niepowodzeniem i powoduje wycofanie transakcji.
  • Aby przenieść nasze zmiany do bazy zawierającej już ich zmiany, na przykład promując zmiany z piaskownicy do środowiska produkcyjnego, zastosuj tylko operacje, których brakuje w tej bazie: te ze źródłem ours oraz aktualizacje merged. Użyj planu scalania, aby je wybrać, i Zastosowania z ochroną, aby zapisać je z warunkami wstępnymi dotyczącymi bieżących wierszy.