
# Védett alkalmazás

A `GuardedApplier` csak akkor alkalmaz egy ütközésmentes `MergeResult`-t, ha a céladatbázis még mindig megfelel az elvárt éles pillanatképnek. Ez akkor hasznos, ha korábban már áttekintett egy egyesítési tervet, és az éles sorok az alkalmazás előtt megváltozhattak.

A tipikus beállítás során a tesztkörnyezet módosításait egy éles adatbázisba emelik át. Az alap az éles adatbázis állapota a tesztkörnyezet másolásakor, az ours a szerkesztett tesztkörnyezet, a theirs pedig az éles adatbázis mostani állapota. A cél az éles adatbázis, ezért az elvárt éles pillanatkép a `theirs`, az írandó műveletek pedig azok, amelyek még nincsenek meg az éles adatbázisban (lásd: [Műveletek kiválasztása](#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";
    }
}
```

A `Merql::applyGuarded()` a `Merql::init()` számára átadott adatbázisba ír. Az elvárt éles pillanatképnek ugyanezt az adatbázist kell leírnia: a célból készítse, és elég frissen, hogy az előfeltételeknek legyen jelentőségük.

## Előfeltételek

A védett SQL éles sorokra vonatkozó előfeltételeket ad a létrehozott utasításokhoz:

- frissítéskor a célsor azonosítójának léteznie kell, és a nem azonosító oszlopok értékeinek egyezniük kell az elvárt éles sorral,
- törléskor a célsor azonosítójának léteznie kell, és a nem azonosító oszlopok értékeinek egyezniük kell az elvárt éles sorral,
- beszúráskor a célban még nem létezhet az adott azonosító.

Ha bármelyik utasítás nulla sort érint, a merql elavultnak tekinti a tervet. A tranzakció visszagördül, a visszaadott `ApplyResult` pedig a következő hibát tartalmazza: `Optimistic precondition failed for guarded merge statement.`

A MySQL esetén az a frissítés is nulla sort érint, amelynek értékei már megegyeznek az éles sorral, mert az MySQL a megváltozott, nem pedig az egyező sorokat számolja. Az ilyen frissítés akkor is elavultnak minősül, ha a sor egyezik ([#2](https://github.com/inline0/merql/issues/2)).
Az SQLite az egyező sorokat számolja, és elfogadja az ilyen frissítést. Ez akkor fordulhat elő, ha `merged` frissítés során mindkét oldal ugyanazt a módosítást hajtotta végre egy soron.

## Műveletek kiválasztása

Az egyesítési eredmény mindkét oldal műveleteit tartalmazza, hiszen ezek alakítják át az alapot az egyesített állapottá. Ha olyan célra alkalmazza, amely már a theirs állapotát tartalmazza, a theirs oldaláról származó műveletek olyan változásokat írnak le, amelyeket a cél már tartalmaz, ezért az előfeltételeik nem teljesülnek:

| Művelet | Állapot egy theirs állapotú célban | Kiválasztás |
|---|---|---|
| `ours` beszúrás, frissítés vagy törlés | Hiányzik | Igen |
| `merged` frissítés (mindkét oldal módosította a sort) | A theirs oszlopai megvannak, az ours oszlopai hiányoznak | Igen |
| `theirs` beszúrás | A sor már létezik, ezért a védett beszúrás nulla sort érint | Nem |
| `merged` törlés (mindkét oldal törölte a sort) | A sor már nincs meg, ezért a védett törlés nulla sort érint | Nem |
| `theirs` frissítés vagy törlés | Már alkalmazva | Nem |

Ezért ha a `Merql::merge()` teljes eredményét átadja a `applyGuarded()` számára, az hibát jelez, amint a theirs beszúrt egy sort, vagy mindkét oldal törölt egyet. Előbb válassza ki a műveleteket a fenti forrás szerinti bontással, vagy a felülvizsgálati UI alapján létrehozott `ChangeGroupSelection` segítségével. Hagyja ki a `$plan->conflicts` elemet tartalmazó sorokat: a kiválasztott eredmény mindig ütközésmentes, és egy ütköző sor művelete az ours értékét tartalmazza az ütköző oszlopban (lásd: [Egyesítési tervek](https://merql.inline0.dev/hu/docs/merge/plans#selecting-what-a-target-is-missing)).

## Kiválasztott alkalmazás

A védett alkalmazás az egyesítési tervből létrehozott kiválasztott egyesítési eredményekkel is működik, beleértve a módosításcsoportok szerint végzett kiválasztást is.

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

Ez lehetővé teszi a gazdaalkalmazás számára, hogy az áttekintett csoportok egy részét előkészítse, és közben továbbra is ellenőrizze, hogy az éles sorok nem tértek el a terv létrehozása óta.

## SQL előnézet

A `SqlGenerator::generateGuarded()` segítségével megvizsgálhatja a védett utasításokat végrehajtás nélkül.

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

A létrehozott utasítások továbbra is paraméterezettek. A frissítések és törlések előfeltételeit további `WHERE` záradékok, a beszúrásokét pedig `NOT EXISTS` ellenőrzések fejezik ki.

## Védett vagy egyszerű alkalmazás

A `Merql::apply()` ugyanazokat a műveleteket hajtja végre előfeltételek nélkül. A frissítések és törlések ilyenkor attól függetlenül sikerülnek, hogy az éles sor megváltozott-e a terv összeállítása óta, egy már létező sor beszúrása pedig az adatbázis ismétlődő kulcsra vonatkozó hibáját eredményezi. Egyszerű alkalmazást használjon olyan célhoz, amelyről tudja, hogy még az alapállapotot tartalmazza; védett alkalmazást pedig akkor, ha a pillanatkép készítése óta más írók is módosíthatták a célt.
