Merge auf Spaltenebene
Das Merge auf Spaltenebene ist der wichtigste Vorteil von merql gegenüber dem naiven Vergleich auf Zeilenebene. Ändern beide Seiten dieselbe Zeile, vergleicht merql jede Spalte einzeln, statt die ganze Zeile als Konflikt zu markieren.
Das Problem mit Merges auf Zeilenebene
Betrachte eine Tabelle posts, in der zwei Personen dieselbe Zeile bearbeiten:
Base: post #42 { title: "Hello", content: "Body", status: "draft" }
Ours: post #42 { title: "Hello", content: "Body v2", status: "draft" }
Theirs: post #42 { title: "New Title", content: "Body", status: "publish" }
Ein Merge auf Zeilenebene erkennt, dass beide Seiten die Zeile geändert haben, und meldet einen Konflikt. Die Änderungen überschneiden sich jedoch nicht. Ours hat content bearbeitet, Theirs dagegen title und status.
So löst das Merge auf Spaltenebene das Problem
merql wendet dieselben Drei-Wege-Merge-Regeln einzeln auf jede Spalte an:
title: base="Hello", ours="Hello", theirs="New Title" -> accept theirs
content: base="Body", ours="Body v2", theirs="Body" -> accept ours
status: base="draft", ours="draft", theirs="publish" -> accept theirs
Ergebnis: { title: "New Title", content: "Body v2", status: "publish" }. Konfliktfrei zusammengeführt.
So funktioniert es
Wenn ThreeWayMerge erkennt, dass beide Seiten dieselbe Zeile aktualisiert haben, übergibt es sie an ColumnMerge::merge():
use Merql\Merge\ColumnMerge;
$result = ColumnMerge::merge(
table: 'posts',
rowKey: '42',
base: ['title' => 'Hello', 'content' => 'Body', 'status' => 'draft'],
ours: ['title' => 'Hello', 'content' => 'Body v2', 'status' => 'draft'],
theirs: ['title' => 'New Title', 'content' => 'Body', 'status' => 'publish'],
);
// $result['values'] -> merged column values
// $result['conflicts'] -> list of Conflict objects (empty if clean)
Für jede Spalte gelten dieselben Regeln:
| Basis | Unsere Seite | Gegenseite | Ergebnis |
|---|---|---|---|
| A | A | A | Basis beibehalten (keine Änderung) |
| A | A | B | Gegenseite übernehmen |
| A | B | A | Unsere Seite übernehmen |
| A | B | B | Übernehmen (beide stimmen überein) |
| A | B | C | Konflikt (unterschiedliche Werte) |
Wann trotzdem Konflikte auftreten
Ein Konflikt auf Spaltenebene tritt auf, wenn beide Seiten dieselbe Spalte auf unterschiedliche Werte geändert haben:
Base: post #42 { title: "Hello" }
Ours: post #42 { title: "Welcome" }
Theirs: post #42 { title: "Greetings" }
Beide haben title auf unterschiedliche Werte geändert. Das ist ein echter Konflikt, der gelöst werden muss.
$result = ColumnMerge::merge(
'posts', '42',
['title' => 'Hello'],
['title' => 'Welcome'],
['title' => 'Greetings'],
);
// $result['conflicts'] contains one Conflict:
// table: "posts", column: "title"
// oursValue: "Welcome", theirsValue: "Greetings", baseValue: "Hello"
Teilkonflikte
Eine einzelne Zeile kann sowohl konfliktfrei zusammengeführt werden als auch Konflikte enthalten. Wird eine Spalte konfliktfrei zusammengeführt, während eine andere einen Konflikt aufweist, enthält MergeOperation für die Zeile die zusammengeführten Werte der konfliktfreien Spalten und standardmäßig den Wert unserer Seite für die Spalten mit Konflikten. Der Konflikt wird zusätzlich separat gemeldet.
Base: { title: "Hello", content: "Body", status: "draft" }
Ours: { title: "Welcome", content: "Body v2", status: "draft" }
Theirs: { title: "Greetings", content: "Body", status: "publish" }
Hier liegt bei title ein Konflikt vor (beide Seiten haben die Spalte unterschiedlich geändert), content wird konfliktfrei zusammengeführt (nur unsere Seite hat die Spalte geändert) und status wird konfliktfrei zusammengeführt (nur die Gegenseite hat die Spalte geändert). Die Operation enthält:
// values: { title: "Welcome", content: "Body v2", status: "publish" }
// One conflict on the "title" column.
Wertvergleich
Spaltenwerte werden nach dem Umwandeln als Zeichenfolgen verglichen. NULL ist ein eigener Wert:
NULLzuNULL= keine Änderung.NULLzu"hello"= Aktualisierung."hello"zuNULL= Aktualisierung."1"zu1= keine Änderung (beide werden zur Zeichenfolge"1").
Das entspricht der Abrufsemantik des MerQL-Verbindungsadapters: Alle Datenbankwerte werden als Zeichenfolgen (oder NULL) zurückgegeben.
Zusammenführung auf Zellenebene
Wenn die Zusammenführung auf Spaltenebene weiterhin einen Konflikt ergibt (beide Seiten haben dieselbe Spalte auf unterschiedliche Werte geändert), kann merql mit der Zusammenführung auf Zellenebene noch eine Ebene tiefer gehen. Das gilt für TEXT-Spalten (zeilenweiser Diff) und JSON-Spalten (Schlüssel-für-Schlüssel-Diff) und löst Fälle, in denen beide Seiten unterschiedliche Teile desselben Spaltenwerts bearbeitet haben.