Hoppa till innehåll

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.

PHP
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.

Kod
         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.

PHP
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:

PHP
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():

PHP
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:

PHP
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.

Via fasaden:

PHP
$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.

PHP
$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:

PHP
$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() (eller Applier) 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 ours och merged-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.