Pierwsze kroki
Ten przewodnik opisuje instalację, łączenie się z bazą danych, tworzenie migawek, obliczanie różnic i przeprowadzanie scalenia trójstronnego.
Instalacja
composer require merql/merql
merql wymaga PHP 8.2+ z ext-mbstring i ext-zlib (oba są wymagane przez zależność pitmaster/pitmaster). Potrzebujesz też rozszerzenia dla używanego adaptera: ext-pdo z ext-pdo_sqlite lub ext-pdo_mysql dla adaptera PDO albo ext-mysqli dla adaptera mysqli. Composer jedynie sugeruje te rozszerzenia, sprawdź więc, czy wymagane jest zainstalowane.
Pakiet instaluje CLI jako vendor/bin/merql. Użyj merql 0.4.1 lub nowszego dla CLI; wcześniejsze wydania nie mogły znaleźć autoloadera, gdy merql było zainstalowane jako zależność.
Łączenie z bazą danych
merql korzysta z niewielkiego interfejsu adaptera połączenia. Wbudowane adaptery obsługują PDO i mysqli, a wbudowane sterowniki dialektów obsługują MySQL i SQLite.
use Merql\Connection;
// SQLite (in-memory or file-based, through PDO).
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/path/to/database.sqlite');
// MySQL through PDO.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');
// MySQL through mysqli.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');
// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=my_database;charset=utf8mb4', 'root', 'password');
fromDsn() otwiera każdą bazę danych obsługiwaną przez PDO za pomocą DSN, ale merql zawiera sterowniki dialektów tylko dla MySQL i SQLite. W przypadku innego silnika, takiego jak pgsql:, połączenie zostanie otwarte, ale Merql::init() zgłosi Unsupported database driver: pgsql, dopóki nie zarejestrujesz dla niego sterownika. Zobacz Sterowniki baz danych.
Tworzenie migawki
Migawka zapisuje odciski palców i dane każdego wiersza z każdej tabeli (lub podzbioru tabel). Odciski umożliwiają szybkie wykrywanie zmian, a pełne dane wierszy są dostępne, gdy potrzebne jest obliczenie różnicy.
use Merql\Merql;
Merql::init($connection);
// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');
// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
Domyślnie migawki są zapisywane jako pliki JSON w katalogu .merql/snapshots/. Możesz zmienić tę lokalizację:
use Merql\Snapshot\SnapshotStore;
SnapshotStore::setDirectory('/var/data/merql-snapshots');
Obliczanie różnicy
Różnica porównuje dwie migawki i zwraca zestaw zmian obejmujący wstawienia, aktualizacje i usunięcia. Obie migawki muszą już istnieć; tutaj current to druga migawka utworzona po zmianie bazy danych.
$changeset = Merql::diff('baseline', 'current');
echo "Inserts: " . count($changeset->inserts()) . "\n";
echo "Updates: " . count($changeset->updates()) . "\n";
echo "Deletes: " . count($changeset->deletes()) . "\n";
// Inspect changes for a single table.
$postChanges = $changeset->forTable('posts');
// Check per-column diffs on updates.
foreach ($changeset->updates() as $update) {
echo "Row {$update->rowKey} in {$update->table}:\n";
foreach ($update->columnDiffs as $diff) {
echo " {$diff->column}: {$diff->oldValue} -> {$diff->newValue}\n";
}
}
Scalenie trójstronne
Główna operacja. Pobiera trzy migawki o podanych nazwach (base, ours, theirs) i scala dwa zestawy zmian.
Base to stan, od którego rozpoczęły pracę obie kopie. Ours i theirs to dwie kopie po niezależnym wprowadzeniu zmian, dlatego każda musi być utworzona na podstawie bazy danych, w której się znajduje. Na przykład, gdy piaskownica została skopiowana z działającej bazy danych:
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'); // the sandbox with our edits
Merql::init($live);
Merql::snapshot('theirs'); // live with its own changes
$result = Merql::merge('base', 'ours', 'theirs');
echo "Operations: {$result->operationCount()}\n";
echo "Conflicts: {$result->conflictCount()}\n";
echo "Clean: " . ($result->isClean() ? 'yes' : 'no') . "\n";
Nie twórz kolejno wszystkich trzech migawek z jednej bazy danych. Theirs zawierałaby już zmiany ours, więc każdy wiersz wstawiony przez ours zostałby zgłoszony jako konflikt insert_insert.
Merql::merge() scala na poziomie kolumn. Aby scalać także wewnątrz wartości TEXT i JSON, użyj ThreeWayMerge z CellMergeConfig; zobacz Scalanie na poziomie komórek.
Obsługa konfliktów
Gdy obie strony zmienią tę samą kolumnę na różne wartości, merql zgłasza konflikt. Możesz rozwiązać konflikty automatycznie lub przeanalizować je ręcznie.
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;
if (!$result->isClean()) {
// Auto-resolve: one side wins.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);
// Or inspect each conflict.
foreach ($result->conflicts() as $conflict) {
echo "Conflict on {$conflict->table()} row {$conflict->rowKey()}:\n";
echo " Type: {$conflict->type()}\n";
echo " Column: {$conflict->column()}\n";
echo " Ours: " . var_export($conflict->oursValue(), true) . "\n";
echo " Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
}
}
Zastosowanie scalenia
Gdy wynik scalenia jest czysty (nie ma nierozwiązanych konfliktów), zastosuj go do bazy danych przekazanej do Merql::init().
Operacje w wyniku scalenia przekształcają stan base w stan scalony i obejmują zmiany z obu stron. Merql::apply() wykonuje każdą z nich, dlatego można je poprawnie zastosować do bazy danych, która wciąż zawiera stan base. Jeśli zastosujesz je do bazy zawierającej już zmiany jednej ze stron, wstawienie dotyczące tej strony trafi na istniejący wiersz, zakończy się błędem i wycofa całe scalenie. Aby przenieść zmiany jednej strony do bazy zawierającej już zmiany drugiej strony (na przykład zmiany z piaskownicy do działającej bazy), zastosuj tylko brakujące w niej operacje za pomocą Zabezpieczone zastosowanie.
$applied = Merql::apply($result);
echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
foreach ($applied->errors() as $error) {
echo "Error: {$error}\n";
}
}
Program stosujący scalenie obejmuje wszystkie operacje transakcją. Jeśli dowolna instrukcja zakończy się błędem, całe scalenie zostanie wycofane, a błąd zwrócony w errors(). W takim przypadku rowsAffected() nadal uwzględnia instrukcje wykonane przed błędem, mimo że zostały wycofane (#4); najpierw sprawdź hasErrors().
Próbne uruchomienie
Wyświetl podgląd SQL, które wygenerowałoby scalenie, bez ich wykonywania.
use Merql\Apply\DryRun;
$statements = DryRun::generate($result);
foreach ($statements as $sql) {
echo "{$sql};\n";
}
Łatka dwukierunkowa
Gdy masz tylko stan base i zestaw zmian (bez równoległych modyfikacji ours), użyj patch(). Odpowiada to scaleniu, w którym ours pozostało bez zmian względem base.
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
Korzystanie z CLI
Wszystkie te operacje są również dostępne z wiersza poleceń. Szczegółowe informacje znajdziesz w CLI dokumentacji.
export MERQL_DB_DSN="sqlite:mydb.sqlite"
vendor/bin/merql snapshot base
# ... change the database ...
vendor/bin/merql snapshot current
vendor/bin/merql diff base current
W przypadku scalenia trójstronnego utwórz każdą migawkę na podstawie bazy danych zawierającej dany stan, a następnie przeprowadź scalanie:
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
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
Filtrowanie tabel i kolumn
Podczas tworzenia migawek można wykluczyć tabele (np. tabele pamięci podręcznej lub sesji) i pominąć zmienne kolumny (np. znaczniki czasu updated_at).
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;
$snapshotter = new Snapshotter($connection);
$snapshot = $snapshotter->capture('baseline', filters: [
TableFilter::exclude(['sessions', 'cache_*']),
ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
Następne kroki
- CLI Dokumentacja pełnego interfejsu wiersza poleceń.
- PHP API Dokumentacja ze szczegółowym opisem każdej klasy i metody.