Aller au contenu

Plans de fusion

Un MergeResult est le résultat exécutable d'une fusion. Un MergePlan en est la forme consultable. Utilisez-le lorsqu'une application doit afficher, préparer, sérialiser ou appliquer de manière sélective des modifications de base de données avant de les écrire dans la base de données cible.

PHP
use Merql\Merql;

$plan = Merql::plan(
    id: 'plan-2026-06-06',
    base: 'base',
    ours: 'ours',
    theirs: 'theirs',
    metadata: ['sandbox_id' => 'ow_123'],
);

Merql::plan() charge les trois instantanés nommés et les fusionne au niveau des colonnes, comme Merql::merge(). Pour planifier une fusion qui utilise la fusion au niveau des cellules pour les colonnes TEXT et JSON, construisez vous-même le résultat et transmettez-le à MergePlanBuilder :

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);

Le plan contient :

  • les noms des instantanés utilisés pour le construire,
  • un résumé des opérations, conflits, incompatibilités de schéma, tables et groupes,
  • des identifiants d'opération stables,
  • des identifiants de groupe de modifications stables,
  • les colonnes d'identité des lignes et leurs valeurs,
  • les valeurs des lignes dans la base, de notre côté, de leur côté et dans le résultat pour chaque opération,
  • des métadonnées facultatives de l'application hôte,
  • un hachage du plan,
  • une version du schéma.

Identifiants stables

Les identifiants d'opération sont déterministes pour un même contenu d'opération. Ils sont dérivés du nom de la table, de la clé de ligne, du type d'opération, de sa source, des colonnes modifiées et des valeurs du résultat.

Les identifiants de groupe de modifications sont déterministes pour un même contenu de groupe. Le générateur par défaut crée un groupe d'opérations par opération. Les applications hôtes peuvent créer des groupes métier plus vastes à partir de la liste d'opérations et les sérialiser avec la même structure DTO.

PHP
foreach ($plan->changeGroups as $group) {
    echo $group->id . ': ' . implode(', ', $group->operationIds);
}

Structure d'une opération

Chaque MergePlanOperation conserve suffisamment de contexte pour la révision et la restauration.

PHP
$operation = $plan->operations[0];

$operation->id;              // stable operation ID
$operation->type;            // insert, update, delete
$operation->table;           // canonical table name
$operation->rowKey;          // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues;  // decoded identity values
$operation->source;          // ours, theirs, or merged
$operation->baseRow;         // row in base snapshot, or null
$operation->oursRow;         // row in ours snapshot, or null
$operation->theirsRow;       // row in theirs snapshot, or null
$operation->resultValues;    // values the merge would write
$operation->changedColumns;  // changed result columns

Sélections

Utilisez ChangeGroupSelection lorsque le UI prépare les groupes. Convertissez-le en OperationSelection lors de l'application ou de la génération d'artefacts de restauration.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$operationSelection = $selection->toOperationSelection($plan);

$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    $operationSelection,
    SnapshotStore::load($plan->baseSnapshot),
);

Le résultat sélectionné est un MergeResult sans conflit qui ne contient que les opérations sélectionnées. Il peut être transmis à Merql::apply() ou à Merql::applyGuarded().

Sélection des éléments manquants dans la cible

Un plan contient les opérations des deux côtés, car le résultat de la fusion transforme la base en état fusionné. Lorsque la cible contient déjà un côté, sélectionnez uniquement les opérations qui lui manquent. Pour une cible qui contient leur côté, comme une base de données live recevant les modifications d'une copie bac à sable, il s'agit de toutes les opérations dont la source est ours, ainsi que des mises à jour merged, qui combinent les modifications de colonnes des deux côtés :

PHP
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

// Rows with an unresolved conflict stay out of the selection.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load($plan->baseSnapshot),
);

Les opérations dont la source est theirs, ainsi que les suppressions merged (lignes supprimées des deux côtés), sont déjà présentes dans cette cible.

Un plan créé à partir d’une fusion avec des conflits contient toujours une opération pour chaque ligne en conflit modifiée par les deux côtés. Les valeurs du résultat correspondent à celles de notre côté dans la colonne en conflit, et SelectedMergeResultFactory renvoie un résultat sans conflit pour toute sélection. Les conflictState de l’opération et du groupe de modifications indiquent actuellement clean même dans ce cas (#9). Vérifiez donc vous-même $plan->conflicts comme indiqué ci-dessus, ou résolvez les conflits avec ConflictResolver et créez le plan à partir du résultat résolu avec MergePlanBuilder. Consultez Application protégée pour écrire la sélection avec des préconditions sur les lignes en temps réel.

Sérialisation

Les plans de fusion et les sélections ont une sérialisation JSON stable. Enregistrez le plan sérialisé si une étape d’application ultérieure doit utiliser exactement l’ensemble d’opérations examiné.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\MergePlanSerializer;

$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);

$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
    json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);

Instantanés avec alias

Les plans de fusion utilisent des noms de table canoniques. Si les noms physiques des tables diffèrent selon les environnements, capturez-les sous le même nom canonique avant de planifier.

PHP
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

SnapshotStore::save($snapshotter->captureAliased('live', [
    'wp_posts' => 'wp_posts',
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abc_posts' => 'wp_posts',
]));

Le schéma stocké dans l’instantané utilise le nom canonique de la table tout en préservant les colonnes, clés, colonnes d’identité, clés de ligne et filtres de la table physique.