Trevägssammanslagning
En trevägssammanslagning jämför två uppsättningar ändringar med en gemensam förfader och kombinerar dem till ett resultat. merql tillämpar algoritmen på databasrader i stället för fillinjer.
use Merql\Merge\ThreeWayMerge;
$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);
echo $result->operationCount(); // resolved operations
echo $result->conflictCount(); // unresolved conflicts
De tre tillstånden
Varje sammanslagning kräver exakt tre ögonblicksbilder:
- Bas -- databasläget vid den tidpunkt då båda grenarna skildes åt.
- Vår -- en gren efter sina ändringar, till exempel en redigerad sandlåsekopia.
- Deras -- den andra grenen efter sina ändringar, till exempel den aktiva databasen som fortsatte att köras.
Vår och deras måste var för sig samlas in från databasen som innehåller respektive gren. Sammanslagningsreglerna behandlar båda sidor likadant; namnen avgör varje operations source, hur konflikttyper benämns (update_delete betyder att vi uppdaterade och de tog bort) och vilken sida ConflictPolicy::OursWins och TheirsWins föredrar.
Base (snapshot at fork time)
/ \
Ours (our changes) Theirs (their changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Utan en gemensam bas går det inte att skilja mellan ”tillagd” och ”oförändrad”. En tvåvägsjämförelse visar att sida A har ett värde och sida B saknar det, men kan inte avgöra om A lade till det eller B tog bort det. Basen besvarar den frågan.
Så beräknas ändringsuppsättningarna
Differ jämför varje ögonblicksbild med basen för att skapa två ändringsuppsättningar. Varje ändringsuppsättning innehåller tre typer av operationer:
| Bas | Aktuellt | Operation |
|---|---|---|
| Rad finns | Rad finns, samma fingeravtryck | Ingen ändring |
| Rad finns | Rad finns, annat fingeravtryck | UPDATE (diff per kolumn) |
| Rad finns | Rad saknas | DELETE |
| Rad saknas | Rad finns | INSERT |
Fingeravtryck (SHA-256-hashar av normaliserat radinnehåll) ger en snabbväg. Om två fingeravtryck matchar är raden oförändrad, och ingen jämförelse på kolumnnivå behövs.
use Merql\Diff\Differ;
$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
Sammanslagningsregler
Därefter slås de två ändringsuppsättningarna samman operation för operation. Tabellen visar resultatet för varje kombination:
| Vår | Deras | Resultat |
|---|---|---|
| Ingen ändring | Ingen ändring | Behåll basen |
| Ingen ändring | Uppdaterad | Godta deras |
| Uppdaterad | Ingen ändring | Godta vår |
| Uppdaterad (samma värde) | Uppdaterad (samma värde) | Godta (båda är överens) |
| Uppdaterad (annat värde) | Uppdaterad (annat värde) | Konflikt |
| Ingen ändring | Borttagen | Godta borttagning |
| Borttagen | Ingen ändring | Godta borttagning |
| Borttagen | Borttagen | Godta borttagning (båda är överens) |
| Uppdaterad | Borttagen | Konflikt |
| Borttagen | Uppdaterad | Konflikt |
| Infogad (endast vår) | -- | Godta infogning |
| -- | Infogad (endast deras) | Godta infogning |
| Infogad (samma PK) | Infogad (samma PK) | Konflikt |
När båda sidor uppdaterar samma rad deklarerar merql inte omedelbart en konflikt. I stället går den vidare till sammanslagning på kolumnnivå och jämför varje kolumn separat. Mer information finns i Sammanslagning på kolumnnivå.
Använda fasaden
Fasaden Merql arbetar med namngivna ögonblicksbilder som har sparats på disk. Anropa Merql::init() med databasen som innehåller respektive tillstånd innan du skapar en ögonblicksbild:
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');
Merql::init($live);
Merql::snapshot('theirs');
$result = Merql::merge('base', 'ours', 'theirs');
Ta inte alla tre ögonblicksbilderna från en databas i följd. Deras ögonblicksbild skulle då även innehålla våra ändringar, så varje rad vi infogade skulle visas som infogad på båda sidor och rapporteras som en insert_insert-konflikt.
Merql::merge(), Merql::patch() och Merql::plan() skapar en ThreeWayMerge utan en CellMergeConfig, så de slås bara samman på kolumnnivå. För sammanslagning på cellnivå läser du in ögonblicksbilderna och använder klassen direkt, enligt exemplet nedan.
Använda klasserna direkt
Om du vill ha mer kontroll instansierar du komponenterna själv. Snapshotter::capture() sparar ingenting, så behåll ögonblicksbilderna i minnet eller spara dem med SnapshotStore::save():
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;
$base = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');
$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);
// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
SnapshotStore::load('base'),
SnapshotStore::load('ours'),
SnapshotStore::load('theirs'),
);
Genvägen med patch
Använd patch() när bara en sida har ändringar (inga samtidiga modifieringar). Det är en tvåvägssammanslagning där vår sida är lika med basen, så konflikter är omöjliga:
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
Via fasaden:
$result = Merql::patch('base', 'changes');
patch() motsvarar merge($base, $base, $changes). Eftersom vår sida aldrig avvek från basen godtas varje ändring från deras sida utan konflikt.
Identifiering av schemamismatchar
Om kolumner har lagts till i eller tagits bort från en tabell mellan ögonblicksbilderna fortsätter sammanslagningen ändå, men registrerar avvikelserna. Ändrade kolumntyper jämförs inte, och en kolumn med nytt namn visas som en borttagen och en tillagd kolumn; se Schemakontroll.
$result = $merge->merge($base, $ours, $theirs);
if ($result->hasSchemaMismatches()) {
foreach ($result->schemaMismatches() as $mismatch) {
echo $mismatch->getMessage() . "\n";
}
}
merql migrerar inte scheman automatiskt. Den som anropar ansvarar för schemaändringar. Sammanslagningen rapporterar dem så att du kan avgöra hur du vill gå vidare.
MergeResult
Resultatobjektet innehåller allt du behöver:
$result->isClean(); // true if no conflicts
$result->operations(); // list of MergeOperation objects
$result->conflicts(); // list of Conflict objects
$result->operationCount(); // number of resolved operations
$result->conflictCount(); // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot(); // the base snapshot used for the merge
Varje MergeOperation har en typ (insert, update eller delete), ett tabellnamn, en radnyckel, kolumnvärdena och en källa (ours, theirs eller merged). merged markerar en rad som båda sidor har uppdaterat (kombinerad kolumn för kolumn) eller som båda har tagit bort.
Var ett sammanslagningsresultat kan tillämpas
Ett sammanslagningsresultat beskriver hur basen omvandlas till det sammanslagna tillståndet. Operationerna innehåller båda sidornas ändringar: en infogning som vi gjort och en infogning som de gjort visas båda som infogningar.
Merql::apply()(ellerApplier) kör varje operation mot den anslutna databasen. Det fungerar utan problem på en databas som fortfarande innehåller basen, till exempel en ny kopia av bastillståndet. Om databasen redan innehåller den ena sidans ändringar misslyckas en infogning av en befintlig rad, och transaktionen återställs.- Om du vill föra över våra ändringar till en databas som redan innehåller deras, till exempel när sandlådeändringar tas i bruk i den aktiva miljön, tillämpar du bara de operationer som databasen saknar: de med källa
oursochmerged-uppdateringar. Använd en sammanslagningsplan för att välja dem och Skyddad tillämpning för att skriva dem med förvillkor för aktuella rader.