Plany scalania
MergeResult to wykonywalny wynik scalania. MergePlan to jego forma przeznaczona do inspekcji. Użyj jej, gdy aplikacja musi wyświetlić, przygotować, serializować lub wybiórczo zastosować zmiany w bazie danych przed zapisaniem ich w docelowej bazie.
use Merql\Merql;
$plan = Merql::plan(
id: 'plan-2026-06-06',
base: 'base',
ours: 'ours',
theirs: 'theirs',
metadata: ['sandbox_id' => 'ow_123'],
);
Merql::plan() wczytuje trzy nazwane zrzuty i scala je na poziomie kolumn, tak jak Merql::merge(). Aby zaplanować scalanie wykorzystujące scalanie na poziomie komórek dla kolumn TEXT i JSON, samodzielnie zbuduj wynik i przekaż go do 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);
Plan zawiera:
- nazwy zrzutów użytych do jego utworzenia,
- podsumowanie operacji, konfliktów, niezgodności schematu, tabel i grup,
- stabilne identyfikatory operacji,
- stabilne identyfikatory grup zmian,
- kolumny identyfikujące wiersze i wartości tożsamości,
- wartości wierszy dla bazy, naszej strony, drugiej strony i wyniku dla każdej operacji,
- opcjonalne metadane aplikacji hosta,
- skrót planu,
- wersję schematu.
Stabilne identyfikatory
Identyfikatory operacji są deterministyczne dla tej samej zawartości operacji. Są wyprowadzane z nazwy tabeli, klucza wiersza, typu operacji, źródła operacji, zmienionych kolumn i wartości wynikowych.
Identyfikatory grup zmian są deterministyczne dla tej samej zawartości grupy. Domyślny konstruktor tworzy jedną grupę operacji na każdą operację. Aplikacje hosta mogą tworzyć większe, specyficzne dla domeny grupy z listy operacji i nadal serializować je w tym samym kształcie DTO.
foreach ($plan->changeGroups as $group) {
echo $group->id . ': ' . implode(', ', $group->operationIds);
}
Struktura operacji
Każdy element MergePlanOperation zachowuje wystarczający kontekst na potrzeby przeglądu i wycofania.
$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
Wybory
Użyj ChangeGroupSelection, gdy UI przygotowuje grupy. Zamień go na OperationSelection podczas stosowania zmian lub generowania artefaktów wycofania.
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),
);
Wybrany wynik to czysty obiekt MergeResult zawierający tylko wybrane operacje. Można go przekazać do Merql::apply() lub Merql::applyGuarded().
Wybieranie brakujących zmian w bazie docelowej
Plan zawiera operacje z obu stron, ponieważ wynik scalania przekształca bazę w stan scalony. Jeśli baza docelowa zawiera już jedną ze stron, wybierz tylko operacje, których jeszcze w niej nie ma. W przypadku bazy docelowej zawierającej zmiany drugiej strony, na przykład działającej bazy danych otrzymującej zmiany z kopii piaskownicy, są to wszystkie operacje ze źródłem ours oraz aktualizacje merged, które łączą zmiany kolumn po obu stronach:
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),
);
Operacje ze źródłem theirs oraz usunięcia merged (wiersze usunięte przez obie strony) są już obecne w tej bazie docelowej.
Plan utworzony ze scalania zawierającego konflikty nadal wymienia operację dla każdego konfliktowego wiersza zaktualizowanego przez obie strony. Wartości wynikowe przechowują naszą wartość w konfliktowej kolumnie, a SelectedMergeResultFactory zwraca czysty wynik dla każdego wyboru. Mimo to pole conflictState operacji i grupy zmian ma obecnie wartość clean (#9), więc samodzielnie sprawdź $plan->conflicts, jak opisano wyżej, lub rozwiąż konflikty za pomocą ConflictResolver i utwórz plan z rozwiązanego wyniku przy użyciu MergePlanBuilder. Informacje o zapisywaniu wybranych zmian z warunkami wstępnymi dla bieżących wierszy znajdziesz w sekcji Zabezpieczone zastosowanie.
Serializacja
Plany scalania i wybory są serializowane w stabilnym formacie JSON. Zapisz serializowany plan, jeśli późniejszy krok zastosowania ma użyć dokładnie tego samego, sprawdzonego zestawu operacji.
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),
);
Aliasowane zrzuty
Plany scalania używają kanonicznych nazw tabel. Jeśli fizyczne nazwy tabel różnią się między środowiskami, przed planowaniem zarejestruj je pod tą samą nazwą kanoniczną.
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',
]));
Schemat zapisany w zrzucie używa kanonicznej nazwy tabeli, zachowując kolumny, klucze, kolumny tożsamości, klucze wierszy i filtry tabeli fizycznej.