Třícestné sloučení
Třícestné sloučení porovnává dvě sady změn vůči společnému předkovi a kombinuje je do jednoho výsledku. merql používá tento algoritmus na databázové řádky místo řádků souborů.
use Merql\Merge\ThreeWayMerge;
$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);
echo $result->operationCount(); // resolved operations
echo $result->conflictCount(); // unresolved conflicts
Tři stavy
Každé sloučení vyžaduje přesně tři snímky:
- Základ -- stav databáze v okamžiku, kdy se obě větve rozdělily.
- Naše -- jedna větev po provedení změn, například upravená sandboxová kopie.
- Jejich -- druhá větev po provedení změn, například živá databáze, která dál fungovala.
Stavy naší a jejich strany musí být zaznamenány z databáze, která danou větev obsahuje. Pravidla sloučení zacházejí s oběma stranami stejně; názvy určují source každé operace, názvy typů konfliktů (update_delete znamená, že naše strana řádek aktualizovala a jejich jej odstranila) a to, kterou stranu upřednostňují ConflictPolicy::OursWins a TheirsWins.
Base (snapshot at fork time)
/ \
Ours (our changes) Theirs (their changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Bez společného základu nelze rozlišit „přidáno“ od „beze změny“. Dvoucestné porovnání ukáže, že strana A má hodnotu a strana B ji nemá, ale nedokáže určit, zda ji A přidala, nebo ji B odstranila. Odpověď poskytne základ.
Jak se počítají sady změn
Differ porovná každý snímek se základem a vytvoří dvě sady změn. Každá sada obsahuje tři druhy operací:
| Základ | Aktuální stav | Operace |
|---|---|---|
| Řádek existuje | Řádek existuje, stejný otisk | Beze změny |
| Řádek existuje | Řádek existuje, jiný otisk | UPDATE (rozdíl po sloupcích) |
| Řádek existuje | Řádek chybí | DELETE |
| Řádek chybí | Řádek existuje | INSERT |
Otisky (hash hodnot SHA-256 normalizovaného obsahu řádku) představují rychlou cestu. Pokud se dva otisky shodují, řádek se nezměnil a není třeba porovnávat jednotlivé sloupce.
use Merql\Diff\Differ;
$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
Pravidla sloučení
Obě sady změn se poté sloučí operaci po operaci. Tato tabulka uvádí výsledek každé kombinace:
| Naše | Jejich | Výsledek |
|---|---|---|
| Beze změny | Beze změny | Ponechat základ |
| Beze změny | Aktualizováno | Přijmout jejich |
| Aktualizováno | Beze změny | Přijmout naši verzi |
| Aktualizováno (stejná hodnota) | Aktualizováno (stejná hodnota) | Přijmout (obě strany souhlasí) |
| Aktualizováno (jiná hodnota) | Aktualizováno (jiná hodnota) | Konflikt |
| Beze změny | Odstraněno | Přijmout odstranění |
| Odstraněno | Beze změny | Přijmout odstranění |
| Odstraněno | Odstraněno | Přijmout odstranění (obě strany souhlasí) |
| Aktualizováno | Odstraněno | Konflikt |
| Odstraněno | Aktualizováno | Konflikt |
| Vloženo (pouze naše strana) | -- | Přijmout vložení |
| -- | Vloženo (pouze jejich strana) | Přijmout vložení |
| Vloženo (stejný PK) | Vloženo (stejný PK) | Konflikt |
Když obě strany aktualizují stejný řádek, merql ihned nevyhlásí konflikt. Přejde ke sloučení na úrovni sloupců a porovná každý sloupec samostatně. Podrobnosti najdete v části Sloučení na úrovni sloupců.
Použití fasády
Fasáda Merql pracuje s pojmenovanými snímky uloženými na disk. Před pořízením snímku zavolejte Merql::init() nad databází, která obsahuje příslušný stav:
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');
Nezaznamenávejte všechny tři snímky postupně z jedné databáze. Jejich snímek by pak obsahoval také změny naší strany, takže každý námi vložený řádek by vypadal jako vložený na obou stranách a byl by nahlášen jako konflikt insert_insert.
Merql::merge(), Merql::patch() a Merql::plan() vytvoří ThreeWayMerge bez CellMergeConfig, takže sloučí pouze na úrovni sloupců. Chcete-li sloučit na úrovni buněk, načtěte snímky a použijte přímo třídu, jak je uvedeno níže.
Přímé použití tříd
Pro větší kontrolu vytvořte instance komponent sami. Snapshotter::capture() nic neukládá, proto uchovávejte snímky v paměti nebo je ukládejte pomocí 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'),
);
Zkratka pro záplatu
Pokud změny provedla jen jedna strana (nedošlo k souběžným úpravám), použijte patch(). Jde o dvoucestné sloučení, při kterém se naše strana rovná základu, takže ke konfliktům nemůže dojít:
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
Přes fasádu:
$result = Merql::patch('base', 'changes');
patch() odpovídá merge($base, $base, $changes). Protože se naše strana od základu nikdy neodchýlila, všechny změny z jejich strany se přijmou bez konfliktu.
Detekce nesouladu schématu
Pokud byly mezi snímky do tabulky přidány nebo z ní odebrány sloupce, sloučení přesto pokračuje a zaznamená nesoulady. Změněné typy sloupců se neporovnávají a přejmenovaný sloupec se projeví jako jeden odebraný a jeden přidaný; viz Ověření schématu.
$result = $merge->merge($base, $ours, $theirs);
if ($result->hasSchemaMismatches()) {
foreach ($result->schemaMismatches() as $mismatch) {
echo $mismatch->getMessage() . "\n";
}
}
merql schémata automaticky nemigruje. Za změny schématu odpovídá volající. Sloučení je ohlásí, abyste se mohli rozhodnout, jak postupovat.
MergeResult
Výsledný objekt obsahuje vše potřebné:
$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
Každý MergeOperation má typ (insert, update nebo delete), název tabulky, klíč řádku, hodnoty sloupců a zdroj (ours, theirs nebo merged). merged označuje řádek, který obě strany aktualizovaly (sloupec po sloupci sloučený), nebo který obě strany odstranily.
K čemu se výsledek sloučení hodí
Výsledek sloučení popisuje, jak převést základní stav na sloučený. Jeho operace zahrnují změny obou stran: vložení provedené naší stranou i vložení provedené jejich stranou se projeví jako vložení.
Merql::apply()(neboApplier) provede každou operaci nad připojenou databází. Lze jej bez problémů použít v databázi, která stále obsahuje základní stav, například v čerstvé kopii tohoto stavu. Pokud databáze už obsahuje změny jedné strany, vložení existujícího řádku selže a transakce se vrátí zpět.- Chcete-li přenést naše změny do databáze, která už obsahuje jejich změny, například při nasazování úprav ze sandboxu do živého prostředí, použijte jen operace, které v databázi chybí: ty se zdrojem
oursa aktualizacemerged. Vyberte je pomocí plánu sloučení a zapište pomocí chráněného použití s podmínkami pro živé řádky.