Saltar al contenido

Combinación a nivel de columna

La combinación a nivel de columna es la principal ventaja de merql frente a la comparación ingenua a nivel de fila. Cuando ambas partes modifican la misma fila, merql compara cada columna por separado en vez de marcar toda la fila como conflicto.

El problema de combinar a nivel de fila

Considere una tabla posts en la que dos personas editan la misma fila:

Código
Base:    post #42  { title: "Hello",     content: "Body",    status: "draft"   }
Ours:    post #42  { title: "Hello",     content: "Body v2", status: "draft"   }
Theirs:  post #42  { title: "New Title", content: "Body",    status: "publish" }

Una combinación a nivel de fila detecta que ambas partes modificaron la fila y declara un conflicto. Pero los cambios no se solapan. Nuestro lado editó content; el suyo, title y status.

Cómo lo resuelve la combinación a nivel de columna

merql aplica las mismas reglas de combinación de tres vías a cada columna por separado:

Código
title:   base="Hello", ours="Hello",  theirs="New Title"  -> accept theirs
content: base="Body",  ours="Body v2", theirs="Body"      -> accept ours
status:  base="draft", ours="draft",  theirs="publish"    -> accept theirs

Resultado: { title: "New Title", content: "Body v2", status: "publish" }. Combinación limpia, sin conflictos.

Cómo funciona

Cuando ThreeWayMerge detecta que ambas partes actualizaron la misma fila, delega en ColumnMerge::merge():

PHP
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    table: 'posts',
    rowKey: '42',
    base: ['title' => 'Hello', 'content' => 'Body', 'status' => 'draft'],
    ours: ['title' => 'Hello', 'content' => 'Body v2', 'status' => 'draft'],
    theirs: ['title' => 'New Title', 'content' => 'Body', 'status' => 'publish'],
);

// $result['values']    -> merged column values
// $result['conflicts'] -> list of Conflict objects (empty if clean)

Se aplican las mismas reglas a cada columna:

Base Nuestro lado Su lado Resultado
A A A Conservar la base (sin cambios)
A A B Aceptar el valor de su lado
A B A Aceptar el valor de nuestro lado
A B B Aceptar (ambos coinciden)
A B C Conflicto (valores distintos)

Cuándo siguen produciéndose conflictos

Se produce un conflicto a nivel de columna cuando ambas partes modifican la misma columna con valores distintos:

Código
Base:    post #42  { title: "Hello" }
Ours:    post #42  { title: "Welcome" }
Theirs:  post #42  { title: "Greetings" }

Ambas partes cambiaron title por valores distintos. Es un conflicto real que requiere resolución.

PHP
$result = ColumnMerge::merge(
    'posts', '42',
    ['title' => 'Hello'],
    ['title' => 'Welcome'],
    ['title' => 'Greetings'],
);

// $result['conflicts'] contains one Conflict:
//   table: "posts", column: "title"
//   oursValue: "Welcome", theirsValue: "Greetings", baseValue: "Hello"

Conflictos parciales

Una misma fila puede contener combinaciones sin conflictos y conflictos. Si una columna se combina sin conflictos y otra entra en conflicto, el MergeOperation de la fila contiene los valores combinados de las columnas sin conflictos y, de forma predeterminada, el valor de nuestro lado en las columnas en conflicto. El conflicto se informa por separado.

Código
Base:    { title: "Hello", content: "Body",    status: "draft" }
Ours:    { title: "Welcome", content: "Body v2", status: "draft" }
Theirs:  { title: "Greetings", content: "Body",    status: "publish" }

Aquí title entra en conflicto (ambas partes la cambiaron de forma distinta), content se combina sin conflictos (solo cambió nuestro lado) y status se combina sin conflictos (solo cambió su lado). La operación contiene:

PHP
// values: { title: "Welcome", content: "Body v2", status: "publish" }
// One conflict on the "title" column.

Comparación de valores

Los valores de las columnas se comparan como cadenas después de convertirlos. NULL es un valor distinto:

  • NULL a NULL = sin cambios.
  • NULL a "hello" = actualización.
  • "hello" a NULL = actualización.
  • "1" a 1 = sin cambios (ambos se convierten en la cadena "1").

Esto coincide con la semántica de recuperación del adaptador de conexión MerQL: todos los valores de la base de datos se devuelven como cadenas (o NULL).

Combinación a nivel de celda

Cuando la combinación a nivel de columna sigue produciendo un conflicto (ambas partes cambiaron la misma columna con valores distintos), merql puede profundizar un nivel más con la combinación a nivel de celda. Esto se aplica a columnas TEXT (diferencias línea por línea) y columnas JSON (diferencias clave por clave), y resuelve los casos en los que ambas partes editaron distintas partes del valor de una misma columna.