
# Chráněné použití

`GuardedApplier` použije čistý `MergeResult` pouze tehdy, pokud cílová databáze stále odpovídá očekávanému aktuálnímu snímku. Hodí se, když byl plán sloučení zkontrolován dříve a aktuální řádky se mohly před použitím změnit.

Obvyklé nastavení přenáší úpravy testovacího prostředí do živé databáze. Základ tvoří živá databáze v okamžiku, kdy z ní bylo testovací prostředí zkopírováno, ours je upravená kopie a theirs je aktuální živá databáze. Cílem je živá databáze, takže očekávaným aktuálním snímkem je `theirs` a zapisují se operace, které živá databáze zatím neobsahuje (viz [Výběr operací](#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()` zapisuje do databáze předané do `Merql::init()`. Očekávaný aktuální snímek musí popisovat stejnou databázi: pořiďte ho z cíle a dostatečně nedávno, aby podmínky měly vypovídací hodnotu.

## Předběžné podmínky

Chráněné SQL přidává do generovaných příkazů podmínky pro aktuální řádky:

- u aktualizací musí v cíli existovat identita řádku a hodnoty sloupců neurčujících identitu se musí shodovat s očekávaným aktuálním řádkem,
- u odstranění musí v cíli existovat identita řádku a hodnoty sloupců neurčujících identitu se musí shodovat s očekávaným aktuálním řádkem,
- u vložení nesmí cílová identita již existovat.

Pokud některý příkaz ovlivní nula řádků, merql považuje plán za zastaralý. Transakce se vrátí zpět a vrácený `ApplyResult` obsahuje chybu `Optimistic precondition failed for guarded merge statement.`

V MySQL ovlivní aktualizace, jejíž hodnoty již odpovídají aktuálnímu řádku, nula řádků, protože MySQL počítá změněné řádky, nikoli nalezené. Aktualizace se proto vyhodnotí jako zastaralá, i když řádek odpovídá ([#2](https://github.com/inline0/merql/issues/2)). SQLite počítá nalezené řádky a přijme ji. Může k tomu dojít při aktualizaci `merged`, když obě strany provedly stejnou změnu řádku.

## Výběr operací

Výsledek sloučení obsahuje operace obou stran, protože mění základ na sloučený stav. Při použití na cíli, který již obsahuje theirs, změny převzaté z theirs popisují stav, který cíl už má, a jejich podmínky selžou:

| Operace | Stav v cíli obsahujícím theirs | Vybrat |
|---|---|---|
| `ours` vložení, aktualizace nebo odstranění | Chybí | Ano |
| `merged` aktualizace (obě strany změnily řádek) | Obsahuje sloupce theirs, chybí sloupce ours | Ano |
| `theirs` vložení | Řádek existuje, takže chráněné vložení ovlivní nula řádků | Ne |
| `merged` odstranění (obě strany odstranily řádek) | Řádek chybí, takže chráněné odstranění ovlivní nula řádků | Ne |
| `theirs` aktualizace nebo odstranění | Již použito | Ne |

Předání celého výsledku `Merql::merge()` do `applyGuarded()` proto selže, jakmile theirs vložilo řádek nebo ho obě strany odstranily. Nejprve vyberte operace podle zdroje, jak je uvedeno výše, nebo pomocí `ChangeGroupSelection` vytvořeného z kontroly UI. Vynechte řádky uvedené v `$plan->conflicts`: vybraný výsledek je vždy čistý a operace konfliktního řádku obsahuje hodnotu ours v konfliktním sloupci (viz [Plány sloučení](https://merql.inline0.dev/cs/docs/merge/plans#selecting-what-a-target-is-missing)).

## Vybrané použití

Chráněné použití funguje s vybranými výsledky sloučení vytvořenými z plánu sloučení, včetně výběrů podle skupiny změn.

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

Hostitelská aplikace tak může připravit podmnožinu zkontrolovaných skupin a zároveň ověřit, že se aktuální řádky od vytvoření plánu nezměnily.

## Náhled SQL

Pomocí `SqlGenerator::generateGuarded()` zkontrolujte chráněné příkazy, aniž byste je provedli.

```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";
}
```

Generované příkazy jsou stále parametrizované. Předběžné podmínky jsou vyjádřeny dalšími klauzulemi `WHERE` pro aktualizace a odstranění a kontrolami `NOT EXISTS` pro vložení.

## Chráněné nebo běžné použití

`Merql::apply()` provádí stejné operace bez předběžných podmínek. Aktualizace a odstranění pak uspějí bez ohledu na to, zda se aktuální řádek od vytvoření plánu změnil, a vložení existujícího řádku selže kvůli chybě duplicitního klíče databáze. Běžné použití zvolte pro cíl, o němž víte, že stále obsahuje základ; chráněné použití zvolte, pokud se cíle od pořízení snímku mohli dotknout jiní zapisující.
