Planos de mesclagem
Um MergeResult é o resultado executável de uma mesclagem. Um MergePlan é a forma inspecionável desse resultado. Use-o quando um aplicativo precisar exibir, preparar, serializar ou aplicar seletivamente alterações no banco de dados antes de gravá-las no banco de dados de destino.
use Merql\Merql;
$plan = Merql::plan(
id: 'plan-2026-06-06',
base: 'base',
ours: 'ours',
theirs: 'theirs',
metadata: ['sandbox_id' => 'ow_123'],
);
Merql::plan() carrega os três snapshots nomeados e os mescla no nível da coluna, como Merql::merge(). Para planejar uma mesclagem que use a mesclagem no nível das células para colunas TEXT e JSON, crie você mesmo o resultado e passe-o para 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);
O plano contém:
- os nomes dos snapshots usados para criá-lo,
- um resumo das operações, conflitos, incompatibilidades de esquema, tabelas e grupos,
- IDs de operação estáveis,
- IDs de grupo de alterações estáveis,
- colunas de identidade das linhas e valores de identidade,
- valores de linha da base, de ours, de theirs e do resultado para cada operação,
- metadados opcionais do aplicativo host,
- um hash do plano,
- uma versão do esquema.
IDs estáveis
Os IDs de operação são determinísticos para os mesmos conteúdos de operação. Eles derivam do nome da tabela, da chave da linha, do tipo de operação, da origem da operação, das colunas alteradas e dos valores do resultado.
Os IDs de grupo de alterações são determinísticos para os mesmos conteúdos do grupo. O criador padrão gera um grupo de operações por operação. Aplicativos host podem criar grupos maiores e específicos do domínio a partir da lista de operações e ainda serializá-los com a mesma estrutura de DTO.
foreach ($plan->changeGroups as $group) {
echo $group->id . ': ' . implode(', ', $group->operationIds);
}
Estrutura da operação
Cada MergePlanOperation mantém contexto suficiente para revisão e reversão.
$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
Seleções
Use ChangeGroupSelection quando o UI preparar grupos. Converta-o em um OperationSelection ao aplicar alterações ou gerar artefatos de reversão.
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),
);
O resultado selecionado é um MergeResult sem conflitos que contém apenas as operações selecionadas. Ele pode ser passado para Merql::apply() ou Merql::applyGuarded().
Selecionando o que falta no destino
Um plano contém as operações dos dois lados, porque o resultado da mesclagem transforma a base no estado mesclado. Quando o destino já contém um dos lados, selecione apenas as operações que ainda não estão nele. Para um destino que contém theirs, como um banco de dados em produção que recebe as edições de uma cópia de sandbox, isso corresponde a todas as operações com origem ours, mais as atualizações merged, que combinam as alterações de colunas dos dois lados:
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),
);
As operações com origem theirs e as exclusões merged (linhas excluídas pelos dois lados) já estão presentes nesse destino.
Um plano criado a partir de uma mesclagem com conflitos ainda lista uma operação para cada linha em conflito que foi atualizada pelos dois lados. Os valores do resultado contêm o valor de ours na coluna em conflito, e SelectedMergeResultFactory retorna um resultado sem conflitos para qualquer seleção. Mesmo assim, a propriedade conflictState da operação e do grupo de alterações atualmente mostra clean (#9); portanto, verifique você mesmo $plan->conflicts como acima, ou resolva os conflitos com ConflictResolver e crie o plano a partir do resultado resolvido com MergePlanBuilder. Consulte Aplicação protegida para gravar a seleção com pré-condições sobre as linhas ativas.
Serialização
Os planos de mesclagem e as seleções têm serialização estável JSON. Armazene o plano serializado quando uma etapa de aplicação posterior precisar usar exatamente o conjunto de operações revisado.
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),
);
Snapshots com alias
Os planos de mesclagem usam nomes canônicos de tabelas. Se os nomes físicos das tabelas forem diferentes entre ambientes, capture-as com o mesmo nome canônico antes de planejar.
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',
]));
O esquema armazenado no snapshot usa o nome canônico da tabela e preserva as colunas, chaves, colunas de identidade, chaves de linha e filtros da tabela física.