Ga naar de inhoud

Aan de slag

Deze handleiding leidt je door de installatie, het verbinden met een database, het vastleggen van snapshots, het berekenen van diffs en het uitvoeren van een drieweg-samenvoeging.

Installatie

Shell
composer require merql/merql

merql vereist PHP 8.2+ met ext-mbstring en ext-zlib (beide vereist door de afhankelijkheid pitmaster/pitmaster). Je hebt ook de extensie nodig voor de adapter die je gebruikt: ext-pdo met ext-pdo_sqlite of ext-pdo_mysql voor de PDO-adapter, of ext-mysqli voor de mysqli-adapter. Composer doet alleen suggesties, dus controleer of de benodigde extensie is geïnstalleerd.

Het pakket installeert de CLI als vendor/bin/merql. Gebruik merql 0.4.1 of later voor de CLI; in eerdere versies kon het binaire bestand de autoloader niet vinden wanneer merql als afhankelijkheid was geïnstalleerd.

Verbinding maken met een database

merql werkt via een kleine interface voor verbindingsadapters. Er zijn ingebouwde adapters voor PDO en mysqli, met ingebouwde dialectstuurprogramma's voor MySQL en 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() opent elke DSN die PDO ondersteunt, maar merql bevat alleen dialectstuurprogramma's voor MySQL en SQLite. Bij een andere database-engine, zoals pgsql:, wordt de verbinding wel geopend, maar geeft Merql::init() een Unsupported database driver: pgsql-fout totdat je er een stuurprogramma voor registreert. Zie Databasestuurprogramma's.

Een snapshot vastleggen

Een snapshot legt elke rij van elke tabel (of een deelverzameling van tabellen) vast als fingerprints en gegevens. Fingerprints maken snelle detectie van wijzigingen mogelijk; de volledige rijgegevens zijn beschikbaar wanneer een diff nodig is.

PHP
use Merql\Merql;

Merql::init($connection);

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

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

Snapshots worden standaard opgeslagen als JSON-bestanden in .merql/snapshots/. Je kunt deze locatie wijzigen:

PHP
use Merql\Snapshot\SnapshotStore;

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

Een diff berekenen

Een diff vergelijkt twee snapshots en retourneert een wijzigingenset met toevoegingen, updates en verwijderingen. Beide snapshots moeten al bestaan; hier is current een tweede snapshot die is gemaakt nadat de database was gewijzigd.

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

Drieweg-samenvoeging

De kernbewerking. Neemt drie benoemde snapshots (base, ours, theirs) en voegt de twee sets wijzigingen samen.

Base is de status waar beide kopieën vanuit zijn gestart. Ours en theirs zijn de twee kopieën nadat ze onafhankelijk zijn gewijzigd; leg elke kopie dus vast vanaf de database waarin deze zich bevindt. Bijvoorbeeld, wanneer een sandbox is gekopieerd vanuit een live database:

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

Maak niet alle drie de snapshots achter elkaar vanuit één database. Theirs bevat dan al de wijzigingen van ours, waardoor elke rij die ours heeft toegevoegd als een insert_insert-conflict wordt aangemerkt.

Merql::merge() voegt samen op kolomniveau. Gebruik ThreeWayMerge met een CellMergeConfig om ook binnen TEXT- en JSON-waarden samen te voegen; zie Samenvoeging op celniveau.

Conflicten afhandelen

Als beide kanten dezelfde kolom wijzigen in verschillende waarden, meldt merql een conflict. Je kunt conflicten automatisch oplossen of ze handmatig bekijken.

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

De samenvoeging toepassen

Zodra je een schoon samenvoegingsresultaat hebt (zonder onopgeloste conflicten), pas je het toe op de database die aan Merql::init() is doorgegeven.

De bewerkingen in een samenvoegingsresultaat zetten base om in de samengevoegde status en bevatten de wijzigingen van beide kanten. Merql::apply() voert ze allemaal uit, dus het resultaat wordt probleemloos toegepast op een database die nog de basisstatus bevat. Als je het toepast op een database die de wijzigingen van één kant al bevat, botst elke toevoeging van die kant met een bestaande rij, mislukt de bewerking en wordt de volledige samenvoeging teruggedraaid. Om de wijzigingen van de ene kant door te voeren in een database die de andere kant al bevat (bijvoorbeeld sandbox-bewerkingen naar live), pas je alleen de ontbrekende bewerkingen toe met Beveiligd toepassen.

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

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

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

De toepasser voert alle bewerkingen uit binnen één transactie. Als een instructie mislukt, wordt de volledige samenvoeging teruggedraaid en verschijnt de fout in errors(). In dat geval telt rowsAffected() nog steeds de instructies die vóór de fout zijn uitgevoerd, ook al zijn ze teruggedraaid (#4); controleer eerst hasErrors().

Proefuitvoering

Bekijk een voorbeeld van de SQL die een samenvoeging zou genereren, zonder deze uit te voeren.

PHP
use Merql\Apply\DryRun;

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

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

Tweezijdige patch

Als je alleen een base en een set wijzigingen hebt (zonder gelijktijdige wijzigingen van "ours"), gebruik je patch(). Dit komt overeen met een samenvoeging waarin ours ongewijzigd is ten opzichte van base.

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

De CLI gebruiken

Al deze bewerkingen zijn ook beschikbaar via de opdrachtregel. Raadpleeg de CLI-referentie voor alle details.

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

Maak voor een drieweg-samenvoeging elke snapshot vanuit de database die de betreffende status bevat en voeg ze daarna samen:

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

Tabellen en kolommen filteren

Snapshots kunnen tabellen uitsluiten (zoals cache- of sessietabellen) en veranderlijke kolommen negeren (zoals updated_at-tijdstempels).

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

Volgende stappen