Aller au contenu

Fusion au niveau des colonnes

La fusion au niveau des colonnes est le principal avantage de merql par rapport à la comparaison naïve au niveau des lignes. Lorsque les deux côtés modifient la même ligne, merql compare chaque colonne séparément au lieu de signaler la ligne entière comme conflit.

Le problème de la fusion au niveau des lignes

Considérez une table posts dans laquelle deux personnes modifient la même ligne :

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

Une fusion au niveau des lignes constate que les deux côtés ont modifié la ligne et déclare un conflit. Pourtant, leurs modifications ne se chevauchent pas. Nous avons modifié content ; eux ont modifié title et status.

Résolution par la fusion au niveau des colonnes

merql applique les mêmes règles de fusion à trois voies à chaque colonne séparément :

Code
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

Résultat : { title: "New Title", content: "Body v2", status: "publish" }. Fusion sans conflit.

Fonctionnement

Lorsque ThreeWayMerge détecte que les deux côtés ont mis à jour la même ligne, il délègue à 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)

Les mêmes règles s'appliquent à chaque colonne :

Base Nous Eux Résultat
A A A Conserver la base (aucune modification)
A A B Accepter leur valeur
A B A Accepter notre valeur
A B B Accepter (accord)
A B C Conflit (valeurs différentes)

Quand des conflits surviennent malgré tout

Un conflit au niveau des colonnes survient lorsque les deux côtés modifient la même colonne avec des valeurs différentes :

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

Les deux côtés ont modifié title avec des valeurs différentes. C'est un conflit réel qui nécessite une résolution.

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"

Conflits partiels

Une même ligne peut contenir des fusions sans conflit et des conflits. Si une colonne fusionne sans conflit et une autre est en conflit, le MergeOperation de la ligne contient les valeurs fusionnées pour les colonnes sans conflit et, par défaut, notre valeur pour les colonnes en conflit. Le conflit est tout de même signalé séparément.

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

Ici, title est en conflit (les deux côtés l'ont modifié différemment), content fusionne sans conflit (seul notre côté l'a modifié) et status fusionne sans conflit (seul leur côté l'a modifié). L'opération contient :

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

Comparaison des valeurs

Les valeurs des colonnes sont converties en chaînes avant d'être comparées. NULL est une valeur distincte :

  • NULL vers NULL = aucun changement.
  • NULL vers "hello" = mise à jour.
  • "hello" vers NULL = mise à jour.
  • "1" vers 1 = aucun changement (les deux sont convertis en chaîne "1").

Cela correspond à la sémantique de récupération des valeurs de l'adaptateur de connexion MerQL : toutes les valeurs de la base de données sont renvoyées sous forme de chaînes (ou de NULL).

Fusion au niveau des cellules

Lorsque la fusion au niveau des colonnes produit malgré tout un conflit (les deux côtés ont modifié la même colonne avec des valeurs différentes), merql peut aller plus loin avec la fusion au niveau des cellules. Cette fonction s'applique aux colonnes TEXT (diff ligne par ligne) et aux colonnes JSON (diff clé par clé), pour résoudre les cas où les deux côtés ont modifié différentes parties de la même valeur de colonne.