Przejdź do treści

Pierwsze kroki

Ten przewodnik opisuje instalację, łączenie się z bazą danych, tworzenie migawek, obliczanie różnic i przeprowadzanie scalenia trójstronnego.

Instalacja

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

PHP
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.

PHP
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ę:

PHP
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.

PHP
$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:

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

PHP
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.

PHP
$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.

PHP
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.

PHP
$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.

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

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

PHP
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