Samenvoegplannen
Een MergeResult is de uitvoerbare uitvoer van een samenvoeging. Een MergePlan is de
inspecteerbare vorm van die uitvoer. Gebruik deze wanneer een applicatie databasewijzigingen moet tonen,
voorbereiden, serialiseren of selectief toepassen voordat ze naar de
doeldatabase worden geschreven.
use Merql\Merql;
$plan = Merql::plan(
id: 'plan-2026-06-06',
base: 'base',
ours: 'ours',
theirs: 'theirs',
metadata: ['sandbox_id' => 'ow_123'],
);
Merql::plan() laadt de drie benoemde snapshots en voegt ze samen op kolomniveau,
net als Merql::merge(). Bouw zelf het resultaat en geef het door aan
MergePlanBuilder om samenvoegen op celniveau toe te passen op TEXT- en JSON-kolommen:
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);
Het plan bevat:
- de snapshotnamen die zijn gebruikt om het plan te bouwen,
- een overzicht van bewerkingen, conflicten, schemamismatches, tabellen en groepen,
- stabiele bewerkings-ID's,
- stabiele ID's voor wijzigingsgroepen,
- rij-identiteitskolommen en identiteitswaarden,
- basis-, onze, hun en resultaatrijwaarden voor elke bewerking,
- optionele metadata van de hostapplicatie,
- een planhash,
- een schemaversie.
Stabiele ID's
Bewerkings-ID's zijn deterministisch bij dezelfde bewerkingsinhoud. Ze worden afgeleid van de tabelnaam, rijsleutel, bewerkingstype, bewerkingsbron, gewijzigde kolommen en resultaatwaarden.
ID's van wijzigingsgroepen zijn deterministisch bij dezelfde groepsinhoud. De standaard builder maakt één bewerkingsgroep per bewerking. Hostapplicaties kunnen grotere domeinspecifieke groepen maken op basis van de bewerkingslijst en deze nog steeds serialiseren met dezelfde DTO-vorm.
foreach ($plan->changeGroups as $group) {
echo $group->id . ': ' . implode(', ', $group->operationIds);
}
Bewerkingsvorm
Elke MergePlanOperation bevat voldoende context voor beoordeling en terugdraaien.
$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
Selecties
Gebruik ChangeGroupSelection wanneer de UI groepen klaarzet. Zet deze om naar een
OperationSelection wanneer je wijzigingen toepast of artefacten voor terugdraaien genereert.
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),
);
Het geselecteerde resultaat is een schoon MergeResult dat alleen geselecteerde
bewerkingen bevat. Het kan worden doorgegeven aan Merql::apply() of Merql::applyGuarded().
Selecteren wat een doel mist
Een plan bevat de bewerkingen van beide kanten, omdat het samenvoegresultaat de basis
omzet naar de samengevoegde staat. Wanneer het doel al één kant bevat, selecteer je alleen de
bewerkingen die het nog niet heeft. Voor een doel dat hun kant bevat, zoals een live
database die de wijzigingen van een sandboxkopie ontvangt, geldt dit voor elke bewerking met
bron ours, plus merged-updates, die de kolomwijzigingen van beide kanten combineren:
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),
);
Bewerkingen met bron theirs en merged-verwijderingen (rijen die beide kanten
hebben verwijderd) zijn al aanwezig in dat doel.
Een plan dat is gebouwd op basis van een samenvoeging met conflicten, vermeldt nog steeds een bewerking voor elke
conflicterende rij die beide kanten hebben bijgewerkt. De resultaatwaarden bevatten onze waarde in
de conflicterende kolom, en SelectedMergeResultFactory retourneert een schoon resultaat
voor elke selectie. De conflictState van de bewerking en wijzigingsgroep
blijven ook dan clean lezen (#9), dus
controleer zelf $plan->conflicts zoals hierboven, of los conflicten op met
ConflictResolver en bouw het plan op basis van het opgeloste resultaat met
MergePlanBuilder. Zie
Beveiligd toepassen voor het schrijven van de selectie met voorwaarden voor live rijen.
Serialisatie
Samenvoegplannen en selecties worden stabiel geserialiseerd naar JSON. Sla het geserialiseerde plan op wanneer een latere toepassingsstap exact dezelfde beoordeelde set bewerkingen moet gebruiken.
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),
);
Aliassnapshots
Samenvoegplannen gebruiken canonieke tabelnamen. Als fysieke tabelnamen tussen omgevingen verschillen, leg ze dan vóór het plannen vast onder dezelfde canonieke naam.
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',
]));
Het schema dat in de snapshot is opgeslagen, gebruikt de canonieke tabelnaam en behoudt tegelijkertijd de kolommen, sleutels, identiteitskolommen, rijsleutels en filters van de fysieke tabel.