Przejdź do treści

Wprowadzenie

merql to czysta biblioteka PHP do przeprowadzania trójstronnych scaleń stanu bazy danych. Tworzy migawki tabel, oblicza zestawy zmian między migawkami, scala dwa zestawy zmian względem wspólnego stanu bazowego i wykrywa konflikty na poziomie kolumn. Ten sam algorytm, którego git używa do plików źródłowych, zastosowany do wierszy i kolumn zamiast linii.

Bez zewnętrznych narzędzi. Obsługuje MySQL i SQLite przez wymienne adaptery połączeń i sterowniki dialektów.

Szybki przykład

Scalenie wymaga trzech stanów tych samych danych: wspólnego stanu bazowego, od którego rozpoczęły obie kopie, oraz dwóch kopii po niezależnym wprowadzeniu zmian. W tym przykładzie piaskownicę skopiowano z działającej bazy danych, a od tego czasu zmiany wprowadzono w obu.

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";
}

Każda migawka pochodzi z bazy danych, która zawiera dany stan. Utworzenie wszystkich trzech kolejno z jednej bazy danych nie opisuje dwóch rozbieżnych kopii: theirs zawierałaby już zmiany ours, a każdy wiersz wstawiony przez ours zostałby zgłoszony jako konflikt insert_insert.

Wynik zawiera operacje, które przekształcają stan base w stan scalony, pochodzące od obu stron i oznaczone źródłem (ours, theirs lub merged). Aby zapisać zmiany z piaskownicy w działającej bazie, zastosuj operacje, których brakuje w tej bazie, za pomocą Zabezpieczone zastosowanie.

Lub z poziomu CLI, które łączy się z bazą danych wskazaną w MERQL_DB_DSN dla każdego snapshot:

Powłoka
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

Dlaczego merql

Większość narzędzi bazodanowych porównujących stan działa na poziomie wierszy: jeśli jakakolwiek kolumna w wierszu różni się między dwiema wersjami, cały wiersz jest oznaczany jako konflikt. Powoduje to fałszywe konflikty, gdy dwie osoby edytują różne kolumny tego samego wiersza.

merql scala na poziomie kolumn. Jeśli jedna strona zmieniła title, a druga status, zmiany zostaną poprawnie scalone bez konfliktu. Konflikt występuje tylko wtedy, gdy obie strony zmienią tę samą kolumnę na różne wartości.

W przypadku kolumn TEXT i JSON merql może zejść poziom niżej i scalać na poziomie komórek. Dwie zmiany różnych wierszy w kolumnie tekstowej lub różnych kluczy kolumny JSON można poprawnie scalić za pomocą tego samego algorytmu trójstronnego. Scalenie na poziomie komórek jest opcjonalne: przekaż CellMergeConfig do ThreeWayMerge. Fasada Merql i CLI scalają tylko na poziomie kolumn. Zobacz Scalanie na poziomie komórek.

Model scalania

Kod
         Base (snapshot at fork time)
        /                              \
   Ours (one copy's changes)            Theirs (the other copy's changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

Trzy stany, tak jak w git. Base to wspólny przodek. Ours i theirs to dwie kopie po ich rozdzieleniu, każda utworzona na podstawie własnej bazy danych. Reguły scalania traktują obie strony jednakowo. Nazwy pojawiają się w polu source każdej operacji, w typach konfliktów (update_delete oznacza, że ours zaktualizowało, a theirs usunęło) oraz w politykach rozstrzygania OursWins i TheirsWins. Bez wspólnego stanu bazowego nie można odróżnić elementu „dodano” od „bez zmian”.

Scalenie samo niczego nie zapisuje. Jego operacje przekształcają stan base w stan scalony, więc Merql::apply() wykonuje je wszystkie poprawnie tylko w bazie danych, która wciąż zawiera stan base. Aby przenieść zmiany jednej strony do bazy zawierającej już zmiany drugiej, na przykład zmiany z piaskownicy do działającej bazy, zastosuj tylko brakujące w niej operacje; zobacz Zabezpieczone zastosowanie.

Wymagania

  • PHP 8.2 lub nowszy.
  • ext-mbstring i ext-zlib, wymagane przez zależność pitmaster/pitmaster zapewniającą różnicę tekstową.
  • Rozszerzenie bazy danych dla używanego adaptera: ext-pdo z ext-pdo_sqlite lub ext-pdo_mysql albo ext-mysqli. composer.json merql jedynie sugeruje ext-pdo i ext-mysqli, dlatego Composer ich nie sprawdza.
  • Baza danych MySQL lub SQLite. Inne silniki wymagają niestandardowego sterownika.

Czym nie jest

merql nie jest narzędziem do migracji schematów. Nie replikuje baz danych ani nie synchronizuje ich w czasie rzeczywistym. Rozwiązuje jeden konkretny problem: na podstawie trzech stanów bazy danych tworzy czwarty, scalony stan z oznaczonymi konfliktami.

Dokumentacja