Fusión de tres vías
Una fusión de tres vías compara dos conjuntos de cambios con un ancestro común y los combina en un único resultado. merql aplica este algoritmo a filas de bases de datos en lugar de a líneas de archivos.
use Merql\Merge\ThreeWayMerge;
$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);
echo $result->operationCount(); // resolved operations
echo $result->conflictCount(); // unresolved conflicts
Los tres estados
Cada fusión requiere exactamente tres instantáneas:
- Base -- el estado de la base de datos en el momento en que se separaron ambas ramas.
- Nuestra -- una rama después de sus cambios, por ejemplo, una copia de pruebas que se editó.
- Ajena -- la otra rama después de sus cambios, por ejemplo, la base de datos activa que siguió ejecutándose.
Nuestra y ajena deben capturarse cada una de la base de datos que contiene esa rama. Las reglas de fusión tratan ambos lados por igual; los nombres determinan el source de cada operación, cómo se nombran los tipos de conflicto (update_delete significa que nuestra rama actualizó y la ajena eliminó) y qué lado favorecen ConflictPolicy::OursWins y TheirsWins.
Base (snapshot at fork time)
/ \
Ours (our changes) Theirs (their changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Sin una base común, no se puede distinguir entre «añadido» y «sin cambios». La comparación de dos vías muestra que el lado A tiene un valor y el lado B no, pero no permite determinar si A lo añadió o B lo eliminó. La base responde a esa pregunta.
Cómo se calculan los conjuntos de cambios
Differ compara cada instantánea con la base para producir dos conjuntos de cambios. Cada conjunto contiene tres tipos de operaciones:
| Base | Actual | Operación |
|---|---|---|
| La fila existe | La fila existe, con la misma huella | Sin cambios |
| La fila existe | La fila existe, con una huella distinta | UPDATE (diferencias por columna) |
| La fila existe | Falta la fila | DELETE |
| Falta la fila | La fila existe | INSERT |
Las huellas (SHA-256 hashes del contenido normalizado de la fila) ofrecen una vía rápida. Si dos huellas coinciden, la fila no ha cambiado y no hace falta comparar las columnas.
use Merql\Diff\Differ;
$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
Reglas de fusión
A continuación, los dos conjuntos de cambios se fusionan operación por operación. Esta tabla define el resultado de cada combinación:
| Nuestra | Ajena | Resultado |
|---|---|---|
| Sin cambios | Sin cambios | Mantener la base |
| Sin cambios | Actualizada | Aceptar la ajena |
| Actualizada | Sin cambios | Aceptar la nuestra |
| Actualizada (mismo valor) | Actualizada (mismo valor) | Aceptar (ambas coinciden) |
| Actualizada (valor distinto) | Actualizada (valor distinto) | Conflicto |
| Sin cambios | Eliminada | Aceptar la eliminación |
| Eliminada | Sin cambios | Aceptar la eliminación |
| Eliminada | Eliminada | Aceptar la eliminación (ambas coinciden) |
| Actualizada | Eliminada | Conflicto |
| Eliminada | Actualizada | Conflicto |
| Insertada (solo nuestra) | -- | Aceptar la inserción |
| -- | Insertada (solo la ajena) | Aceptar la inserción |
| Insertada (misma PK) | Insertada (misma PK) | Conflicto |
Cuando ambos lados actualizan la misma fila, merql no declara inmediatamente un conflicto. Desciende al nivel de columna y compara cada columna por separado. Consulte Fusión a nivel de columna para obtener más detalles.
Uso de la fachada
La fachada Merql opera con instantáneas con nombre que se han guardado en disco. Llame a Merql::init() con la base de datos que contiene cada estado antes de crear su instantánea:
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');
No capture las tres instantáneas de una sola base de datos en secuencia. La instantánea ajena incluiría también los cambios de la nuestra, por lo que cada fila insertada por nuestra rama aparecería como insertada en ambos lados y se notificaría como un conflicto insert_insert.
Merql::merge(), Merql::patch() y Merql::plan() crean un ThreeWayMerge sin un CellMergeConfig, por lo que solo fusionan a nivel de columna. Para fusionar a nivel de celda, cargue las instantáneas y use directamente la clase, como se muestra a continuación.
Uso directo de las clases
Para tener más control, cree las instancias de los componentes usted mismo. Snapshotter::capture() no guarda nada, así que conserve las instantáneas en memoria o guárdelas con 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'),
);
El atajo de patch
Cuando solo un lado tiene cambios (sin modificaciones simultáneas), use patch(). Es una fusión de dos vías en la que la nuestra es igual a la base, por lo que no puede haber conflictos:
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
A través de la fachada:
$result = Merql::patch('base', 'changes');
patch() equivale a merge($base, $base, $changes). Como la nuestra nunca se desvió de la base, todos los cambios de la ajena se aceptan sin conflictos.
Detección de discrepancias de esquema
Si se añadieron o eliminaron columnas de una tabla entre las instantáneas, la fusión continúa, pero registra las discrepancias. No se comparan los tipos de columna modificados, y una columna renombrada aparece como una columna eliminada y otra añadida; consulte Validación del esquema.
$result = $merge->merge($base, $ours, $theirs);
if ($result->hasSchemaMismatches()) {
foreach ($result->schemaMismatches() as $mismatch) {
echo $mismatch->getMessage() . "\n";
}
}
merql no migra esquemas automáticamente. Los cambios de esquema son responsabilidad de quien llama. La fusión los informa para que pueda decidir cómo proceder.
El objeto MergeResult
El objeto del resultado contiene todo lo que necesita:
$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
Cada MergeOperation tiene un tipo (insert, update o delete), un nombre de tabla, una clave de fila, los valores de las columnas y un origen (ours, theirs o merged). merged marca una fila que ambos lados actualizaron (combinada columna por columna) o que ambos lados eliminaron.
Dónde se aplica un resultado de fusión
Un resultado de fusión describe cómo convertir la base en el estado fusionado. Sus operaciones incluyen los cambios de ambos lados: una inserción de nuestra rama y una inserción de la ajena aparecen como inserciones.
Merql::apply()(oApplier) ejecuta todas las operaciones en la base de datos conectada. Se aplica correctamente a una base de datos que aún contiene la base, como una copia recién creada de ese estado. Si se aplica a una base de datos que ya contiene los cambios de una rama, falla al insertar una fila existente y se revierte la transacción.- Para incorporar los cambios de nuestra rama en una base de datos que ya contiene los de la ajena, por ejemplo, para llevar las ediciones de pruebas a producción, aplique solo las operaciones que faltan en esa base de datos: las que tienen el origen
oursy las actualizacionesmerged. Use un plan de fusión para seleccionarlas y Aplicación protegida para escribirlas con condiciones previas sobre las filas activas.