Zum Inhalt springen

Einführung

merql ist eine reine PHP-Bibliothek für Drei-Wege-Merges von Datenbankzuständen. Sie erstellt Snapshots von Tabellen, berechnet Änderungen zwischen ihnen, führt zwei Änderungssätze anhand einer gemeinsamen Basis zusammen und erkennt Konflikte auf Spaltenebene. Derselbe Algorithmus wie bei git für Quelldateien, angewendet auf Zeilen und Spalten statt auf Zeilen im Text.

Keine externen Werkzeuge. Unterstützt MySQL und SQLite über austauschbare Verbindungsadapter und Dialekttreiber.

Schnellstart-Beispiel

Für ein Merge brauchst du drei Zustände derselben Daten: die gemeinsame Basis beider Kopien und die beiden Kopien nach unabhängigen Änderungen. Hier wurde eine Sandbox aus der Live-Datenbank kopiert und beide wurden seitdem bearbeitet.

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

Erstelle jeden Snapshot aus der Datenbank, in der der jeweilige Zustand vorliegt. Alle drei Snapshots nacheinander aus einer Datenbank zu erstellen, bildet keine zwei auseinanderentwickelten Kopien ab: Theirs würde bereits die Änderungen von Ours enthalten, und jede von Ours eingefügte Zeile würde als insert_insert-Konflikt gemeldet.

Das Ergebnis listet die Vorgänge auf, die Base in den zusammengeführten Zustand überführen. Sie stammen von beiden Seiten und sind jeweils mit ihrer Quelle markiert (ours, theirs oder merged). Um die Änderungen der Sandbox in Live zu übernehmen, wende mit Guarded Apply nur die Vorgänge an, die Live noch fehlen.

Oder über CLI, das für jedes snapshot eine Verbindung zur in MERQL_DB_DSN angegebenen Datenbank herstellt:

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

Warum merql

Die meisten Datenbankwerkzeuge zum Vergleichen von Zuständen arbeiten auf Zeilenebene: Unterscheidet sich in zwei Versionen auch nur eine Spalte einer Zeile, wird die gesamte Zeile als Konflikt markiert. Das führt zu falschen Konflikten, wenn zwei Personen verschiedene Spalten derselben Zeile bearbeiten.

merql führt Änderungen auf Spaltenebene zusammen. Ändert eine Seite title und die andere status, lassen sich diese Änderungen ohne Konflikt zusammenführen. Ein Konflikt entsteht nur, wenn beide Seiten dieselbe Spalte auf unterschiedliche Werte ändern.

Für TEXT- und JSON-Spalten kann merql mit einem Merge auf Zellenebene noch tiefer gehen. Zwei Änderungen an verschiedenen Zeilen einer Textspalte oder an verschiedenen Schlüsseln einer JSON-Spalte lassen sich mit demselben Drei-Wege-Algorithmus konfliktfrei zusammenführen. Das Merge auf Zellenebene ist optional: Übergib ein CellMergeConfig an ThreeWayMerge. Die Fassade Merql und CLI führen nur auf Spaltenebene zusammen. Siehe Cell-Level Merge.

Das Merge-Modell

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

Drei Zustände wie bei git: Base ist der gemeinsame Vorfahr. Ours und Theirs sind die beiden Kopien nach ihrer Aufspaltung, jeweils aus der zugehörigen Datenbank erfasst. Die Merge-Regeln behandeln beide Seiten gleich. Die Namen erscheinen im source jedes Vorgangs, in Konflikttypen (update_delete bedeutet, dass Ours aktualisiert und Theirs gelöscht hat) sowie in den Auflösungsrichtlinien OursWins und TheirsWins. Ohne gemeinsame Basis lässt sich „hinzugefügt“ nicht von „unverändert“ unterscheiden.

Ein Merge schreibt selbst nichts in die Datenbank. Seine Vorgänge überführen Base in den zusammengeführten Zustand. Merql::apply() führt sie daher nur dann erfolgreich aus, wenn die Datenbank noch Base enthält. Um die Änderungen einer Seite in eine Datenbank zu übernehmen, die bereits die andere enthält, etwa Sandbox-Änderungen in Live, wende nur die noch fehlenden Vorgänge an. Siehe Guarded Apply.

Voraussetzungen

  • PHP 8.2 oder neuer.
  • ext-mbstring und ext-zlib, erforderlich für die Abhängigkeit pitmaster/pitmaster, die den Text-Diff bereitstellt.
  • Eine Datenbankerweiterung für den verwendeten Adapter: ext-pdo mit ext-pdo_sqlite oder ext-pdo_mysql oder ext-mysqli. composer.json von merql schlägt nur ext-pdo und ext-mysqli vor; Composer prüft daher nicht, ob sie installiert sind.
  • Eine MySQL- oder SQLite-Datenbank. Für andere Engines brauchst du einen benutzerdefinierten driver.

Was es nicht ist

merql ist kein Werkzeug für Schema-Migrationen. Es repliziert keine Datenbanken und synchronisiert sie nicht in Echtzeit. Es löst ein konkretes Problem: Ausgehend von drei Datenbankzuständen einen vierten, zusammengeführten Zustand mit erkannten Konflikten erzeugen.

Dokumentation

  • Erste Schritte – Installation, erster Snapshot, erstes Merge.
  • CLI Reference – vollständige Referenz zum Befehl vendor/bin/merql.
  • PHP API Reference – vollständige API-Dokumentation aller Klassen.
  • Merge-Pläne – stabile Vorgangs-IDs, Änderungsgruppen und Auswahlmöglichkeiten.
  • Guarded Apply – ausgewählte Schreibvorgänge mit Vorbedingungen für Live-Zeilen.
  • Rollback – Rollback-Artefakte und Prüfungen auf Abweichungen.