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