
# Suojattu käyttöönotto

`GuardedApplier` ottaa puhtaan `MergeResult`-tuloksen käyttöön vain, jos kohdetietokanta vastaa edelleen odotettua reaaliaikaista tilannevedosta. Tämä on hyödyllistä, kun yhdistämissuunnitelma on tarkistettu aiemmin ja reaaliaikaiset rivit ovat saattaneet muuttua ennen käyttöönottoa.

Tavallisessa asetelmassa hiekkalaatikon muokkaukset siirretään reaaliaikaiseen tietokantaan. Pohja on reaaliaikainen tietokanta sillä hetkellä, kun hiekkalaatikko kopioitiin siitä, meidän-puoli on muokattu hiekkalaatikko ja heidän-puoli on reaaliaikainen tietokanta nykytilassaan. Kohteena on reaaliaikainen tietokanta, joten odotettu reaaliaikainen tilannevedos on `theirs`, ja kirjoitettavat operaatiot ovat niitä, joita reaaliaikaisessa tietokannassa ei vielä ole (katso [Operaatioden valinta](#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()` kirjoittaa tietokantaan, joka välitetään funktiolle `Merql::init()`. Odotetun reaaliaikaisen tilannevedoksen on kuvattava samaa tietokantaa: ota se kohteesta ja riittävän hiljattain, jotta ehdot ovat mielekkäitä.

## Ehdot

Suojattu SQL lisää luotuihin lausekkeisiin reaaliaikaisia rivejä koskevat ehdot:

- päivityksissä kohderivin identiteetin on oltava olemassa ja muiden kuin identiteettisarakkeiden arvojen on vastattava odotettua reaaliaikaista riviä,
- poistoissa kohderivin identiteetin on oltava olemassa ja muiden kuin identiteettisarakkeiden arvojen on vastattava odotettua reaaliaikaista riviä,
- lisäyksissä kohteen identiteetti ei saa olla jo olemassa.

Jos jokin lauseke vaikuttaa nollaan riviin, merql käsittelee suunnitelmaa vanhentuneena. Tapahtuma perutaan, ja palautettava `ApplyResult` sisältää virheen `Optimistic precondition failed for guarded merge statement.`

MySQL-ajurissa päivitys, jonka arvot vastaavat jo reaaliaikaista riviä, vaikuttaa myös nollaan riviin, koska MySQL laskee muuttuneet rivit eikä täsmänneitä rivejä. Tällainen päivitys epäonnistuu vanhentuneena, vaikka rivi täsmää ([#2](https://github.com/inline0/merql/issues/2)).
SQLite laskee täsmänneet rivit ja hyväksyy sen. Näin voi käydä `merged`-päivityksessä, kun molemmat puolet ovat tehneet samaan riviin saman muutoksen.

## Operaatioiden valinta

Yhdistämistulos sisältää molempien puolten operaatiot, koska se muuttaa pohjatilan yhdistetyksi tilaksi. Kun tulos otetaan käyttöön kohteessa, jossa on jo heidän-puolen tila, heidän-puoleltaan peräisin olevat operaatiot kuvaavat muutoksia, jotka kohteessa ovat jo, joten niiden ehdot epäonnistuvat:

| Operaatio | Tila kohteessa, jossa on heidän-puolen tila | Valitse |
|---|---|---|
| `ours` lisäys, päivitys tai poisto | Puuttuu | Kyllä |
| `merged` päivitys (molemmat puolet muuttivat riviä) | Sisältää heidän sarakkeensa, meidän sarakkeet puuttuvat | Kyllä |
| `theirs` lisäys | Rivi on olemassa, joten suojattu lisäys vaikuttaa nollaan riviin | Ei |
| `merged` poisto (molemmat puolet poistivat rivin) | Rivi on poistettu, joten suojattu poisto vaikuttaa nollaan riviin | Ei |
| `theirs` päivitys tai poisto | Jo käytössä | Ei |

Siksi koko tuloksen `Merql::merge()` välittäminen funktiolle `applyGuarded()` epäonnistuu heti, kun heidän-puolensa on lisännyt rivin tai molemmat puolet ovat poistaneet rivin. Valitse ensin operaatiot lähteen perusteella yllä kuvatulla tavalla tai luomalla `ChangeGroupSelection` tarkistuksen UI-tuloksen pohjalta. Jätä pois kohteessa `$plan->conflicts` luetellut rivit: valittu tulos on aina puhdas, ja ristiriitaisen rivin operaatio sisältää meidän arvomme ristiriitaisessa sarakkeessa (katso [Yhdistämissuunnitelmat](https://merql.inline0.dev/fi/docs/merge/plans#selecting-what-a-target-is-missing)).

## Valikoitu käyttöönotto

Suojattu käyttöönotto toimii yhdistämissuunnitelmasta luotujen valittujen yhdistämistulosten kanssa, myös muutosryhmän perusteella tehtyjen valintojen kanssa.

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

Tämän ansiosta isäntäsovellus voi ottaa käyttöön osan tarkistetuista ryhmistä ja samalla varmistaa, etteivät reaaliaikaiset rivit ole poikenneet suunnitelman luonnin jälkeen.

## SQL-esikatselu

Käytä toimintoa `SqlGenerator::generateGuarded()` suojattujen lausekkeiden tarkasteluun suorittamatta niitä.

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

Luodut lausekkeet ovat yhä parametrisoituja. Päivitysten ja poistojen ehdot ilmaistaan ylimääräisillä `WHERE`-lauseilla ja lisäysten ehdot `NOT EXISTS`-tarkistuksilla.

## Suojattu tai tavallinen käyttöönotto

`Merql::apply()` suorittaa samat operaatiot ilman ehtoja. Päivitykset ja poistot onnistuvat tällöin riippumatta siitä, onko reaaliaikainen rivi muuttunut suunnitelman luonnin jälkeen. Olemassa olevan rivin lisäys epäonnistuu tietokannan kaksoisavainvirheeseen. Käytä tavallista käyttöönottoa kohteessa, jonka tiedät olevan yhä pohjatilassa, ja suojattua käyttöönottoa, kun muut kirjoittajat ovat voineet muuttaa kohdetta tilannevedoksen ottamisen jälkeen.
