
# Защитено прилагане

`GuardedApplier` прилага резултат `MergeResult` само ако целевата база данни все още съвпада с очакваната моментна снимка на живото състояние. Това е полезно, когато планът за сливане е бил прегледан по-рано и живите редове може да са се променили преди прилагането.

Типичната настройка прехвърля редакциите от изолирана среда към жива база данни. Основата е живата база в момента, когато е създадено копието за изолираната среда; нашата страна е редактираната изолирана среда, а тяхната — живата база в момента. Целта е живата база данни, затова очакваната моментна снимка на живото състояние е `theirs`, а операциите за запис са тези, които живата база все още няма (вижте [Избор на операции](#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()` записва в базата данни, подадена на `Merql::init()`. Очакваната моментна снимка на живото състояние трябва да описва същата база данни: вземете я от целевата база и създайте достатъчно скорошна снимка, за да са смислени предварителните условия.

## Предварителни условия

Защитеното SQL добавя предварителни условия за живите редове към генерираните заявки:

- актуализациите изискват идентичността на целевия ред да съществува, а стойностите в колоните, различни от колоните за идентичност, да съвпадат с очаквания жив ред,
- изтриванията изискват идентичността на целевия ред да съществува, а стойностите в колоните, различни от колоните за идентичност, да съвпадат с очаквания жив ред,
- вмъкванията изискват целевата идентичност все още да не съществува.

Ако някоя заявка засегне нула реда, merql третира плана като остарял. Транзакцията се отменя и върнатият `ApplyResult` съдържа грешката `Optimistic precondition failed for guarded merge statement.`

При MySQL актуализация, чиито стойности вече съвпадат с живия ред, също засяга нула реда, защото MySQL отчита променените редове, а не съвпадналите. Такава актуализация се проваля като остаряла, въпреки че редът съвпада ([#2](https://github.com/inline0/merql/issues/2)).
SQLite отчита съвпадналите редове и я приема. Това може да се случи при актуализация `merged`,
когато двете страни са направили една и съща промяна в ред.

## Избор на операции

Резултатът от сливане съдържа операциите и на двете страни, тъй като превръща основата в обединеното състояние. При прилагане към цел, която вече съдържа тяхната версия, операциите от тяхната страна описват промени, които целта вече има, и техните предварителни условия се провалят:

| Операция | Състояние в целта, която съдържа техните промени | Изберете |
|---|---|---|
| `ours` вмъкване, актуализиране или изтриване | Липсва | Да |
| `merged` актуализиране (и двете страни са променили реда) | Съдържа колоните на тяхната страна, но липсват колоните на нашата | Да |
| `theirs` вмъкване | Редът съществува, затова защитеното вмъкване не засяга нито един ред | Не |
| `merged` изтриване (и двете страни са изтрили реда) | Редът липсва, затова защитеното изтриване не засяга нито един ред | Не |
| `theirs` актуализиране или изтриване | Вече е приложено | Не |

Затова подаването на целия резултат от `Merql::merge()` към `applyGuarded()` се проваля,
щом тяхната страна е вмъкнала ред или и двете страни са изтрили ред. Първо изберете операциите — по източник, както по-горе, или с `ChangeGroupSelection`, създаден от преглед UI. Изключете редовете, посочени в `$plan->conflicts`: избраният резултат винаги е чист, а операцията за конфликтен ред съдържа стойността на нашата страна в конфликтната колона (вижте [Планове за сливане](https://merql.inline0.dev/bg/docs/merge/plans#selecting-what-a-target-is-missing)).

## Избрано прилагане

Защитеното прилагане работи с избрани резултати от сливане, създадени от план за сливане, включително избраните по група промени.

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

Това позволява на хост приложението да подготви подмножество от прегледаните групи и да потвърди, че активните редове не са се променили след създаването на плана.

## Преглед на SQL

Използвайте `SqlGenerator::generateGuarded()`, за да прегледате защитените инструкции, без да ги изпълнявате.

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

Генерираните инструкции продължават да използват параметри. Предварителните условия се задават с допълнителни клаузи `WHERE` за актуализации и изтривания и проверки `NOT EXISTS` за вмъквания.

## Защитено или обикновено прилагане

`Merql::apply()` изпълнява същите операции без предварителни условия. Тогава актуализациите и изтриванията успяват независимо дали активният ред се е променил след създаването на плана, а вмъкването на съществуващ ред се проваля с грешка за дублиран ключ от базата данни. Използвайте обикновено прилагане за цел, за която знаете, че все още съдържа базовото състояние, и защитено прилагане, когато други процеси може да са променили целта след създаването на моментната снимка.
