Vai al contenuto

Merge a livello di colonna

Il merge a livello di colonna è il principale vantaggio di merql rispetto al confronto ingenuo a livello di riga. Quando entrambi i lati modificano la stessa riga, merql confronta ogni colonna separatamente invece di segnalare un conflitto per l'intera riga.

Il problema del merge a livello di riga

Considerate una tabella posts in cui due persone modificano la stessa riga:

Codice
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" }

Un merge a livello di riga rileva che entrambi i lati hanno modificato la riga e segnala un conflitto. Ma le modifiche non si sovrappongono. Ours ha modificato content, mentre theirs ha modificato title e status.

Come il merge a livello di colonna risolve il problema

merql applica a ogni colonna, separatamente, le stesse regole di merge a tre vie:

Codice
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

Risultato: { title: "New Title", content: "Body v2", status: "publish" }. Merge senza conflitti.

Come funziona

Quando ThreeWayMerge rileva che entrambi i lati hanno aggiornato la stessa riga, delega a 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)

Per ogni colonna si applicano le stesse regole:

Base Ours Theirs Risultato
A A A Mantieni la base (nessuna modifica)
A A B Accetta theirs
A B A Accetta ours
A B B Accetta (entrambi concordano)
A B C Conflitto (valori diversi)

Quando si verificano ancora conflitti

Si verifica un conflitto a livello di colonna quando entrambi i lati modificano la stessa colonna assegnandole valori diversi:

Codice
Base:    post #42  { title: "Hello" }
Ours:    post #42  { title: "Welcome" }
Theirs:  post #42  { title: "Greetings" }

Entrambi hanno modificato title assegnandole valori diversi. È un conflitto effettivo che richiede una risoluzione.

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"

Conflitti parziali

Una singola riga può contenere sia merge senza conflitti sia conflitti. Se una colonna viene unita senza conflitti ma un'altra genera un conflitto, il MergeOperation della riga contiene i valori uniti delle colonne senza conflitti e, per impostazione predefinita, il valore ours delle colonne in conflitto. Il conflitto viene comunque segnalato separatamente.

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

Qui title è in conflitto (entrambi i lati lo hanno modificato diversamente), content viene unito senza conflitti (è cambiato solo ours) e status viene unito senza conflitti (è cambiato solo theirs). L'operazione contiene:

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

Confronto dei valori

I valori delle colonne vengono confrontati come stringhe dopo la conversione. NULL è un valore distinto:

  • Da NULL a NULL = nessuna modifica.
  • Da NULL a "hello" = aggiornamento.
  • Da "hello" a NULL = aggiornamento.
  • Da "1" a 1 = nessuna modifica (entrambi diventano la stringa "1").

Questo corrisponde alla semantica di fetch dell'adapter di connessione MerQL: tutti i valori del database vengono restituiti come stringhe (o NULL).

Merge a livello di cella

Quando l’unione a livello di colonna genera ancora un conflitto (entrambi i lati hanno modificato la stessa colonna con valori diversi), merql può scendere di un livello con unione a livello di cella. Si applica alle colonne TEXT (confronto riga per riga) e alle colonne JSON (confronto chiave per chiave), risolvendo i casi in cui entrambi i lati hanno modificato parti diverse del valore della stessa colonna.