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