
# Apsaugotas pritaikymas

`GuardedApplier` pritaiko švarų `MergeResult` tik tuo atveju, jei tikslinė duomenų bazė vis dar atitinka numatytą tiesioginę momentinę kopiją. Tai naudinga, kai sujungimo planas buvo peržiūrėtas anksčiau, o tiesioginės eilutės iki pritaikymo galėjo pasikeisti.

Įprastai smėliadėžės pakeitimai perkeliami į tiesioginę duomenų bazę. Bazinė versija yra tiesioginė duomenų bazė tuo metu, kai iš jos buvo nukopijuota smėliadėžė; mūsų versija yra redaguota smėliadėžė; jų versija – dabartinė tiesioginė duomenų bazė. Tikslas yra tiesioginė duomenų bazė, todėl numatyta tiesioginė momentinė kopija yra `theirs`, o įrašomos tos operacijos, kurių tiesioginėje duomenų bazėje dar nėra (žr. [Operacijų pasirinkimas](#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()` įrašo į duomenų bazę, perduotą `Merql::init()`. Numatyta tiesioginė momentinė kopija turi aprašyti tą pačią duomenų bazę: paimkite ją iš tikslo ir užfiksuokite pakankamai neseniai, kad išankstinės sąlygos būtų prasmingos.

## Išankstinės sąlygos

Apsaugotas SQL prie generuojamų sakinių prideda tiesioginių eilučių išankstines sąlygas:

- atnaujinant reikalaujama, kad tikslinės eilutės tapatybė egzistuotų, o ne tapatybės stulpelių reikšmės sutaptų su numatyta tiesiogine eilute,
- trinant reikalaujama, kad tikslinės eilutės tapatybė egzistuotų, o ne tapatybės stulpelių reikšmės sutaptų su numatyta tiesiogine eilute,
- įterpiant reikalaujama, kad tikslinė tapatybė dar neegzistuotų.

Jei kuris nors sakinys paveikia nulį eilučių, merql laiko planą pasenusiu. Operacija atšaukiama, o grąžinamame `ApplyResult` yra klaida `Optimistic precondition failed for guarded merge statement.`.

Naudojant MySQL, atnaujinimas, kurio reikšmės jau sutampa su tiesiogine eilute, taip pat paveikia nulį eilučių, nes MySQL skaičiuoja pakeistas, o ne atrinktas eilutes. Toks atnaujinimas laikomas pasenusiu, nors eilutė sutampa ([#2](https://github.com/inline0/merql/issues/2)). SQLite skaičiuoja atrinktas eilutes ir jį priima. Taip gali nutikti atliekant `merged` atnaujinimą, kai abi pusės vienodai pakeitė eilutę.

## Operacijų pasirinkimas

Sujungimo rezultate yra abiejų pusių operacijos, nes jos paverčia bazinę versiją sujungta būsena. Pritaikius rezultatą tikslui, kuriame jau yra jų versija, iš jų pusės kilusios operacijos aprašo pakeitimus, kuriuos tikslas jau turi, todėl jų išankstinės sąlygos neįvykdomos:

| Operacija | Tikslinės duomenų bazės, kurioje yra jų versija, būsena | Pasirinkti |
|---|---|---|
| `ours` įterpimas, atnaujinimas arba trynimas | Nėra | Taip |
| `merged` atnaujinimas (abi pusės pakeitė eilutę) | Yra jų stulpeliai, trūksta mūsų stulpelių | Taip |
| `theirs` įterpimas | Eilutė egzistuoja, todėl apsaugotas įterpimas paveikia nulį eilučių | Ne |
| `merged` trynimas (abi pusės ištrynė eilutę) | Eilutės nebėra, todėl apsaugotas trynimas paveikia nulį eilučių | Ne |
| `theirs` atnaujinimas arba trynimas | Jau pritaikyta | Ne |

Todėl perduodant visą `Merql::merge()` rezultatą į `applyGuarded()`, pritaikymas nepavyksta vos tik jų pusė įterpia eilutę arba abi pusės ją ištrina. Pirmiausia pasirinkite operacijas pagal šaltinį, kaip nurodyta pirmiau, arba naudodami `ChangeGroupSelection`, sudarytą iš peržiūros UI. Praleiskite `$plan->conflicts` nurodytas eilutes: pasirinktas rezultatas visada būna be konfliktų, o operacijoje, skirtoje konfliktuojančiai eilutei, konflikto stulpelyje pateikiama mūsų reikšmė (žr. [Sujungimo planai](https://merql.inline0.dev/lt/docs/merge/plans#selecting-what-a-target-is-missing)).

## Pasirinktas pritaikymas

Apsaugotas pritaikymas veikia su pasirinktais sujungimo rezultatais, gautais iš sujungimo plano, įskaitant pakeitimų grupėmis atliktus pasirinkimus.

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

Taip pagrindinė programa gali paruošti peržiūrėtų grupių poaibį ir vis tiek patikrinti, kad po plano sukūrimo tiesioginės eilutės nepasikeitė.

## SQL peržiūra

Naudokite `SqlGenerator::generateGuarded()`, kad peržiūrėtumėte apsaugotus sakinius jų nevykdydami.

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

Generuojami sakiniai ir toliau parametrizuojami. Atnaujinimų ir trynimų išankstinės sąlygos išreiškiamos papildomomis `WHERE` sąlygomis, o įterpimų – `NOT EXISTS` patikromis.

## Apsaugotas arba įprastas pritaikymas

`Merql::apply()` vykdo tas pačias operacijas be išankstinių sąlygų. Tada atnaujinimai ir trynimai pavyksta nepaisant to, ar tiesioginė eilutė pasikeitė po plano sudarymo, o įterpus jau esamą eilutę pateikiama duomenų bazės pasikartojančio rakto klaida. Įprastą pritaikymą naudokite tikslui, kuriame, kaip žinote, vis dar yra bazinė versija, o apsaugotą – kai kiti rašytojai galėjo pakeisti tikslą po jo momentinės kopijos sukūrimo.
