Ga naar de inhoud

Inleiding

merql is een pure PHP-bibliotheek die drieweg-samenvoegingen uitvoert op de status van databases. De bibliotheek maakt snapshots van tabellen, berekent wijzigingensets tussen snapshots, voegt twee sets wijzigingen samen op basis van een gedeelde basis en detecteert conflicten op kolomniveau. Hetzelfde algoritme dat git gebruikt voor bronbestanden, toegepast op rijen en kolommen in plaats van regels.

Geen externe tools. Ondersteunt MySQL en SQLite via verwisselbare verbindingsadapters en dialectstuurprogramma's.

Snel voorbeeld

Voor een samenvoeging zijn drie statussen van dezelfde gegevens nodig: de basis waar beide kopieën vanuit zijn gestart en de twee kopieën nadat ze onafhankelijk zijn gewijzigd. Hier is een sandbox gekopieerd vanuit de live database en zijn beide sindsdien bewerkt.

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

Elke snapshot wordt gemaakt vanuit de database die de betreffende status bevat. Alle drie achter elkaar vanuit één database maken beschrijft geen twee uiteenlopende kopieën: theirs bevat dan al de wijzigingen van ours, waardoor elke rij die ours heeft toegevoegd als een insert_insert-conflict wordt aangemerkt.

Het resultaat vermeldt de bewerkingen van beide kanten die base omzetten in de samengevoegde status, elk voorzien van de herkomst (ours, theirs of merged). Pas voor het overzetten van de sandbox-wijzigingen naar live de bewerkingen toe die live nog niet heeft, met Beveiligd toepassen.

Of gebruik de CLI, die verbinding maakt met de database die voor elke snapshot is opgegeven in MERQL_DB_DSN:

Shell
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

Waarom merql

De meeste databasetools die statussen vergelijken, werken op rijniveau: als een kolom in een rij verschilt tussen twee versies, wordt de hele rij als conflict aangemerkt. Daardoor ontstaan onterechte conflicten wanneer twee mensen verschillende kolommen van dezelfde rij bewerken.

merql voegt samen op kolomniveau. Als de ene kant de title heeft gewijzigd en de andere kant de status, worden die wijzigingen probleemloos samengevoegd. Alleen als beide kanten dezelfde kolom wijzigen in verschillende waarden, ontstaat er een conflict.

Voor TEXT- en JSON-kolommen kan merql met samenvoeging op celniveau nog een niveau dieper gaan. Twee bewerkingen op verschillende regels van een tekstkolom, of op verschillende sleutels van een JSON-kolom, kunnen probleemloos worden samengevoegd met hetzelfde drieweg-algoritme. Samenvoeging op celniveau is optioneel: geef een CellMergeConfig door aan ThreeWayMerge. De Merql-gevel en de CLI voegen alleen samen op kolomniveau. Zie Samenvoeging op celniveau.

Het samenvoegingsmodel

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

Drie statussen, net als bij git. Base is de gemeenschappelijke voorouder. Ours en theirs zijn de twee kopieën nadat ze uiteen zijn gaan lopen; elke kopie wordt vastgelegd vanuit de eigen database. De samenvoegregels behandelen beide kanten hetzelfde. De namen verschijnen in de source van elke bewerking, in conflicttypen (update_delete betekent dat ours is bijgewerkt en theirs is verwijderd) en in het beleid voor het oplossen van OursWins en TheirsWins. Zonder een gemeenschappelijke basis kun je geen onderscheid maken tussen "toegevoegd" en "ongewijzigd".

Een samenvoeging schrijft zelf niets weg. De bewerkingen ervan zetten base om in de samengevoegde status, dus Merql::apply() voert ze allemaal probleemloos uit, maar alleen op een database die nog de basisstatus bevat. Om de wijzigingen van één kant over te zetten naar een database die de andere kant al bevat, bijvoorbeeld sandbox-bewerkingen naar live, pas je alleen de ontbrekende bewerkingen toe; zie Beveiligd toepassen.

Vereisten

  • PHP 8.2 of later.
  • ext-mbstring en ext-zlib, vereist door de afhankelijkheid pitmaster/pitmaster die de tekstdiff levert.
  • Een database-extensie voor de gebruikte adapter: ext-pdo met ext-pdo_sqlite of ext-pdo_mysql, of ext-mysqli. De composer.json van merql beveelt alleen ext-pdo en ext-mysqli aan, dus Composer controleert niet of deze zijn geïnstalleerd.
  • Een database van het type MySQL of SQLite. Voor andere engines is een aangepast stuurprogramma nodig.

Wat het niet is

merql is geen schema-migratietool. Het repliceert geen databases en synchroniseert ze niet in realtime. Het lost één specifiek probleem op: gegeven drie databasestatussen een vierde samengevoegde status produceren, met conflicten aangeduid.

Documentatie