Защитено прилагане
GuardedApplier прилага резултат MergeResult само ако целевата база данни все още съвпада с очакваната моментна снимка на живото състояние. Това е полезно, когато планът за сливане е бил прегледан по-рано и живите редове може да са се променили преди прилагането.
Типичната настройка прехвърля редакциите от изолирана среда към жива база данни. Основата е живата база в момента, когато е създадено копието за изолираната среда; нашата страна е редактираната изолирана среда, а тяхната — живата база в момента. Целта е живата база данни, затова очакваната моментна снимка на живото състояние е theirs, а операциите за запис са тези, които живата база все още няма (вижте Избор на операции).
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: избраният резултат винаги е чист, а операцията за конфликтен ред съдържа стойността на нашата страна в конфликтната колона (вижте Планове за сливане).
Избрано прилагане
Защитеното прилагане работи с избрани резултати от сливане, създадени от план за сливане, включително избраните по група промени.
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(), за да прегледате защитените инструкции, без да ги изпълнявате.
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() изпълнява същите операции без предварителни условия. Тогава актуализациите и изтриванията успяват независимо дали активният ред се е променил след създаването на плана, а вмъкването на съществуващ ред се проваля с грешка за дублиран ключ от базата данни. Използвайте обикновено прилагане за цел, за която знаете, че все още съдържа базовото състояние, и защитено прилагане, когато други процеси може да са променили целта след създаването на моментната снимка.