Beskyttet anvendelse
GuardedApplier anvender kun et konfliktfrit MergeResult, hvis måldatabasen stadig
stemmer overens med det forventede live-snapshot. Det er nyttigt, når en sammenfletningsplan er blevet
gennemgået tidligere, og live-rækker kan være ændret inden anvendelsen.
Den typiske opsætning overfører ændringer fra et sandkassemiljø til en live-database. Base er live på det tidspunkt, hvor sandkassen blev kopieret fra den; ours er den redigerede sandkasse, og theirs er live, som den er nu. Målet er live-databasen, så det forventede live-snapshot er theirs, og de operationer, der skal skrives, er dem, live endnu ikke har (se Valg af operationer).
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() skriver til databasen, der er sendt til Merql::init(). Det forventede live-snapshot skal beskrive den samme database: Hent det fra målet og sørg for, at det er nyt nok til, at forudsætningerne giver mening.
Forudsætninger
Beskyttet SQL føjer forudsætninger om live-rækker til genererede sætninger:
- opdateringer kræver, at målrækkens identitet findes, og at værdierne i ikke-identitetskolonner svarer til den forventede live-række,
- sletninger kræver, at målrækkens identitet findes, og at værdierne i ikke-identitetskolonner svarer til den forventede live-række,
- indsættelser kræver, at målets identitet ikke allerede findes.
Hvis en sætning påvirker nul rækker, behandler merql planen som forældet. Transaktionen rulles tilbage, og den returnerede ApplyResult indeholder fejlen Optimistic precondition failed for guarded merge statement.
På MySQL påvirker en opdatering, hvis værdier allerede svarer til live-rækken, også nul rækker, fordi MySQL tæller ændrede rækker i stedet for rækker, der matchede. Derfor fejler opdateringen som forældet, selvom rækken matcher (#2).
SQLite tæller rækker, der matchede, og accepterer opdateringen. Det kan ske ved en merged-opdatering, når begge sider foretog den samme ændring af en række.
Valg af operationer
Et sammenfletningsresultat indeholder begge siders operationer, fordi det omdanner base til den sammenflettede tilstand. Når det anvendes på et mål, der allerede indeholder theirs, beskriver operationerne fra theirs ændringer, som målet allerede har, og deres forudsætninger fejler:
| Operation | Tilstand i et mål, der indeholder theirs | Vælg |
|---|---|---|
ours indsættelse, opdatering eller sletning |
Mangler | Ja |
merged opdatering (begge sider ændrede rækken) |
Har theirs' kolonner, mangler ours' | Ja |
theirs indsættelse |
Rækken findes, så den beskyttede indsættelse påvirker nul rækker | Nej |
merged sletning (begge sider slettede rækken) |
Rækken er væk, så den beskyttede sletning påvirker nul rækker | Nej |
theirs opdatering eller sletning |
Allerede anvendt | Nej |
Hvis hele resultatet fra Merql::merge() sendes til applyGuarded(), fejler det derfor, så snart theirs har indsat en række, eller begge sider har slettet en række. Vælg først operationerne efter kilde som ovenfor eller med et ChangeGroupSelection, der er opbygget ud fra en gennemgangs-UI. Udelad rækker, der er anført i $plan->conflicts: Et valgt resultat er altid konfliktfrit, og operationen for en række med konflikt indeholder ours' værdi i konfliktkolonnen (se Sammenfletningsplaner).
Valgt anvendelse
Beskyttet anvendelse fungerer med valgte sammenfletningsresultater fra en sammenfletningsplan, herunder valg foretaget efter ændringsgruppe.
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));
Det gør det muligt for et værtsprogram at klargøre et undersæt af gennemgåede grupper og stadig fastslå, at live-rækkerne ikke har ændret sig siden planens oprettelse.
Forhåndsvisning af SQL
Brug SqlGenerator::generateGuarded() til at undersøge de beskyttede sætninger uden at udføre dem.
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;
$statements = SqlGenerator::generateGuarded(
$selected,
SnapshotStore::load($plan->theirsSnapshot),
);
foreach ($statements as $statement) {
echo $statement['sql'] . "\n";
}
De genererede sætninger er stadig parametriserede. Forudsætninger angives med ekstra WHERE-klausuler for opdateringer og sletninger og med NOT EXISTS-kontroller for indsættelser.
Beskyttet eller almindelig anvendelse
Merql::apply() udfører de samme operationer uden forudsætninger. Opdateringer og sletninger lykkes dermed, uanset om live-rækken er ændret, siden planen blev oprettet, og en indsættelse af en eksisterende række fejler med databasens fejl om dubletnøgle. Brug almindelig anvendelse til et mål, som du ved stadig indeholder base, og beskyttet anvendelse, når andre kan have ændret målet, siden det blev taget et snapshot af.