
# Trojstranné zlúčenie

Trojstranné zlúčenie porovnáva dve množiny zmien voči spoločnému predkovi a spája ich do jedného výsledku. merql používa tento algoritmus na riadky databázy namiesto riadkov súborov.

```php
use Merql\Merge\ThreeWayMerge;

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

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

## Tri stavy

Každé zlúčenie vyžaduje presne tri snímky:

- **Základná verzia** -- stav databázy v okamihu, keď sa obe vetvy oddelili.
- **Naša strana** -- jedna vetva po vykonaní zmien, napríklad upravená sandboxová kópia.
- **Ich strana** -- druhá vetva po vykonaní zmien, napríklad živá databáza, ktorá ďalej fungovala.

Našu aj ich stranu treba zaznamenať z databázy, v ktorej sa daná vetva nachádza. Pravidlá zlúčenia zaobchádzajú s oboma stranami rovnako; názvy určujú `source` každej operácie, názvy typov konfliktov (`update_delete` znamená, že naša strana aktualizovala a ich strana odstránila riadok) a to, ktorej strane dávajú prednosť `ConflictPolicy::OursWins` a `TheirsWins`.

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

Bez spoločnej základnej verzie nemožno rozlíšiť „pridané“ od „nezmenené“. Dvojstranné porovnanie zistí, že strana A má hodnotu a strana B ju nemá, ale nedokáže určiť, či ju strana A pridala, alebo ju strana B odstránila. Na túto otázku odpovedá základná verzia.

## Ako sa počítajú sady zmien

`Differ` porovná každú snímku so základnou verziou a vytvorí dve sady zmien. Každá sada obsahuje tri druhy operácií:

| Základná verzia | Aktuálny stav | Operácia |
|---|---|---|
| Riadok existuje | Riadok existuje, rovnaký odtlačok | Žiadna zmena |
| Riadok existuje | Riadok existuje, odlišný odtlačok | **UPDATE** (rozdiely po jednotlivých stĺpcoch) |
| Riadok existuje | Riadok chýba | **DELETE** |
| Riadok chýba | Riadok existuje | **INSERT** |

Odtlačky (SHA-256 hashy normalizovaného obsahu riadka) umožňujú rýchlu kontrolu. Ak sa dva odtlačky zhodujú, riadok sa nezmenil a porovnávanie na úrovni stĺpcov nie je potrebné.

```php
use Merql\Diff\Differ;

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

## Pravidlá zlúčenia

Dve sady zmien sa potom zlúčia operáciu po operácii. Táto tabuľka uvádza výsledok pre každú kombináciu:

| Naša strana | Ich strana | Výsledok |
|---|---|---|
| Bez zmeny | Bez zmeny | Ponechať základnú verziu |
| Bez zmeny | Aktualizované | Prijať zmeny ich strany |
| Aktualizované | Bez zmeny | Prijať naše zmeny |
| Aktualizované (rovnaká hodnota) | Aktualizované (rovnaká hodnota) | Prijať (zhoda oboch strán) |
| Aktualizované (odlišná hodnota) | Aktualizované (odlišná hodnota) | **Konflikt** |
| Bez zmeny | Odstránené | Prijať odstránenie |
| Odstránené | Bez zmeny | Prijať odstránenie |
| Odstránené | Odstránené | Prijať odstránenie (zhoda oboch strán) |
| Aktualizované | Odstránené | **Konflikt** |
| Odstránené | Aktualizované | **Konflikt** |
| Pridané (iba na našej strane) | -- | Prijať pridanie |
| -- | Pridané (iba na ich strane) | Prijať pridanie |
| Pridané (rovnaký PK) | Pridané (rovnaký PK) | **Konflikt** |

Keď obe strany aktualizujú ten istý riadok, merql okamžite neoznačí konflikt. Prejde na úroveň stĺpcov a porovná každý stĺpec samostatne. Podrobnosti nájdete v príručke [Zlúčenie na úrovni stĺpcov](https://merql.inline0.dev/sk/docs/merge/column-level).

## Použitie fasády

Fasáda `Merql` pracuje s pomenovanými snímkami uloženými na disku. Pred vytvorením snímky zavolajte `Merql::init()` s databázou, v ktorej sa nachádza každý 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');
```

Nevytvárajte všetky tri snímky postupne z jednej databázy. Ich strana by potom obsahovala aj zmeny našej strany, takže každý riadok pridaný na našej strane by vyzeral ako pridaný na oboch stranách a bol by označený ako konflikt `insert_insert`.

`Merql::merge()`, `Merql::patch()` a `Merql::plan()` vytvoria `ThreeWayMerge` bez `CellMergeConfig`, takže zlučujú iba na úrovni stĺpcov. Ak chcete zlúčiť na úrovni buniek, načítajte snímky a použite priamo túto triedu, ako je uvedené nižšie.

## Priame použitie tried

Ak chcete väčšiu kontrolu, vytvorte inštancie komponentov sami. `Snapshotter::capture()` nič neukladá, preto ponechajte snímky v pamäti alebo ich uložte pomocou `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'),
);
```

## Skratka na opravu

Ak sa zmenila iba jedna strana (bez súbežných úprav), použite `patch()`. Ide o obojsmerné zlúčenie, v ktorom sa naša strana rovná základnej verzii, takže konflikty nemôžu vzniknúť:

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

Cez fasádu:

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

`patch()` je ekvivalentom `merge($base, $base, $changes)`. Keďže sa naša strana nikdy neodchýlila od základnej verzie, každá zmena z ich strany sa prijme bez konfliktu.

## Zisťovanie nezhôd schémy

Ak boli medzi snímkami do tabuľky pridané alebo z nej odstránené stĺpce, zlúčenie napriek tomu pokračuje, no zaznamená nezhody. Zmenené typy stĺpcov sa neporovnávajú a premenovaný stĺpec sa zobrazí ako jeden odstránený a jeden pridaný; pozrite si [Overenie schémy](https://merql.inline0.dev/sk/docs/advanced/schema).

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

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

merql automaticky nemigruje schémy. Za zmeny schémy zodpovedá volajúci. Zlúčenie ich nahlási, aby ste sa mohli rozhodnúť, ako pokračovať.

## MergeResult

Objekt výsledku obsahuje všetko potrebné:

```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` alebo `delete`), názov tabuľky, kľúč riadka, hodnoty stĺpcov a zdroj (`ours`, `theirs` alebo `merged`). `merged` označuje riadok, ktorý aktualizovali obe strany (kombináciou zmien po jednotlivých stĺpcoch), alebo ktorý obe strany odstránili.

## Kde možno použiť výsledok zlúčenia

Výsledok zlúčenia opisuje, ako previesť základnú verziu do zlúčeného stavu. Jeho operácie obsahujú zmeny z oboch strán: vloženie z našej strany aj vloženie z ich strany sa zobrazia ako vloženia.

- `Merql::apply()` (alebo `Applier`) vykoná každú operáciu v pripojenej databáze. Bez problémov sa použije v databáze, ktorá stále obsahuje základnú verziu, napríklad v jej čerstvej kópii. Ak ho použijete v databáze, ktorá už obsahuje zmeny jednej strany, vloženie už existujúceho riadka zlyhá a transakcia sa vráti späť.
- Ak chcete preniesť našu stranu do databázy, ktorá už obsahuje ich stranu, napríklad nasadiť úpravy zo sandboxu do produkčného prostredia, použite iba operácie, ktoré v danej databáze chýbajú: tie so zdrojom `ours` a aktualizácie `merged`. Pomocou [plánu zlúčenia](https://merql.inline0.dev/sk/docs/merge/plans) ich vyberte a pomocou [Riadeného použitia](https://merql.inline0.dev/sk/docs/apply/guarded) ich zapíšte s predbežnými podmienkami pre živé riadky.
