Pular para o conteúdo

Mesclagem de três vias

Uma mesclagem de três vias compara dois conjuntos de alterações com um ancestral comum e os combina em um único resultado. merql aplica esse algoritmo a linhas de banco de dados em vez de linhas de arquivos.

PHP
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts

Os três estados

Toda mesclagem exige exatamente três snapshots:

  • Base -- o estado do banco de dados no momento em que os dois branches divergiram.
  • Ours -- um branch após suas alterações, por exemplo, uma cópia de sandbox que foi editada.
  • Theirs -- o outro branch após suas alterações, por exemplo, o banco de dados em produção que continuou em funcionamento.

Ours e theirs devem ser capturados cada um do banco de dados que contém o respectivo branch. As regras de mesclagem tratam os dois lados da mesma forma; os nomes determinam a source de cada operação, como os tipos de conflito são nomeados (update_delete significa que ours atualizou e theirs excluiu) e qual lado ConflictPolicy::OursWins e TheirsWins favorecem.

Código
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

Sem uma base comum, não é possível distinguir "adicionado" de "inalterado". A comparação de dois lados mostra que o lado A tem um valor e o lado B não, mas não consegue determinar se A o adicionou ou se B o excluiu. A base responde a essa pergunta.

Como os conjuntos de alterações são calculados

O Differ compara cada snapshot com a base para gerar dois conjuntos de alterações. Cada conjunto contém três tipos de operação:

Base Atual Operação
A linha existe A linha existe, mesma impressão digital Nenhuma alteração
A linha existe A linha existe, impressão digital diferente UPDATE (diferenças por coluna)
A linha existe Linha ausente DELETE
Linha ausente A linha existe INSERT

As impressões digitais (hashes SHA-256 do conteúdo normalizado da linha) oferecem um caminho rápido. Se duas impressões digitais forem iguais, a linha não foi alterada e não é necessário comparar as colunas.

PHP
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);

Regras de mesclagem

Em seguida, os dois conjuntos de alterações são mesclados operação por operação. Esta tabela define o resultado para cada combinação:

Ours Theirs Resultado
Nenhuma alteração Nenhuma alteração Manter a base
Nenhuma alteração Atualizada Aceitar theirs
Atualizada Nenhuma alteração Aceitar ours
Atualizada (mesmo valor) Atualizada (mesmo valor) Aceitar (ambos concordam)
Atualizada (valor diferente) Atualizada (valor diferente) Conflito
Nenhuma alteração Excluída Aceitar exclusão
Excluída Nenhuma alteração Aceitar exclusão
Excluída Excluída Aceitar exclusão (ambos concordam)
Atualizada Excluída Conflito
Excluída Atualizada Conflito
Inserida (somente ours) -- Aceitar inserção
-- Inserida (somente theirs) Aceitar inserção
Inserida (mesma PK) Inserida (mesma PK) Conflito

Quando os dois lados atualizam a mesma linha, merql não declara imediatamente um conflito. Ele passa à mesclagem no nível da coluna, comparando cada coluna de forma independente. Consulte Mesclagem no nível da coluna para obter detalhes.

Usando a facade

A facade Merql opera em snapshots nomeados que foram persistidos em disco. Chame Merql::init() com o banco de dados que contém cada estado antes de criar o snapshot:

PHP
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

$result = Merql::merge('base', 'ours', 'theirs');

Não crie os três snapshots em sequência a partir de um único banco de dados. Nesse caso, theirs também conteria as alterações de ours, e toda linha inserida por ours apareceria como inserida nos dois lados e seria relatada como um conflito insert_insert.

Merql::merge(), Merql::patch() e Merql::plan() criam um ThreeWayMerge sem um CellMergeConfig; portanto, mesclam apenas no nível da coluna. Para mesclar no nível das células, carregue os snapshots e use diretamente a classe, como abaixo.

Usando as classes diretamente

Para ter mais controle, instancie os componentes por conta própria. Snapshotter::capture() não persiste nada; portanto, mantenha os snapshots na memória ou salve-os com SnapshotStore::save():

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);

O atalho de patch

Quando apenas um lado tem alterações (sem modificações simultâneas), use patch(). Esta é uma mesclagem de dois lados em que ours é igual à base, portanto, não há possibilidade de conflitos:

PHP
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.

Pela facade:

PHP
$result = Merql::patch('base', 'changes');

patch() é equivalente a merge($base, $base, $changes). Como ours nunca divergiu da base, todas as alterações de theirs são aceitas sem conflito.

Detecção de incompatibilidades de esquema

Se colunas forem adicionadas ou removidas de uma tabela entre snapshots, a mesclagem ainda será executada, mas registrará as incompatibilidades. Tipos de coluna alterados não são comparados, e uma coluna renomeada aparece como uma removida e outra adicionada; consulte Validação de esquema.

PHP
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}

merql não migra esquemas automaticamente. As alterações de esquema são responsabilidade de quem faz a chamada. A mesclagem as informa para que você decida como prosseguir.

O MergeResult

O objeto de resultado contém tudo de que você precisa:

PHP
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge

Cada MergeOperation tem um tipo (insert, update ou delete), um nome de tabela, uma chave de linha, os valores das colunas e uma origem (ours, theirs ou merged). merged indica uma linha atualizada pelos dois lados (com as colunas combinadas individualmente) ou excluída pelos dois lados.

Onde aplicar um resultado de mesclagem

Um resultado de mesclagem descreve como transformar a base no estado mesclado. Suas operações contêm as alterações dos dois lados: tanto uma inserção de ours quanto uma inserção de theirs aparecem como inserções.

  • Merql::apply() (ou Applier) executa cada operação no banco de dados conectado. É aplicado sem problemas a um banco de dados que ainda contém a base, como uma cópia nova do estado da base. Se for aplicado a um banco de dados que já contém as alterações de um dos lados, uma inserção de uma linha que já existe falha e a transação é revertida.
  • Para levar as alterações de ours a um banco de dados que já contém theirs, como ao promover edições da sandbox para produção, aplique apenas as operações que faltam nesse banco: as de origem ours e as atualizações merged. Use um plano de mesclagem para selecioná-las e Aplicação protegida para gravá-las com pré-condições sobre as linhas ativas.