Planuri de îmbinare
Un MergeResult este rezultatul executabil al unei îmbinări. Un MergePlan este forma inspectabilă a acelui rezultat. Folosiți-l când o aplicație trebuie să afișeze, să pregătească, să serializeze sau să aplice selectiv modificări ale bazei de date înainte de a le scrie în baza de date țintă.
use Merql\Merql;
$plan = Merql::plan(
id: 'plan-2026-06-06',
base: 'base',
ours: 'ours',
theirs: 'theirs',
metadata: ['sandbox_id' => 'ow_123'],
);
Merql::plan() încarcă cele trei instantanee numite și le îmbină la nivel de coloană, la fel ca Merql::merge(). Pentru a planifica o îmbinare care folosește îmbinarea la nivel de celulă pentru coloanele TEXT și JSON, construiți rezultatul singur și transmiteți-l către MergePlanBuilder:
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);
Planul conține:
- numele instantaneelor folosite pentru a-l construi,
- un rezumat al operațiilor, conflictelor, nepotrivirilor de schemă, tabelelor și grupurilor,
- ID-uri de operații stabile,
- ID-uri stabile ale grupurilor de modificări,
- coloanele de identitate ale rândurilor și valorile de identitate,
- valorile rândurilor de bază, ale noastre, ale lor și rezultate pentru fiecare operație,
- metadate opționale ale aplicației gazdă,
- o amprentă a planului,
- o versiune a schemei.
ID-uri stabile
ID-urile operațiilor sunt deterministe pentru același conținut al operației. Sunt derivate din numele tabelului, cheia rândului, tipul operației, sursa operației, coloanele modificate și valorile rezultate.
ID-urile grupurilor de modificări sunt deterministe pentru același conținut al grupului. Generatorul implicit creează un grup de operații pentru fiecare operație. Aplicațiile gazdă pot crea grupuri mai mari, specifice domeniului, din lista de operații și le pot serializa în continuare cu aceeași structură DTO.
foreach ($plan->changeGroups as $group) {
echo $group->id . ': ' . implode(', ', $group->operationIds);
}
Structura operației
Fiecare MergePlanOperation păstrează suficient context pentru revizuire și revenire.
$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
Selecții
Folosiți ChangeGroupSelection când UI pregătește grupurile. Convertiți-l într-un OperationSelection la aplicare sau la generarea artefactelor de revenire.
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),
);
Rezultatul selectat este un MergeResult curat, care conține doar operațiile selectate. Poate fi transmis către Merql::apply() sau Merql::applyGuarded().
Selectarea elementelor care lipsesc dintr-o țintă
Un plan conține operațiile ambelor părți, deoarece rezultatul îmbinării transformă baza în starea îmbinată. Când ținta conține deja una dintre părți, selectați doar operațiile care îi lipsesc. Pentru o țintă care conține starea lor, cum ar fi o bază de date activă care primește modificările unei copii de tip sandbox, acestea sunt toate operațiile cu sursa ours, plus actualizările merged, care combină modificările de coloană ale ambelor părți:
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),
);
Operațiile cu sursa theirs și ștergerile merged (rânduri șterse de ambele părți) sunt deja prezente în acea țintă.
Un plan construit dintr-o îmbinare cu conflicte listează în continuare câte o operație pentru fiecare rând aflat în conflict pe care ambele părți l-au actualizat. Valorile rezultatului conțin valoarea noastră în coloana aflată în conflict, iar SelectedMergeResultFactory returnează un rezultat curat pentru orice selecție. Și atunci, conflictState operației și al grupului de modificări are în prezent valoarea clean (#9), așa că verificați singur $plan->conflicts ca mai sus sau rezolvați conflictele cu ConflictResolver și construiți planul din rezultatul rezolvat cu MergePlanBuilder. Consultați Aplicare protejată pentru scrierea selecției cu condiții prealabile verificate pentru rândurile active.
Serializare
Planurile de îmbinare și selecțiile au o serializare JSON stabilă. Stocați planul serializat dacă o etapă ulterioară de aplicare trebuie să folosească exact setul de operații revizuit.
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),
);
Instantanee cu aliasuri
Planurile de îmbinare folosesc nume canonice pentru tabele. Dacă numele fizice ale tabelelor diferă între medii, capturați-le sub același nume canonic înainte de planificare.
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',
]));
Schema stocată în instantaneu folosește numele canonic al tabelului, păstrând coloanele, cheile, coloanele de identitate, cheile rândurilor și filtrele tabelului fizic.