Vai al contenuto

Applicazione protetta

GuardedApplier applica un MergeResult senza conflitti solo se il database di destinazione corrisponde ancora allo snapshot atteso delle righe correnti. È utile quando un piano di fusione è stato revisionato in precedenza e le righe correnti potrebbero essere cambiate prima dell'applicazione.

La configurazione tipica promuove le modifiche di un ambiente sandbox a un database live. Base è il database live al momento della copia della sandbox, ours è la sandbox modificata e theirs è il database live attuale. La destinazione è il database live, quindi lo snapshot atteso delle righe correnti è theirs e le operazioni da scrivere sono quelle che il database live non contiene ancora (vedere Scegliere le operazioni).

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() scrive nel database passato a Merql::init(). Lo snapshot atteso delle righe correnti deve descrivere quello stesso database: acquisitelo dalla destinazione e abbastanza di recente da rendere significative le precondizioni.

Precondizioni

SQL protetta aggiunge precondizioni sulle righe correnti alle istruzioni generate:

  • gli aggiornamenti richiedono che l'identità della riga di destinazione esista e che i valori delle colonne non di identità corrispondano alla riga corrente attesa,
  • le eliminazioni richiedono che l'identità della riga di destinazione esista e che i valori delle colonne non di identità corrispondano alla riga corrente attesa,
  • gli inserimenti richiedono che l'identità di destinazione non esista già.

Se un'istruzione interessa zero righe, merql considera il piano obsoleto. La transazione viene annullata e il risultato restituito ApplyResult contiene l'errore Optimistic precondition failed for guarded merge statement.

Su MySQL, anche un aggiornamento i cui valori sono già uguali alla riga corrente interessa zero righe, perché MySQL conta le righe modificate, non quelle corrispondenti. L'aggiornamento fallisce come obsoleto anche se la riga corrisponde (#2). SQLite conta le righe corrispondenti e lo accetta. Può succedere con un aggiornamento merged quando entrambi i lati hanno apportato la stessa modifica a una riga.

Scegliere le operazioni

Un risultato di fusione contiene le operazioni di entrambi i lati, perché trasforma base nello stato risultante dalla fusione. Se applicate a una destinazione che contiene già theirs, le operazioni provenienti da theirs descrivono modifiche che la destinazione ha già e le relative precondizioni falliscono:

Operazione Stato in una destinazione che contiene theirs Seleziona
ours inserimento, aggiornamento o eliminazione Assente Sì
merged aggiornamento (entrambi i lati hanno modificato la riga) Contiene le colonne di theirs, mancano quelle di ours Sì
theirs inserimento La riga esiste, quindi l'inserimento protetto interessa zero righe No
merged eliminazione (entrambi i lati hanno eliminato la riga) La riga non esiste, quindi l'eliminazione protetta interessa zero righe No
theirs aggiornamento o eliminazione Già applicata No

Passare l'intero risultato di Merql::merge() a applyGuarded() comporta quindi un errore non appena theirs ha inserito una riga o entrambi i lati ne hanno eliminata una. Selezionate prima le operazioni, in base alla sorgente come sopra oppure con un ChangeGroupSelection creato da una revisione UI. Escludete le righe elencate in $plan->conflicts: il risultato selezionato è sempre senza conflitti e l'operazione per una riga in conflitto include il valore di ours nella colonna in conflitto (vedere Piani di fusione).

Applicazione selezionata

L'applicazione protetta funziona con i risultati di fusione selezionati generati da un piano di fusione, comprese le selezioni effettuate per gruppo di modifiche.

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

Questo consente a un'applicazione host di preparare un sottoinsieme di gruppi revisionati e verificare comunque che le righe correnti non siano cambiate dalla creazione del piano.

Anteprima di SQL

Usate SqlGenerator::generateGuarded() per esaminare le istruzioni protette senza eseguirle.

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

Le istruzioni generate sono comunque parametrizzate. Le precondizioni sono espresse con clausole WHERE aggiuntive per gli aggiornamenti e le eliminazioni, e verifiche NOT EXISTS per gli inserimenti.

Applicazione protetta o semplice

Merql::apply() esegue le stesse operazioni senza precondizioni. Gli aggiornamenti e le eliminazioni riescono quindi indipendentemente dal fatto che la riga corrente sia cambiata dopo la creazione del piano, mentre l'inserimento di una riga esistente genera un errore di chiave duplicata del database. Usate l'applicazione semplice per una destinazione che sapete contenere ancora base e quella protetta quando altri processi potrebbero aver modificato la destinazione dopo la creazione dello snapshot.