
# Aplicação protegida

`GuardedApplier` aplica um `MergeResult` sem conflitos somente se o banco de dados de destino ainda
corresponder ao snapshot ativo esperado. Isso é útil quando um plano de mesclagem foi
revisado anteriormente e as linhas ativas podem ter mudado antes da aplicação.

A configuração típica promove as edições de um ambiente de testes para um banco de dados ativo. A base é
 o banco ativo no momento em que o ambiente de testes foi copiado, ours é o ambiente de testes editado
e theirs é o banco ativo no momento. O destino é o banco ativo, portanto o
snapshot ativo esperado é `theirs`, e as operações a gravar são aquelas
que o banco ativo ainda não tem (consulte [Escolha das operações](#choosing-operations)).

```php
use Merql\Connection;
use Merql\Merql;
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

Merql::init(Connection::sqlite('/data/live.sqlite'));

$plan = Merql::plan('promote-1', 'base', 'ours', 'theirs');

// Skip rows with unresolved conflicts.
$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('base'),
);

$applied = Merql::applyGuarded($selected, SnapshotStore::load('theirs'));

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo $error . "\n";
    }
}
```

`Merql::applyGuarded()` grava no banco de dados passado para `Merql::init()`. O
snapshot ativo esperado deve descrever esse mesmo banco de dados: obtenha-o do
destino e capture-o recentemente o bastante para que as pré-condições tenham validade.

## Pré-condições

A SQL protegida adiciona pré-condições de linhas ativas às instruções geradas:

- as atualizações exigem que a identidade da linha de destino exista e que os valores das colunas
  que não são de identidade correspondam à linha ativa esperada,
- as exclusões exigem que a identidade da linha de destino exista e que os valores das colunas
  que não são de identidade correspondam à linha ativa esperada,
- as inserções exigem que a identidade de destino ainda não exista.

Se alguma instrução afetar zero linhas, merql considera o plano desatualizado. A
transação é revertida e o `ApplyResult` retornado contém o erro
`Optimistic precondition failed for guarded merge statement.`

No MySQL, uma atualização cujos valores já são iguais aos da linha ativa também afeta zero
linhas, porque MySQL conta as linhas alteradas, e não as correspondidas. Essa
atualização falha como desatualizada, embora a linha corresponda ([#2](https://github.com/inline0/merql/issues/2)).
SQLite conta as linhas correspondidas e a aceita. Isso pode acontecer com uma atualização `merged`
quando ambos os lados fizeram a mesma alteração em uma linha.

## Escolha das operações

Um resultado de mesclagem contém as operações de ambos os lados, pois transforma a base no
estado mesclado. Aplicadas a um destino que já contém theirs, as operações
que vieram de theirs descrevem alterações que o destino já tem, e suas
pré-condições falham:

| Operação | Estado em um destino que contém theirs | Selecionar |
|---|---|---|
| `ours` inserção, atualização ou exclusão | Ausente | Sim |
| `merged` atualização (ambos os lados alteraram a linha) | Contém as colunas de theirs, faltam as de ours | Sim |
| `theirs` inserção | A linha existe, portanto a inserção protegida afeta zero linhas | Não |
| `merged` exclusão (ambos os lados excluíram a linha) | A linha foi removida, portanto a exclusão protegida afeta zero linhas | Não |
| `theirs` atualização ou exclusão | Já aplicada | Não |

Passar o resultado completo de `Merql::merge()` para `applyGuarded()`, portanto, falha
assim que theirs inseriu uma linha ou ambos os lados excluíram uma. Selecione primeiro as operações,
por origem, como acima, ou com um `ChangeGroupSelection` criado a partir de uma revisão
UI. Exclua as linhas listadas em `$plan->conflicts`: um resultado selecionado está sempre
sem conflitos, e a operação de uma linha em conflito carrega o valor de ours na
coluna conflitante (consulte [Planos de mesclagem](https://merql.inline0.dev/pt/docs/merge/plans#selecting-what-a-target-is-missing)).

## Aplicação selecionada

A aplicação protegida funciona com resultados de mesclagem selecionados produzidos por um plano de mesclagem,
incluindo seleções feitas por grupo de alterações.

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([$plan->changeGroups[0]->id]);
$selected = (new SelectedMergeResultFactory())->fromChangeGroupSelection(
    $plan,
    $selection,
    SnapshotStore::load($plan->baseSnapshot),
);

$applied = Merql::applyGuarded($selected, SnapshotStore::load($plan->theirsSnapshot));
```

Isso permite que um aplicativo host prepare um subconjunto dos grupos revisados e ainda confirme
que as linhas ativas não divergiram desde a criação do plano.

## Pré-visualização de SQL

Use `SqlGenerator::generateGuarded()` para inspecionar as instruções protegidas sem
executá-las.

```php
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;

$statements = SqlGenerator::generateGuarded(
    $selected,
    SnapshotStore::load($plan->theirsSnapshot),
);

foreach ($statements as $statement) {
    echo $statement['sql'] . "\n";
}
```

As instruções geradas continuam parametrizadas. As pré-condições são expressas
com cláusulas `WHERE` adicionais para atualizações/exclusões e verificações `NOT EXISTS` para
inserções.

## Aplicação protegida ou simples

`Merql::apply()` executa as mesmas operações sem pré-condições. Assim, atualizações e
exclusões são concluídas independentemente de a linha ativa ter mudado desde a criação do plano, e uma inserção de uma linha existente falha com o erro de chave duplicada do banco de dados. Use a aplicação simples para um destino que você sabe que ainda contém a base, e a aplicação protegida
quando outros processos possam ter alterado o destino desde que ele foi capturado.
