
# 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.

```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()` 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`:

```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);
```

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.

```php
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.

```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
```

## Seleções

Use `ChangeGroupSelection` quando o UI preparar grupos. Converta-o em um `OperationSelection` ao aplicar alterações ou gerar artefatos de reversão.

```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),
);
```

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:

```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),
);
```

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](https://github.com/inline0/merql/issues/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](https://merql.inline0.dev/pt/docs/apply/guarded) 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.

```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),
);
```

## 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.

```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',
]));
```

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.
