Към съдържанието

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

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

Типичната настройка прехвърля редакциите от изолирана среда към жива база данни. Основата е живата база в момента, когато е създадено копието за изолираната среда; нашата страна е редактираната изолирана среда, а тяхната — живата база в момента. Целта е живата база данни, затова очакваната моментна снимка на живото състояние е theirs, а операциите за запис са тези, които живата база все още няма (вижте Избор на операции).

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). SQLite отчита съвпадналите редове и я приема. Това може да се случи при актуализация merged, когато двете страни са направили една и съща промяна в ред.

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

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

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

Затова подаването на целия резултат от Merql::merge() към applyGuarded() се проваля, щом тяхната страна е вмъкнала ред или и двете страни са изтрили ред. Първо изберете операциите — по източник, както по-горе, или с ChangeGroupSelection, създаден от преглед UI. Изключете редовете, посочени в $plan->conflicts: избраният резултат винаги е чист, а операцията за конфликтен ред съдържа стойността на нашата страна в конфликтната колона (вижте Планове за сливане).

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

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

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() изпълнява същите операции без предварителни условия. Тогава актуализациите и изтриванията успяват независимо дали активният ред се е променил след създаването на плана, а вмъкването на съществуващ ред се проваля с грешка за дублиран ключ от базата данни. Използвайте обикновено прилагане за цел, за която знаете, че все още съдържа базовото състояние, и защитено прилагане, когато други процеси може да са променили целта след създаването на моментната снимка.