Guarded Apply
GuardedApplier wendet eine konfliktfreie MergeResult nur an, wenn die Zieldatenbank noch
mit dem erwarteten Live-Snapshot übereinstimmt. Das ist nützlich, wenn ein Zusammenführungsplan
früher geprüft wurde und sich Live-Zeilen vor der Anwendung geändert haben könnten.
Typischerweise werden Änderungen aus einer Sandbox in eine Live-Datenbank übernommen. Base ist
der Live-Stand zum Zeitpunkt, an dem die Sandbox daraus erstellt wurde, ours ist die bearbeitete Sandbox
und theirs ist der aktuelle Live-Stand. Das Ziel ist die Live-Datenbank, daher lautet der erwartete
Live-Snapshot theirs. Geschrieben werden die Operationen, die live noch fehlen (siehe Operationen auswählen).
use Merql\Connection;
use Merql\Merql;
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;
Merql::init(Connection::sqlite('/data/live.sqlite'));
$plan = Merql::plan('promote-1', 'base', 'ours', 'theirs');
// Skip rows with unresolved conflicts.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
$conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}
$ids = [];
foreach ($plan->operations as $operation) {
$missing = $operation->source === 'ours'
|| ($operation->source === 'merged' && $operation->type === 'update');
if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
$ids[] = $operation->id;
}
}
$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
$plan,
OperationSelection::fromIds($ids),
SnapshotStore::load('base'),
);
$applied = Merql::applyGuarded($selected, SnapshotStore::load('theirs'));
if ($applied->hasErrors()) {
foreach ($applied->errors() as $error) {
echo $error . "\n";
}
}
Merql::applyGuarded() schreibt in die an Merql::init() übergebene Datenbank. Der
erwartete Live-Snapshot muss dieselbe Datenbank beschreiben: Erfasse ihn aus dem Ziel und so kurz
vorher, dass die Vorbedingungen aussagekräftig sind.
Vorbedingungen
Guarded SQL ergänzt die generierten Anweisungen um Vorbedingungen für Live-Zeilen:
- Bei Aktualisierungen muss die Zielzeilenidentität vorhanden sein und die Werte der Nicht-Identitätsspalten müssen der erwarteten Live-Zeile entsprechen.
- Bei Löschungen muss die Zielzeilenidentität vorhanden sein und die Werte der Nicht-Identitätsspalten müssen der erwarteten Live-Zeile entsprechen.
- Bei Einfügungen darf die Zielidentität noch nicht vorhanden sein.
Wenn eine Anweisung keine Zeilen betrifft, behandelt merql den Plan als veraltet. Die
Transaktion wird zurückgerollt und das zurückgegebene ApplyResult enthält den Fehler
Optimistic precondition failed for guarded merge statement.
Bei MySQL betrifft eine Aktualisierung, deren Werte bereits der Live-Zeile entsprechen, ebenfalls null
Zeilen, da MySQL geänderte statt gefundene Zeilen zählt. Eine solche Aktualisierung gilt als veraltet,
obwohl die Zeile übereinstimmt (#2).
SQLite zählt gefundene Zeilen und akzeptiert sie. Das kann bei einer merged-
Aktualisierung passieren, wenn beide Seiten dieselbe Änderung an einer Zeile vorgenommen haben.
Operationen auswählen
Ein Zusammenführungsergebnis enthält die Operationen beider Seiten, da es den Basiszustand in den zusammengeführten Zustand überführt. Wird es auf ein Ziel angewendet, das bereits theirs enthält, beschreiben die von theirs stammenden Operationen Änderungen, die das Ziel schon hat, und ihre Vorbedingungen schlagen fehl:
| Operation | Zustand in einem Ziel mit theirs | Auswählen |
|---|---|---|
ours Einfügen, Aktualisieren oder Löschen |
Fehlt | Ja |
merged Aktualisierung (beide Seiten haben die Zeile geändert) |
Enthält die Spalten von theirs, ours fehlt | Ja |
theirs Einfügen |
Zeile vorhanden, daher betrifft die abgesicherte Einfügung null Zeilen | Nein |
merged Löschen (beide Seiten haben die Zeile gelöscht) |
Zeile ist bereits entfernt, daher betrifft das abgesicherte Löschen null Zeilen | Nein |
theirs Aktualisierung oder Löschung |
Bereits angewendet | Nein |
Das gesamte Ergebnis von Merql::merge() an applyGuarded() zu übergeben, schlägt daher fehl,
sobald theirs eine Zeile eingefügt oder beide Seiten eine Zeile gelöscht haben. Wähle zuerst die Operationen aus,
nach Quelle wie oben oder mit einem ChangeGroupSelection aus einem Prüf-UI. Lass Zeilen aus $plan->conflicts weg: Ein ausgewähltes Ergebnis ist immer konfliktfrei, und die Operation für eine Konfliktzeile enthält den Wert von ours in der betreffenden Spalte (siehe Zusammenführungspläne).
Ausgewählte Anwendung
Guarded Apply funktioniert mit ausgewählten Zusammenführungsergebnissen aus einem Zusammenführungsplan, einschließlich Auswahlen nach Änderungsgruppe.
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;
$selection = ChangeGroupSelection::fromIds([$plan->changeGroups[0]->id]);
$selected = (new SelectedMergeResultFactory())->fromChangeGroupSelection(
$plan,
$selection,
SnapshotStore::load($plan->baseSnapshot),
);
$applied = Merql::applyGuarded($selected, SnapshotStore::load($plan->theirsSnapshot));
Damit kann eine Host-Anwendung eine Teilmenge geprüfter Gruppen bereitstellen und zugleich sicherstellen, dass Live-Zeilen seit der Planerstellung unverändert geblieben sind.
SQL-Vorschau
Verwende SqlGenerator::generateGuarded(), um die abgesicherten Anweisungen zu prüfen,
ohne sie auszuführen.
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;
$statements = SqlGenerator::generateGuarded(
$selected,
SnapshotStore::load($plan->theirsSnapshot),
);
foreach ($statements as $statement) {
echo $statement['sql'] . "\n";
}
Die generierten Anweisungen sind weiterhin parametrisiert. Vorbedingungen werden bei Aktualisierungen und Löschungen
als zusätzliche WHERE-Klauseln und bei Einfügungen als NOT EXISTS-Prüfungen ausgedrückt.
Abgesicherte oder einfache Anwendung
Merql::apply() führt dieselben Operationen ohne Vorbedingungen aus. Aktualisierungen und
Löschungen sind dann erfolgreich, unabhängig davon, ob sich die Live-Zeile seit der Planerstellung geändert hat.
Eine Einfügung einer vorhandenen Zeile schlägt mit dem Duplikat-Schlüsselfehler der Datenbank fehl.
Verwende die einfache Anwendung für ein Ziel, von dem du weißt, dass es noch den Basiszustand enthält, und
Guarded Apply, wenn andere Schreibvorgänge das Ziel seit dem Snapshot verändert haben könnten.