Preskočiť na obsah

Začíname

Táto príručka vás prevedie inštaláciou, pripojením k databáze, zachytávaním snímok, výpočtom rozdielov a vykonaním trojstranného zlúčenia.

Inštalácia

Shell
composer require merql/merql

merql vyžaduje PHP 8.2+ s ext-mbstring a ext-zlib (obe vyžaduje závislosť pitmaster/pitmaster). Potrebujete aj rozšírenie pre použitý ovládač: ext-pdo s ext-pdo_sqlite alebo ext-pdo_mysql pre ovládač PDO, prípadne ext-mysqli pre ovládač mysqli. Composer tieto rozšírenia iba odporúča, preto skontrolujte, či je potrebné rozšírenie nainštalované.

Balík nainštaluje CLI ako vendor/bin/merql. Pre CLI použite merql 0.4.1 alebo novšiu verziu; v starších vydaniach binárny súbor nedokázal nájsť zavádzač, keď bol merql nainštalovaný ako závislosť.

Pripojenie k databáze

merql používa jednoduché rozhranie adaptéra pripojenia. Obsahuje vstavané adaptéry pre PDO a mysqli a vstavané ovládače dialektov pre MySQL a 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() otvára každý DSN podporovaný PDO, ale merql obsahuje ovládače dialektov iba pre MySQL a SQLite. Pri použití iného databázového systému, napríklad pgsql:, sa pripojenie otvorí, no Merql::init() vyvolá Unsupported database driver: pgsql, kým preň nezaregistrujete ovládač. Pozrite si Ovládače databáz.

Zachytenie snímky

Snímka zaznamenáva každý riadok každej tabuľky (alebo podmnožiny tabuliek) vo forme odtlačkov a údajov. Odtlačky umožňujú rýchlo zisťovať zmeny; úplné údaje riadkov sú k dispozícii pri porovnávaní.

PHP
use Merql\Merql;

Merql::init($connection);

// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');

// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);

Snímky sa predvolene ukladajú ako súbory JSON v adresári .merql/snapshots/. Toto umiestnenie môžete zmeniť:

PHP
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');

Výpočet rozdielu

Porovnanie dvoch snímok vráti súbor zmien obsahujúci vloženia, aktualizácie a odstránenia. Obe snímky už musia existovať; tu je current druhá snímka zachytená po zmene databázy.

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

Trojstranné zlúčenie

Základná operácia. Prevezme tri pomenované snímky (base, ours, theirs) a zlúči dve množiny zmien.

Base je stav, z ktorého vychádzali obe kópie. Ours a theirs sú dve kópie po tom, ako sa nezávisle zmenili, preto treba každú zachytiť z databázy, v ktorej sa nachádza. Napríklad keď bola testovacia kópia vytvorená z produkčnej databázy:

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

Nevytvárajte všetky tri snímky postupne z jednej databázy. Theirs by už obsahoval zmeny ours a každý riadok vložený v ours by sa zobrazil ako konflikt insert_insert.

Merql::merge() vykonáva zlúčenie na úrovni stĺpcov. Ak chcete zlúčiť aj obsah hodnôt TEXT a JSON, použite ThreeWayMerge s CellMergeConfig; pozrite si Zlučovanie na úrovni buniek.

Riešenie konfliktov

Keď obe strany zmenia ten istý stĺpec na rôzne hodnoty, merql nahlási konflikt. Konflikty môžete vyriešiť automaticky alebo ich preskúmať ručne.

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

Použitie zlúčenia

Keď máte čistý výsledok zlúčenia (bez nevyriešených konfliktov), použite ho v databáze zadanej cez Merql::init().

Operácie vo výsledku zlúčenia prevedú base na zlúčený stav a zahŕňajú zmeny oboch strán. Merql::apply() vykoná každú z nich, preto sa dajú bez problémov použiť v databáze, ktorá stále obsahuje stav base. Ak ich použijete v databáze, ktorá už obsahuje zmeny jednej strany, každý jej vložený riadok narazí na už existujúci riadok, operácia zlyhá a celé zlúčenie sa vráti späť. Ak chcete preniesť zmeny jednej strany do databázy, ktorá už obsahuje zmeny druhej strany (napríklad úpravy v testovacom prostredí do produkcie), použite iba operácie, ktoré jej chýbajú, pomocou Kontrolované použitie.

PHP
$applied = Merql::apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo "Error: {$error}\n";
    }
}

Aplikátor zabalí všetky operácie do transakcie. Ak ktorýkoľvek príkaz zlyhá, celé zlúčenie sa vráti späť a chyba sa vráti v errors(). V takom prípade rowsAffected() stále počíta príkazy vykonané pred zlyhaním, hoci boli vrátené späť (#4); najprv skontrolujte hasErrors().

Skúšobné spustenie

Zobrazte náhľad SQL, ktoré by zlúčenie vytvorilo, bez jeho vykonania.

PHP
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}

Dvojstranná záplata

Ak máte iba base a množinu zmien (bez súbežných úprav v „ours“), použite patch(). To zodpovedá zlúčeniu, pri ktorom sa „ours“ nezmenilo oproti base.

PHP
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);

Používanie CLI

Všetky tieto operácie sú dostupné aj z príkazového riadka. Úplné podrobnosti nájdete v CLI Referencia.

Shell
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

Pri trojstrannom zlúčení vytvorte každú snímku z databázy, ktorá obsahuje príslušný stav, a potom ich zlúčte:

Shell
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

Filtrovanie tabuliek a stĺpcov

Zo snímok možno vylúčiť tabuľky (napríklad tabuľky vyrovnávacej pamäte alebo relácií) a ignorovať nestále stĺpce (napríklad časové značky 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']),
]);

Ďalšie kroky