Introduzione
merql è una libreria PHP pura che esegue merge a tre vie sullo stato del database. Acquisisce snapshot delle tabelle, calcola gli insiemi di modifiche tra snapshot, unisce due insiemi di modifiche rispetto a una base comune e rileva i conflitti a livello di colonna. Applica alle righe e alle colonne, anziché alle righe di testo, lo stesso algoritmo usato da git per i file sorgente.
Nessuno strumento esterno. Supporta MySQL e SQLite tramite adapter di connessione e driver di dialetto collegabili.
Esempio rapido
Per un merge servono tre stati degli stessi dati: la base da cui sono partite entrambe le copie e le due copie dopo le modifiche indipendenti. In questo caso, una sandbox è stata copiata dal database live ed entrambi sono stati modificati da allora.
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";
}
Ogni snapshot viene acquisito dal database che contiene lo stato corrispondente. Acquisirli tutti e tre dallo stesso database, uno dopo l'altro, non descrive due copie divergenti: theirs conterrebbe già le modifiche di ours e ogni riga inserita da ours verrebbe segnalata come conflitto insert_insert.
Il risultato elenca le operazioni che trasformano la base nello stato unito, provenienti da entrambi i lati, ciascuna contrassegnata con la propria origine (ours, theirs o merged). Per trasferire le modifiche della sandbox nel database live, applicate le operazioni che mancano in live con Applicazione condizionale.
Oppure dalla CLI, che si connette al database indicato in MERQL_DB_DSN per ogni 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
Perché merql
La maggior parte degli strumenti di database che confrontano gli stati lavora a livello di riga: se una colonna qualsiasi di una riga differisce tra due versioni, l'intera riga viene segnalata come conflitto. Questo genera falsi conflitti quando due persone modificano colonne diverse della stessa riga.
merql esegue il merge a livello di colonna. Se un lato modifica title e l'altro modifica status, le modifiche vengono unite senza conflitti. Un conflitto viene generato solo quando entrambi i lati modificano la stessa colonna assegnandole valori diversi.
Per le colonne TEXT e JSON, merql può scendere di un livello con il merge a livello di cella. Due modifiche a righe diverse di una colonna di testo, o a chiavi diverse di una colonna JSON, possono essere unite senza conflitti con lo stesso algoritmo a tre vie. Il merge a livello di cella è facoltativo: passate un CellMergeConfig a ThreeWayMerge. La facciata Merql e CLI eseguono il merge solo a livello di colonna. Consultate Merge a livello di cella.
Il modello di merge
Base (snapshot at fork time)
/ \
Ours (one copy's changes) Theirs (the other copy's changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Tre stati, come in git. Base è l'antenato comune. Ours e theirs sono le due copie dopo la divergenza, ciascuna acquisita dal proprio database. Le regole di merge trattano entrambi i lati allo stesso modo. I nomi compaiono in source di ogni operazione, nei tipi di conflitto (update_delete significa che ours è stato aggiornato e theirs eliminato) e nelle politiche di risoluzione OursWins e TheirsWins. Senza una base comune, non è possibile distinguere tra «aggiunto» e «invariato».
Un merge non scrive nulla da solo. Le sue operazioni trasformano la base nello stato unito, quindi Merql::apply() le esegue tutte correttamente solo su un database che contiene ancora la base. Per trasferire le modifiche di un lato in un database che contiene già quelle dell'altro, ad esempio per portare le modifiche della sandbox nel database live, applicate solo le operazioni mancanti; consultate Applicazione condizionale.
Requisiti
- PHP 8.2 o versioni successive.
ext-mbstringeext-zlib, richiesti dalla dipendenzapitmaster/pitmasterche fornisce il diff del testo.- Un'estensione per il database usato dall'adapter:
ext-pdoconext-pdo_sqliteoext-pdo_mysql, oppureext-mysqli.composer.jsondi merql si limita a suggerireext-pdoeext-mysqli, quindi Composer non verifica che siano installati. - Un database MySQL o SQLite. Per altri motori serve un driver personalizzato.
Cosa non è
merql non è uno strumento per la migrazione degli schemi. Non replica i database né li sincronizza in tempo reale. Risolve un problema specifico: dati tre stati di un database, produce un quarto stato risultante dal merge e identifica i conflitti.
Documentazione
- Guida introduttiva -- installazione, primo snapshot, primo merge.
- CLI di riferimento -- riferimento completo al comando
vendor/bin/merql. - PHP API di riferimento -- documentazione completa API per tutte le classi.
- Piani di merge -- ID operazione stabili, gruppi di modifiche e selezioni.
- Applicazione condizionale -- scritture selezionate con precondizioni sulle righe live.
- Ripristino -- artefatti di rollback e controlli delle divergenze.