Îmbinare în trei direcții
O îmbinare în trei direcții compară două seturi de modificări cu un strămoș comun și le combină într-un singur rezultat. merql aplică acest algoritm rândurilor din baza de date, în locul liniilor din fișiere.
use Merql\Merge\ThreeWayMerge;
$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);
echo $result->operationCount(); // resolved operations
echo $result->conflictCount(); // unresolved conflicts
Cele trei stări
Fiecare îmbinare necesită exact trei instantanee:
- Bază -- starea bazei de date în momentul în care cele două ramuri au divergat.
- A noastră -- o ramură după modificările sale, de exemplu o copie sandbox care a fost editată.
- A lor -- cealaltă ramură după modificările sale, de exemplu baza de date activă care a continuat să funcționeze.
Stările noastră și a lor trebuie capturate fiecare din baza de date care conține ramura respectivă. Regulile de îmbinare tratează ambele părți în mod egal; denumirile stabilesc source fiecărei operații, numele tipurilor de conflict (update_delete înseamnă că noi am actualizat, iar ei au șters) și partea favorizată de ConflictPolicy::OursWins și TheirsWins.
Base (snapshot at fork time)
/ \
Ours (our changes) Theirs (their changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Fără o bază comună, nu puteți distinge „adăugat” de „nemodificat”. Comparația în două direcții arată că partea A are o valoare, iar partea B nu, dar nu poate determina dacă A a adăugat-o sau B a șters-o. Baza răspunde la această întrebare.
Cum se calculează seturile de modificări
Differ compară fiecare instantaneu cu baza pentru a produce două seturi de modificări. Fiecare set conține trei tipuri de operații:
| Bază | Curent | Operație |
|---|---|---|
| Rândul există | Rândul există, amprentă identică | Nicio modificare |
| Rândul există | Rândul există, amprentă diferită | UPDATE (diferențiere pentru fiecare coloană) |
| Rândul există | Rândul lipsește | DELETE |
| Rândul lipsește | Rândul există | INSERT |
Amprentele (SHA-256 hash-uri ale conținutului normalizat al rândului) oferă o cale rapidă. Dacă două amprente coincid, rândul nu s-a schimbat și nu este necesară compararea la nivel de coloană.
use Merql\Diff\Differ;
$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
Reguli de îmbinare
Cele două seturi de modificări sunt apoi îmbinate operație cu operație. Acest tabel definește rezultatul pentru fiecare combinație:
| Ale noastre | Ale lor | Rezultat |
|---|---|---|
| Fără modificări | Fără modificări | Păstrează baza |
| Fără modificări | Actualizat | Acceptă varianta lor |
| Actualizat | Fără modificări | Acceptă varianta noastră |
| Actualizat (aceeași valoare) | Actualizat (aceeași valoare) | Acceptă (ambele sunt de acord) |
| Actualizat (valori diferite) | Actualizat (valori diferite) | Conflict |
| Fără modificări | Șters | Acceptă ștergerea |
| Șters | Fără modificări | Acceptă ștergerea |
| Șters | Șters | Acceptă ștergerea (ambele sunt de acord) |
| Actualizat | Șters | Conflict |
| Șters | Actualizat | Conflict |
| Inserat (doar al nostru) | -- | Acceptă inserarea |
| -- | Inserat (doar al lor) | Acceptă inserarea |
| Inserat (același PK) | Inserat (același PK) | Conflict |
Când ambele părți actualizează același rând, merql nu declară imediat un conflict. Trecerea se face la îmbinarea la nivel de coloană, comparând fiecare coloană separat. Pentru detalii, consultați Îmbinare la nivel de coloană.
Utilizarea facade-ului
Merql facade-ul operează asupra unor instantanee denumite, persistate pe disc. Apelați Merql::init() cu baza de date care conține fiecare stare înainte de a crea instantaneul:
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');
Nu creați toate cele trei instantanee pe rând dintr-o singură bază de date. Varianta lor ar conține atunci și modificările noastre, astfel că fiecare rând inserat de noi ar apărea ca inserat de ambele părți și ar fi raportat drept insert_insert conflict.
Merql::merge(), Merql::patch() și Merql::plan() creează un ThreeWayMerge fără CellMergeConfig, așa că îmbină doar la nivel de coloană. Pentru îmbinare la nivel de celulă, încărcați instantaneele și folosiți direct clasa, ca mai jos.
Utilizarea directă a claselor
Pentru mai mult control, instanțiați singuri componentele. Snapshotter::capture() nu persistă nimic, așa că păstrați instantaneele în memorie sau salvați-le cu 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'),
);
Scurtătura pentru patch-uri
Când doar o parte are modificări (fără modificări concurente), folosiți patch(). Aceasta este o îmbinare bidirecțională în care varianta noastră este identică cu baza, deci conflictele sunt imposibile:
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
Prin facade:
$result = Merql::patch('base', 'changes');
patch() este echivalent cu merge($base, $base, $changes). Deoarece varianta noastră nu s-a abătut niciodată de la bază, toate modificările din varianta lor sunt acceptate fără conflict.
Detectarea nepotrivirilor de schemă
Dacă s-au adăugat sau eliminat coloane dintr-un tabel între instantanee, îmbinarea continuă, dar înregistrează nepotrivirile. Tipurile coloanelor modificate nu sunt comparate, iar o coloană redenumită apare ca una eliminată și una adăugată; consultați Validarea schemei.
$result = $merge->merge($base, $ours, $theirs);
if ($result->hasSchemaMismatches()) {
foreach ($result->schemaMismatches() as $mismatch) {
echo $mismatch->getMessage() . "\n";
}
}
merql nu migrează automat schemele. Modificările schemei țin de responsabilitatea apelantului. Îmbinarea le raportează, astfel încât să puteți decide cum să procedați.
MergeResult
Obiectul rezultat conține tot ce vă trebuie:
$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
Fiecare MergeOperation are un tip (insert, update sau delete), un nume de tabel, o cheie de rând, valorile coloanelor și o sursă (ours, theirs sau merged). merged marchează un rând actualizat de ambele părți (combinat coloană cu coloană) sau șters de ambele părți.
Unde se aplică rezultatul unei îmbinări
Rezultatul unei îmbinări descrie cum se transformă baza în starea îmbinată. Operațiile sale conțin modificările ambelor părți: o inserare făcută de noi și una făcută de ei apar ambele ca inserări.
Merql::apply()(sauApplier) execută fiecare operație asupra bazei de date conectate. Se aplică fără probleme unei baze de date care încă păstrează baza, de exemplu unei copii proaspete a stării de bază. Dacă baza de date conține deja modificările uneia dintre părți, inserarea unui rând existent eșuează, iar tranzacția este anulată.- Pentru a aduce varianta noastră într-o bază de date care conține deja varianta lor, de exemplu pentru a promova modificările din sandbox în mediul live, aplicați doar operațiile care lipsesc din acea bază de date: cele cu sursa
oursși actualizărilemerged. Folosiți un plan de îmbinare pentru a le selecta și Aplicare protejată pentru a le scrie cu precondiții asupra rândurilor live.