Introducere
merql este o bibliotecă PHP pură care efectuează îmbinări în trei direcții asupra stării unei baze de date. Creează instantanee ale tabelelor, calculează seturi de modificări între instantanee, îmbină două seturi de modificări pornind de la o bază comună și detectează conflictele la nivel de coloană. Folosește același algoritm pe care git îl folosește pentru fișierele sursă, aplicat rândurilor și coloanelor în locul liniilor.
Fără instrumente externe. Acceptă MySQL și SQLite prin adaptoare de conexiune și drivere de dialect care pot fi conectate.
Exemplu rapid
O îmbinare necesită trei stări ale acelorași date: baza de la care au pornit ambele copii și cele două copii după ce au fost modificate independent. În acest exemplu, un mediu sandbox a fost copiat din baza de date activă, iar ambele au fost editate de atunci.
use Merql\Connection;
use Merql\Merql;
$live = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');
// Base: live at the moment the sandbox was copied from it.
Merql::init($live);
Merql::snapshot('base');
// ... copy live.sqlite to sandbox.sqlite, then both databases change ...
// Ours: the sandbox with our edits. Theirs: live with its own changes.
Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');
$result = Merql::merge('base', 'ours', 'theirs');
if ($result->isClean()) {
foreach ($result->operations() as $operation) {
echo "{$operation->type} {$operation->table} #{$operation->rowKey} ({$operation->source})\n";
}
} else {
echo "Conflicts: {$result->conflictCount()}\n";
}
Fiecare instantaneu este capturat din baza de date care conține starea respectivă. Capturarea succesivă a tuturor celor trei dintr-o singură bază de date nu descrie două copii divergente: versiunea celorlalți ar conține deja modificările noastre, iar fiecare rând inserat de noi ar fi raportat drept conflict insert_insert.
Rezultatul enumeră operațiile care transformă baza în starea îmbinată, din ambele părți, fiecare marcată cu sursa sa (ours, theirs sau merged). Pentru a transfera modificările din sandbox în baza activă, aplicați operațiile care încă îi lipsesc bazei active folosind Aplicare condiționată.
Sau din CLI, care se conectează la baza de date numită în MERQL_DB_DSN pentru fiecare snapshot:
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
# ... copy live.sqlite to sandbox.sqlite, then both databases change ...
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
De ce merql
Majoritatea instrumentelor pentru baze de date care compară stări lucrează la nivel de rând: dacă vreo coloană dintr-un rând diferă între două versiuni, întregul rând este marcat ca fiind în conflict. Astfel apar conflicte false când două persoane editează coloane diferite din același rând.
merql îmbină la nivel de coloană. Dacă o parte a modificat title, iar cealaltă a modificat status, modificările se îmbină fără conflict. Un conflict apare doar când ambele părți modifică aceeași coloană cu valori diferite.
Pentru coloanele TEXT și JSON, merql poate coborî încă un nivel, efectuând îmbinarea la nivel de celulă. Două editări pe linii diferite ale unei coloane text sau pe chei diferite ale unei coloane JSON se pot îmbina fără conflict folosind același algoritm în trei direcții. Îmbinarea la nivel de celulă este opțională: transmiteți un CellMergeConfig către ThreeWayMerge. Interfața Merql și CLI efectuează îmbinări doar la nivel de coloană. Consultați Îmbinare la nivel de celulă.
Modelul de îmbinare
Base (snapshot at fork time)
/ \
Ours (one copy's changes) Theirs (the other copy's changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Trei stări, la fel ca în git. Baza este strămoșul comun. Versiunile noastră și a celorlalți sunt cele două copii după ce au evoluat separat, fiecare capturată din propria bază de date. Regulile de îmbinare tratează ambele părți la fel. Numele apar în câmpul source al fiecărei operații, în tipurile de conflict (update_delete înseamnă că noi am actualizat, iar ceilalți au șters) și în politicile de rezolvare OursWins și TheirsWins. Fără o bază comună, nu puteți distinge între „adăugat” și „neschimbat”.
O îmbinare nu scrie nimic de la sine. Operațiile sale transformă baza în starea îmbinată, așadar Merql::apply() le execută pe toate fără probleme doar asupra unei baze de date care păstrează încă starea de bază. Pentru a transfera modificările unei părți într-o bază de date care o conține deja pe cealaltă parte, de exemplu pentru a promova modificările din sandbox în baza activă, aplicați doar operațiile care îi lipsesc; consultați Aplicare condiționată.
Cerințe
- PHP 8.2 sau o versiune ulterioară.
ext-mbstringșiext-zlib, necesare pentru dependențapitmaster/pitmastercare furnizează compararea textului.- O extensie de bază de date pentru adaptorul folosit:
ext-pdocuext-pdo_sqlitesauext-pdo_mysql, oriext-mysqli.composer.jsondin merql doar sugereazăext-pdoșiext-mysqli, așadar Composer nu verifică dacă acestea sunt instalate. - O bază de date MySQL sau SQLite. Pentru alte motoare este necesar un driver personalizat.
Ce nu este
merql nu este un instrument de migrare a schemei. Nu replică bazele de date și nu le sincronizează în timp real. Rezolvă o singură problemă specifică: primește trei stări ale unei baze de date și produce o a patra stare îmbinată, identificând conflictele.
Documentație
- Noțiuni introductive -- instalare, primul instantaneu, prima îmbinare.
- CLI Referință -- referința completă pentru comanda
vendor/bin/merql. - PHP API Referință -- documentație completă API pentru toate clasele.
- Planuri de îmbinare -- ID-uri stabile pentru operații, grupuri de modificări și selecții.
- Aplicare condiționată -- scrieri selectate cu precondiții pentru rândurile active.
- Revenire -- artefacte de revenire și verificări ale abaterilor.