
# Třícestné sloučení

Třícestné sloučení porovnává dvě sady změn vůči společnému předkovi a kombinuje je do jednoho výsledku. merql používá tento algoritmus na databázové řádky místo řádků souborů.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Tři stavy

Každé sloučení vyžaduje přesně tři snímky:

- **Základ** -- stav databáze v okamžiku, kdy se obě větve rozdělily.
- **Naše** -- jedna větev po provedení změn, například upravená sandboxová kopie.
- **Jejich** -- druhá větev po provedení změn, například živá databáze, která dál fungovala.

Stavy naší a jejich strany musí být zaznamenány z databáze, která danou větev obsahuje. Pravidla sloučení zacházejí s oběma stranami stejně; názvy určují `source` každé operace, názvy typů konfliktů (`update_delete` znamená, že naše strana řádek aktualizovala a jejich jej odstranila) a to, kterou stranu upřednostňují `ConflictPolicy::OursWins` a `TheirsWins`.

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

Bez společného základu nelze rozlišit „přidáno“ od „beze změny“. Dvoucestné porovnání ukáže, že strana A má hodnotu a strana B ji nemá, ale nedokáže určit, zda ji A přidala, nebo ji B odstranila. Odpověď poskytne základ.

## Jak se počítají sady změn

`Differ` porovná každý snímek se základem a vytvoří dvě sady změn. Každá sada obsahuje tři druhy operací:

| Základ | Aktuální stav | Operace |
|---|---|---|
| Řádek existuje | Řádek existuje, stejný otisk | Beze změny |
| Řádek existuje | Řádek existuje, jiný otisk | **UPDATE** (rozdíl po sloupcích) |
| Řádek existuje | Řádek chybí | **DELETE** |
| Řádek chybí | Řádek existuje | **INSERT** |

Otisky (hash hodnot SHA-256 normalizovaného obsahu řádku) představují rychlou cestu. Pokud se dva otisky shodují, řádek se nezměnil a není třeba porovnávat jednotlivé sloupce.

```php
use Merql\Diff\Differ;

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

## Pravidla sloučení

Obě sady změn se poté sloučí operaci po operaci. Tato tabulka uvádí výsledek každé kombinace:

| Naše | Jejich | Výsledek |
|---|---|---|
| Beze změny | Beze změny | Ponechat základ |
| Beze změny | Aktualizováno | Přijmout jejich |
| Aktualizováno | Beze změny | Přijmout naši verzi |
| Aktualizováno (stejná hodnota) | Aktualizováno (stejná hodnota) | Přijmout (obě strany souhlasí) |
| Aktualizováno (jiná hodnota) | Aktualizováno (jiná hodnota) | **Konflikt** |
| Beze změny | Odstraněno | Přijmout odstranění |
| Odstraněno | Beze změny | Přijmout odstranění |
| Odstraněno | Odstraněno | Přijmout odstranění (obě strany souhlasí) |
| Aktualizováno | Odstraněno | **Konflikt** |
| Odstraněno | Aktualizováno | **Konflikt** |
| Vloženo (pouze naše strana) | -- | Přijmout vložení |
| -- | Vloženo (pouze jejich strana) | Přijmout vložení |
| Vloženo (stejný PK) | Vloženo (stejný PK) | **Konflikt** |

Když obě strany aktualizují stejný řádek, merql ihned nevyhlásí konflikt. Přejde ke sloučení na úrovni sloupců a porovná každý sloupec samostatně. Podrobnosti najdete v části [Sloučení na úrovni sloupců](https://merql.inline0.dev/cs/docs/merge/column-level).

## Použití fasády

Fasáda `Merql` pracuje s pojmenovanými snímky uloženými na disk. Před pořízením snímku zavolejte `Merql::init()` nad databází, která obsahuje příslušný stav:

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

Nezaznamenávejte všechny tři snímky postupně z jedné databáze. Jejich snímek by pak obsahoval také změny naší strany, takže každý námi vložený řádek by vypadal jako vložený na obou stranách a byl by nahlášen jako konflikt `insert_insert`.

`Merql::merge()`, `Merql::patch()` a `Merql::plan()` vytvoří `ThreeWayMerge` bez `CellMergeConfig`, takže sloučí pouze na úrovni sloupců. Chcete-li sloučit na úrovni buněk, načtěte snímky a použijte přímo třídu, jak je uvedeno níže.

## Přímé použití tříd

Pro větší kontrolu vytvořte instance komponent sami. `Snapshotter::capture()` nic neukládá, proto uchovávejte snímky v paměti nebo je ukládejte pomocí `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'),
);
```

## Zkratka pro záplatu

Pokud změny provedla jen jedna strana (nedošlo k souběžným úpravám), použijte `patch()`. Jde o dvoucestné sloučení, při kterém se naše strana rovná základu, takže ke konfliktům nemůže dojít:

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

Přes fasádu:

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

`patch()` odpovídá `merge($base, $base, $changes)`. Protože se naše strana od základu nikdy neodchýlila, všechny změny z jejich strany se přijmou bez konfliktu.

## Detekce nesouladu schématu

Pokud byly mezi snímky do tabulky přidány nebo z ní odebrány sloupce, sloučení přesto pokračuje a zaznamená nesoulady. Změněné typy sloupců se neporovnávají a přejmenovaný sloupec se projeví jako jeden odebraný a jeden přidaný; viz [Ověření schématu](https://merql.inline0.dev/cs/docs/advanced/schema).

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

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

merql schémata automaticky nemigruje. Za změny schématu odpovídá volající. Sloučení je ohlásí, abyste se mohli rozhodnout, jak postupovat.

## MergeResult

Výsledný objekt obsahuje vše potřebné:

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

Každý `MergeOperation` má typ (`insert`, `update` nebo `delete`), název tabulky, klíč řádku, hodnoty sloupců a zdroj (`ours`, `theirs` nebo `merged`). `merged` označuje řádek, který obě strany aktualizovaly (sloupec po sloupci sloučený), nebo který obě strany odstranily.

## K čemu se výsledek sloučení hodí

Výsledek sloučení popisuje, jak převést základní stav na sloučený. Jeho operace zahrnují změny obou stran: vložení provedené naší stranou i vložení provedené jejich stranou se projeví jako vložení.

- `Merql::apply()` (nebo `Applier`) provede každou operaci nad připojenou databází. Lze jej bez problémů použít v databázi, která stále obsahuje základní stav, například v čerstvé kopii tohoto stavu. Pokud databáze už obsahuje změny jedné strany, vložení existujícího řádku selže a transakce se vrátí zpět.
- Chcete-li přenést naše změny do databáze, která už obsahuje jejich změny, například při nasazování úprav ze sandboxu do živého prostředí, použijte jen operace, které v databázi chybí: ty se zdrojem `ours` a aktualizace `merged`. Vyberte je pomocí [plánu sloučení](https://merql.inline0.dev/cs/docs/merge/plans) a zapište pomocí [chráněného použití](https://merql.inline0.dev/cs/docs/apply/guarded) s podmínkami pro živé řádky.
