@quereus/isolation 4.4.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/src/alter-migration.d.ts +261 -0
- package/dist/src/alter-migration.d.ts.map +1 -0
- package/dist/src/alter-migration.js +827 -0
- package/dist/src/alter-migration.js.map +1 -0
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/isolated-table.d.ts +47 -20
- package/dist/src/isolated-table.d.ts.map +1 -1
- package/dist/src/isolated-table.js +70 -26
- package/dist/src/isolated-table.js.map +1 -1
- package/dist/src/isolation-module.d.ts +61 -318
- package/dist/src/isolation-module.d.ts.map +1 -1
- package/dist/src/isolation-module.js +191 -609
- package/dist/src/isolation-module.js.map +1 -1
- package/dist/src/isolation-types.d.ts +49 -1
- package/dist/src/isolation-types.d.ts.map +1 -1
- package/dist/src/overlay-rows.d.ts +5 -20
- package/dist/src/overlay-rows.d.ts.map +1 -1
- package/dist/src/overlay-rows.js +7 -31
- package/dist/src/overlay-rows.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -84,6 +84,30 @@ The isolation layer operates at the **row level**, merging query results from tw
|
|
|
84
84
|
└─────────────────────────────────────────────────────────┘
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### Reading a pushed index window
|
|
88
|
+
|
|
89
|
+
When the underlying serves a read through an index seek, the overlay must be narrowed to
|
|
90
|
+
the same window — otherwise a staged row outside it would leak into the answer. The
|
|
91
|
+
overlay is full-scanned (it cannot resolve an index name the underlying minted for its own
|
|
92
|
+
plan), and `IsolatedTable`'s constraint matcher re-applies the pushed window itself. Several
|
|
93
|
+
equality constraints on the *same* column are read as an **IN set**, not as an AND: that is
|
|
94
|
+
how an `IN`-list seek is encoded — one equality per seek value — so an AND reading would
|
|
95
|
+
match nothing and silently drop every staged row.
|
|
96
|
+
|
|
97
|
+
This holds for a set of values that only exists at run time (`where v in (select …)`,
|
|
98
|
+
which the engine may materialize and push down as a seek) exactly as it does for a literal
|
|
99
|
+
`in (1, 2, 3)`: the pushed window has the same shape either way, and this layer cannot —
|
|
100
|
+
and need not — tell them apart.
|
|
101
|
+
|
|
102
|
+
One ordering caveat. The **primary-key** merge walks both streams in the primary key's own
|
|
103
|
+
declared order — ascending, or descending for a `primary key (… desc)`. The engine sorts a
|
|
104
|
+
run-time key set into that same order before pushing it, so that path arrives in order. A
|
|
105
|
+
**literal** list does not: `where pk in (3, 1, 2)` is visited in list order, and with rows
|
|
106
|
+
staged in the transaction the merge mis-pairs them — a staged update can surface alongside
|
|
107
|
+
the stale stored row, and a staged delete can reappear. That is tracked as
|
|
108
|
+
`backlog/bug-isolation-multiseek-merge-order`; the secondary-index merge is unaffected
|
|
109
|
+
(it sorts the overlay itself and excludes shadowed rows by primary key).
|
|
110
|
+
|
|
87
111
|
## Isolation Level
|
|
88
112
|
|
|
89
113
|
The guarantee this layer actually provides is closer to **read-committed with
|
|
@@ -110,12 +134,15 @@ read-your-own-writes** than to snapshot isolation:
|
|
|
110
134
|
|
|
111
135
|
**Lazy overlay creation** — No memory overhead until the first write in a transaction.
|
|
112
136
|
|
|
113
|
-
**Transparent hook forwarding** — `IsolationModule` is a wrapper, so the engine/planner/lens machinery reaches *it* (the registered module) rather than the underlying. Optional `VirtualTableModule` hooks whose behavior is isolation-transparent are forwarded straight through to the underlying: `getMappingAdvertisements` (decomposition shape), `getBestAccessPlan` (index awareness), the `beginSchemaBatch` / `endSchemaBatch` APPLY SCHEMA batch hooks (single-substrate-commit batching of migration DDL), `assertCatalogObjectPersistable` (the pre-flight veto that lets a persisting module refuse a view / materialized view whose catalog entry it could not durably write
|
|
137
|
+
**Transparent hook forwarding** — `IsolationModule` is a wrapper, so the engine/planner/lens machinery reaches *it* (the registered module) rather than the underlying. Optional `VirtualTableModule` hooks whose behavior is isolation-transparent are forwarded straight through to the underlying: `getMappingAdvertisements` (decomposition shape), `getBestAccessPlan` (index awareness), the `beginSchemaBatch` / `endSchemaBatch` APPLY SCHEMA batch hooks (single-substrate-commit batching of migration DDL), `onRegister` (the registration-time handoff of the `Database`, which a persisting underlying uses to subscribe to the schema-change notifier before any statement runs — which `Database` a module serves is isolation-transparent, and without the forward a wrapped store would fall back to subscribing at its first table hook and silently drop a view created ahead of that), `assertCatalogObjectPersistable` (the pre-flight veto that lets a persisting module refuse a view / materialized view — or a table a RENAME would rewrite — whose catalog entry it could not durably write; whether a definition is encodable is a property of the definition text, not of the overlay, and without the forward a wrapped store would silently keep dropping such views on reopen. The table arm also needs the underlying module to recognise its OWN tables through this wrapper, since a wrapped table's `vtabModule` is the `IsolationModule`: it does so by matching `underlying`), and `getBackingHost` (the materialized-view backing-host capability — assigned in the constructor only when the underlying implements it, so method *presence* mirrors the underlying; backing writes are privileged and bypass the per-connection overlay entirely, making the underlying host's pending state the right surface). Hooks whose underlying value would *misdescribe* the wrapped behavior are intentionally **not** forwarded: `getCapabilities` is augmented with `isolation`/`savepoints` rather than passed through verbatim; `supports` (full-query push-down) is suppressed so the overlay always sees every row to merge; and `concurrencyMode` / `expectedLatencyMs` are derived rather than passed through verbatim: `concurrencyMode` is the weaker of the underlying and overlay modes, capped at `reentrant-reads` (the wrapper's own write path mutates shared overlay-merge state non-atomically, so it is never `fully-reentrant`), while `expectedLatencyMs` forwards the underlying's hint (defaulting to `0` when the underlying declares none).
|
|
114
138
|
|
|
115
139
|
**Atomic ALTER (issuer-faithful) + cross-connection poison** — DDL through Quereus is not transaction-scoped and the shared underlying base auto-commits its mutation immediately, so a half-applied ALTER cannot be rolled back. The blast radius is isolation-faithful — an ALTER never depends on another connection's uncommitted data:
|
|
116
140
|
|
|
117
141
|
- **Issuer's own overlay** — `IsolationModule.alterTable` dry-run **validates the issuing connection's own affected overlay's backfill** (per-row `NOT NULL` checks — for a new `ADD COLUMN ... NOT NULL` and for tightening an existing column with `ALTER COLUMN ... SET NOT NULL` — and the tombstone-present guard) *before* mutating the underlying. A rejection fires while the underlying, the schema catalog, and every overlay are still untouched, so for the issuer the ALTER either fails clean or fully applies — base and catalog never diverge. (The issuer staged both the data and the DDL, so rejecting up front is the least-surprising behavior.)
|
|
142
|
+
- **`ALTER TABLE ... ALTER COLUMN ... SET COLLATE` on a primary-key column** — the change re-keys the table, so two staged rows can land on one new key. A deletion marker sharing its new key with a staged live row is that row's before-image (delete `'A'`, insert `'a'`, then switch to `NOCASE`), so the marker is discarded and the ALTER succeeds; two staged **live** rows on one key are a real duplicate and are refused. A shape the re-keyed overlay cannot physically hold — a savepoint that could restore both a marker and its replacement at one key — is refused as retryable (`BUSY`), the same answer a plain non-isolated table gives. Every refusal is surfaced *before* the shared underlying is touched, so the transaction survives intact.
|
|
143
|
+
- **`ALTER TABLE ... ALTER PRIMARY KEY`** — the one change no overlay can follow: an overlay's staged rows (and its deletion markers) are keyed by the *old* primary key. A connection that has uncommitted changes staged for the table is therefore rejected with `BUSY` (retryable — not `UNSUPPORTED`, which the engine would treat as "fall back to a shadow-table rebuild" and silently lose the staged writes) *before* the underlying is touched — commit or roll back first, then retry. Another connection's overlay holding staged rows is poisoned; one holding nothing is swapped for an empty overlay under the new key. (Both bundled underlyings — `MemoryTableModule` and the store — re-key in place, so these paths arise under either. The refusal above is about the *overlay's* representation, not the underlying's capability.)
|
|
118
144
|
- **Another connection's overlay** — the shared underlying and the catalog change regardless of any *other* connection's uncommitted state. A foreign overlay that *can* migrate is carried forward as usual (a staged `NULL` is backfilled when a usable literal `DEFAULT` exists); one that *cannot* (its staged row can't satisfy the new or newly-`NOT NULL` column) is left in place and marked **poisoned**. Its owning connection then raises a `CONSTRAINT` error the next time it reads (merged), writes, or commits that table, and recovers by rolling back (which discards the overlay and its poison). A committed-snapshot (`committed.<table>`) read bypasses the overlay and keeps working. A layer-invariant violation (e.g. a missing tombstone column, `INTERNAL`) still rethrows loud for everyone rather than poisoning.
|
|
145
|
+
- **`ALTER TABLE ... ADD COLUMN` position** — `SchemaChangeInfo`'s optional `insertAtIndex` names a slot instead of the end (there is no SQL syntax for it; only an in-process module wrapper can set one). A caller-named position is honored in the overlay too, for both the issuer's own overlay and every migrated foreign one; with none supplied, the new column lands ahead of the overlay's own deletion-marker column so that column stays last.
|
|
119
146
|
|
|
120
147
|
**Configurable overlay module** — Use memory for fast transactions, or persistent storage for large transactions:
|
|
121
148
|
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { Database, TableSchema, SchemaChangeInfo, Row, SqlValue } from '@quereus/quereus';
|
|
2
|
+
import type { ConnectionOverlayState, Predicate } from './isolation-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Carrying a connection's open staging area (its "overlay") forward across an ALTER TABLE.
|
|
5
|
+
*
|
|
6
|
+
* When one connection runs ALTER TABLE, the shared underlying table mutates immediately and
|
|
7
|
+
* irreversibly. Every open overlay therefore has to be judged BEFORE that mutation and then
|
|
8
|
+
* reshaped to match it, or else marked unusable ("poisoned") for its owning connection. This
|
|
9
|
+
* module owns that whole subject; `IsolationModule.alterTable` is its only caller and stays
|
|
10
|
+
* responsible for the surrounding lifecycle — which overlays are in scope, how a failure is
|
|
11
|
+
* routed (issuer vs foreign), and the ALTER PRIMARY KEY overlay swap.
|
|
12
|
+
*
|
|
13
|
+
* The flow, in the order `alterTable` runs it:
|
|
14
|
+
*
|
|
15
|
+
* 1. {@link assertColumnNameNotTombstone} — reject a column name that would collide with the
|
|
16
|
+
* overlay's private deletion-marker flag, before anything mutates.
|
|
17
|
+
* 2. {@link deriveAlterMigrationPlan} — precompute every per-change-type constant the later
|
|
18
|
+
* passes need, from the PRE-alter schema (so it is valid before the underlying mutates).
|
|
19
|
+
* 3. {@link validateOverlayMigration} — dry-run one overlay's fallible work without mutating
|
|
20
|
+
* it. Runs for the issuer before `underlying.alterTable` (atomic abort) and per foreign
|
|
21
|
+
* overlay after it (a data rejection becomes poison, via {@link buildAlterPoisonMessage}).
|
|
22
|
+
* 4. {@link planPkRekeyMarkerDrops} / {@link applyPkRekeyMarkerDrops} /
|
|
23
|
+
* {@link reinsertPkRekeyMarkers} — the primary-key re-key marker handling, which the issuer
|
|
24
|
+
* path also needs BEFORE the underlying mutates so the overlay's own `alterSchema` can be
|
|
25
|
+
* pre-flighted against the exact state the migrate step will see.
|
|
26
|
+
* 5. {@link migrateOverlayForward} — reshape one overlay to the post-alter schema, in place.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* What the ALTER-migration helpers need back from the module that owns the overlays.
|
|
30
|
+
* `IsolationModule` satisfies it structurally; nothing here reaches for more of the class
|
|
31
|
+
* than these three members.
|
|
32
|
+
*/
|
|
33
|
+
export interface AlterMigrationHost {
|
|
34
|
+
/** Name of the extra column every overlay carries as its deletion-marker flag. */
|
|
35
|
+
readonly tombstoneColumn: string;
|
|
36
|
+
/** `<base predicate, rescoped to the overlay> AND <tombstone> = 0`. */
|
|
37
|
+
overlayPredicate(base: Predicate | undefined, baseName: string, overlayName: string): Predicate;
|
|
38
|
+
/** Case-insensitive "does `schema` declare an index named `indexName`". */
|
|
39
|
+
schemaHasIndex(schema: TableSchema, indexName: string): boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Per-ALTER constants for backfilling a freshly added column into staged overlay
|
|
43
|
+
* rows. Precomputed once per `addColumn` (see `deriveAddColumnBackfill`) so the
|
|
44
|
+
* per-row loop only branches on tombstone / evaluator / literal.
|
|
45
|
+
*/
|
|
46
|
+
export interface AddColumnBackfillContext {
|
|
47
|
+
/** The folded literal DEFAULT, or `null` when there is no usable literal default. */
|
|
48
|
+
foldedDefault: SqlValue;
|
|
49
|
+
/** Per-row evaluator for a non-foldable `new.<col>` default; absent for a literal default. */
|
|
50
|
+
evaluator?: (row: Row) => SqlValue | Promise<SqlValue>;
|
|
51
|
+
/** Whether the new column is NOT NULL (enforced on both the evaluator and folded-default paths). */
|
|
52
|
+
newColNotNull: boolean;
|
|
53
|
+
/** New column name, for the NOT NULL error message. */
|
|
54
|
+
newColName: string;
|
|
55
|
+
/** Owning table name, for the NOT NULL error message. */
|
|
56
|
+
tableName: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Per-ALTER constants for an `alter column … set not null` overlay backfill (see
|
|
60
|
+
* `deriveSetNotNullBackfill`). Precomputed once so the per-row validate/backfill loops only
|
|
61
|
+
* branch on tombstone / has-default. Present only for a NOT NULL *tightening* (`setNotNull: true`)
|
|
62
|
+
* with staged overlays to carry forward.
|
|
63
|
+
*
|
|
64
|
+
* `alter column … set data type` rides the same derive → validate seam via
|
|
65
|
+
* {@link SetDataTypeConvertContext}. The two are mutually exclusive: the runtime rejects a
|
|
66
|
+
* multi-attribute `alter column` before it reaches this module.
|
|
67
|
+
*/
|
|
68
|
+
export interface SetNotNullBackfillContext {
|
|
69
|
+
/** Zero-based index of the now-NOT-NULL column in the overlay's data columns. */
|
|
70
|
+
colIndex: number;
|
|
71
|
+
/** The folded literal DEFAULT used to backfill staged NULLs; meaningful only when `hasDefault`. */
|
|
72
|
+
foldedDefault: SqlValue;
|
|
73
|
+
/** Whether a usable literal DEFAULT exists — backfill when true, reject the staged NULL when false. */
|
|
74
|
+
hasDefault: boolean;
|
|
75
|
+
/** Column name, for the CONSTRAINT message. */
|
|
76
|
+
colName: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Per-ALTER constants for an `alter column … set data type` overlay pre-validation (see
|
|
80
|
+
* {@link deriveSetDataTypeConvert}). Present only when the retype actually rewrites values (the
|
|
81
|
+
* new physical type differs from the old) and there are staged overlays to judge — a
|
|
82
|
+
* metadata-only retype leaves every staged value as it stands.
|
|
83
|
+
*
|
|
84
|
+
* The conversion itself is done by the overlay module when the retype is forwarded through its
|
|
85
|
+
* `alterSchema` ({@link forwardAlterColumnToOverlay}); this context exists so
|
|
86
|
+
* {@link validateOverlayMigration} can prove every staged value convertible FIRST — atomically
|
|
87
|
+
* for the issuer, and as the poison-vs-forward gate for a foreign overlay.
|
|
88
|
+
*/
|
|
89
|
+
export interface SetDataTypeConvertContext {
|
|
90
|
+
/** Zero-based index of the retyped column in the overlay's data columns. */
|
|
91
|
+
colIndex: number;
|
|
92
|
+
/** Per-value conversion; throws MISMATCH exactly as the underlying's does. */
|
|
93
|
+
convert: (v: SqlValue) => SqlValue;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Per-ALTER constants for an `alter column … set collate` that re-keys the PRIMARY KEY (see
|
|
97
|
+
* {@link derivePkRekey}). Present only when the normalized collation actually changes on a PK
|
|
98
|
+
* member column and there are staged overlays to carry — a non-PK collate change, or a re-declare
|
|
99
|
+
* of the column's current collation, re-keys nothing. A retype of a PK column is rejected
|
|
100
|
+
* upstream, so `setCollation` is the only ALTER COLUMN attribute that re-keys.
|
|
101
|
+
*
|
|
102
|
+
* Under the re-keyed primary key, a staged deletion marker and a staged live row that land on
|
|
103
|
+
* one key are the same logical row's before and after — not two rows — so
|
|
104
|
+
* {@link validateOverlayMigration} accepts the pair and {@link dropCollapsedPkRekeyMarkers}
|
|
105
|
+
* discards the marker before the re-key is forwarded to the overlay. Two live rows on one key
|
|
106
|
+
* stay a real duplicate and are refused. This is the isolation-layer lift of the rule
|
|
107
|
+
* `TransactionLayer.rekeyPrimaryKey` applies to its own write log one level down: a deletion
|
|
108
|
+
* whose key an upsert now occupies is dropped.
|
|
109
|
+
*/
|
|
110
|
+
export interface PkRekeyContext {
|
|
111
|
+
/** Overlay positions of the PRIMARY KEY columns (identical to the base's — the tombstone flag sits after them). */
|
|
112
|
+
pkIndices: readonly number[];
|
|
113
|
+
/** Serializes a PK tuple under the POST-change collation — two old keys that serialize alike collapse. */
|
|
114
|
+
keyOf: (pk: readonly SqlValue[]) => string;
|
|
115
|
+
/** User-facing table name, for the CONSTRAINT message. */
|
|
116
|
+
tableName: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Every per-change-type constant one ALTER's overlay migration needs, derived once from the
|
|
120
|
+
* PRE-alter schema and shared by the validate and migrate passes so the two cannot drift.
|
|
121
|
+
*
|
|
122
|
+
* At most one field is ever populated — each is gated on a different change type (or, for the
|
|
123
|
+
* three `alterColumn` arms, a different attribute, and the runtime admits exactly one attribute
|
|
124
|
+
* per ALTER COLUMN). Bundled into one object rather than threaded as one parameter per
|
|
125
|
+
* attribute, which is what the cluster did while it lived on `IsolationModule`.
|
|
126
|
+
*/
|
|
127
|
+
export interface AlterMigrationPlan {
|
|
128
|
+
addColumn?: AddColumnBackfillContext;
|
|
129
|
+
setNotNull?: SetNotNullBackfillContext;
|
|
130
|
+
setDataType?: SetDataTypeConvertContext;
|
|
131
|
+
pkRekey?: PkRekeyContext;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Rejects an ADD / RENAME COLUMN that would give a base column the same name as the
|
|
135
|
+
* overlay's private tombstone flag, BEFORE `underlying.alterTable` runs.
|
|
136
|
+
*
|
|
137
|
+
* Every overlay carries one extra column named `AlterMigrationHost.tombstoneColumn`, so a base
|
|
138
|
+
* column of that name collides with it. Forwarding the change to an open overlay makes the
|
|
139
|
+
* overlay module raise a duplicate-name `ERROR`, which is not a data condition and so
|
|
140
|
+
* rethrows out of `IsolationModule.applyInPlaceOverlayChange` — after the underlying has already
|
|
141
|
+
* (irreversibly) applied. The catalog then keeps the pre-alter column set while the base
|
|
142
|
+
* holds the new one, and the next write to the table lands values against the wrong
|
|
143
|
+
* columns. Rejecting up front keeps the atomic-abort guarantee `IsolationModule.alterTable`
|
|
144
|
+
* documents.
|
|
145
|
+
*
|
|
146
|
+
* NOTE: the check is unconditional — not gated on an overlay existing — so the answer
|
|
147
|
+
* does not depend on whether some connection happens to have a transaction open. That
|
|
148
|
+
* also stops a table from acquiring the colliding name while idle and hitting
|
|
149
|
+
* `IsolationModule.createOverlaySchema`'s (silent) duplicate at the next BEGIN. A CREATE TABLE
|
|
150
|
+
* declaring the name still gets that duplicate — tracked separately.
|
|
151
|
+
*/
|
|
152
|
+
export declare function assertColumnNameNotTombstone(host: AlterMigrationHost, schemaName: string, tableName: string, change: SchemaChangeInfo): void;
|
|
153
|
+
/**
|
|
154
|
+
* Builds the poison message stamped onto a foreign overlay whose backfill could not
|
|
155
|
+
* satisfy a cross-connection ALTER (see `IsolationModule.alterTable` tier 3). Names the
|
|
156
|
+
* schema.table and the offending column so the owning connection's eventual
|
|
157
|
+
* read/write/commit error is self-explanatory. Poison arises on the addColumn NOT NULL
|
|
158
|
+
* path (a new NOT-NULL column with no usable default), the `set not null` tightening
|
|
159
|
+
* path (a staged NULL with no usable default), the `set data type` path (a staged value
|
|
160
|
+
* that cannot be converted to the new type), and the primary-key `set collate` path (two
|
|
161
|
+
* staged live rows colliding under the new collation); other change types never reach
|
|
162
|
+
* here but are handled defensively.
|
|
163
|
+
*/
|
|
164
|
+
export declare function buildAlterPoisonMessage(schemaName: string, tableName: string, change: SchemaChangeInfo): string;
|
|
165
|
+
/**
|
|
166
|
+
* Precomputes every per-change-type constant this ALTER's overlay migration needs, from the
|
|
167
|
+
* PRE-alter schema — so the whole plan is valid BEFORE the irreversible `underlying.alterTable`
|
|
168
|
+
* and the same plan drives the migration after it.
|
|
169
|
+
*
|
|
170
|
+
* `toMigrate` is the overlays that will actually be carried forward (issuer-own first). The
|
|
171
|
+
* attribute contexts are probed from `toMigrate[0]`'s schema, never from a skipped poisoned
|
|
172
|
+
* overlay whose schema may be a stale pre-alter layout; with no overlays to migrate there is
|
|
173
|
+
* nothing to backfill, convert or re-key, so those arms stay undefined.
|
|
174
|
+
*/
|
|
175
|
+
export declare function deriveAlterMigrationPlan(change: SchemaChangeInfo, db: Database, tableName: string, toMigrate: readonly [string, ConnectionOverlayState][]): AlterMigrationPlan;
|
|
176
|
+
/**
|
|
177
|
+
* Dry-runs an overlay's ALTER migration-fallible work without mutating anything,
|
|
178
|
+
* so the caller can run it for every affected overlay BEFORE the irreversible
|
|
179
|
+
* `underlying.alterTable` (see `IsolationModule.alterTable`). It exercises the EXACT code
|
|
180
|
+
* paths the real migration uses — the tombstone-present guard and, for addColumn,
|
|
181
|
+
* `computeAddColumnValue` per staged row — so a dry-run pass and the subsequent
|
|
182
|
+
* migrate pass cannot diverge:
|
|
183
|
+
*
|
|
184
|
+
* - A clean overlay (`!hasChanges`) stages no rows, so there is nothing to validate.
|
|
185
|
+
* - A missing tombstone column throws INTERNAL here, before the underlying is touched.
|
|
186
|
+
* - For addColumn, each staged row runs through {@link computeAddColumnValue}, which owns
|
|
187
|
+
* both NOT NULL reject sources and short-circuits tombstone rows; a violation throws
|
|
188
|
+
* CONSTRAINT here, atomically. Computed values are discarded.
|
|
189
|
+
*
|
|
190
|
+
* For `set not null` with NO usable DEFAULT (`plan.setNotNull.hasDefault === false`), a staged
|
|
191
|
+
* non-tombstone NULL at the now-NOT-NULL column throws CONSTRAINT here — for the issuer this
|
|
192
|
+
* aborts atomically before the underlying mutates; for a foreign overlay the caller maps it to
|
|
193
|
+
* poison. With a usable DEFAULT the staged NULLs are backfilled by
|
|
194
|
+
* {@link backfillStagedNotNull}, so nothing is rejected here.
|
|
195
|
+
*
|
|
196
|
+
* For `set data type` (`plan.setDataType`), every staged non-NULL value is run through the
|
|
197
|
+
* conversion and the result discarded, so an unconvertible one throws MISMATCH here. For the
|
|
198
|
+
* ISSUER this is belt-and-braces — the underlying's own pre-mutation pass walks the same rows
|
|
199
|
+
* via `IsolationModule.issuerEffectiveRows` — EXCEPT when an outer wrapper supplied `rows`, in
|
|
200
|
+
* which case this is the only check. For a FOREIGN overlay it is the only pass that ever sees
|
|
201
|
+
* those rows, and it must run before the migration so a failure becomes poison rather than a
|
|
202
|
+
* half-migrated overlay.
|
|
203
|
+
*
|
|
204
|
+
* For a primary-key re-keying `set collate` (`plan.pkRekey`), the staged rows are grouped by
|
|
205
|
+
* their POST-change key: a deletion marker sharing a key with one live row is that row's
|
|
206
|
+
* before-image (the migrate step discards it — {@link dropCollapsedPkRekeyMarkers}), and
|
|
207
|
+
* markers alone on one key are one deletion — neither is a duplicate. Two LIVE rows on one
|
|
208
|
+
* key ARE, so that throws CONSTRAINT here, mirroring the underlying's own collision message.
|
|
209
|
+
* For the ISSUER this is belt-and-braces (the underlying's pre-mutation pass judges the same
|
|
210
|
+
* staged rows via `IsolationModule.issuerEffectiveRows`, though this check fires first); for a
|
|
211
|
+
* FOREIGN overlay it is the only pass that sees those rows, and its throw becomes poison.
|
|
212
|
+
*
|
|
213
|
+
* Non-addColumn / non-tightening / non-retype / non-re-key changes (an empty `plan`) only run
|
|
214
|
+
* the tombstone guard; their row translation appends/removes nothing fallible on data grounds.
|
|
215
|
+
*/
|
|
216
|
+
export declare function validateOverlayMigration(host: AlterMigrationHost, oldState: ConnectionOverlayState, plan: AlterMigrationPlan): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Carries ONE already-open overlay to the post-alter schema, IN PLACE — through the overlay's
|
|
219
|
+
* own `alterSchema` / `createIndex` / ordinary writes — so the overlay's layer chain and
|
|
220
|
+
* savepoint snapshots survive the ALTER and `rollback to savepoint` keeps distinguishing rows
|
|
221
|
+
* staged before the savepoint from rows staged after it (see
|
|
222
|
+
* `IsolationModule.applyIndexChangeToOverlays` for why the old rebuild — copying staged rows
|
|
223
|
+
* into a fresh staging table — destroyed that distinction). Per change type:
|
|
224
|
+
*
|
|
225
|
+
* - ADD / DROP / RENAME COLUMN: {@link forwardColumnShapeToOverlay}.
|
|
226
|
+
* - ALTER COLUMN: {@link forwardAlterColumnToOverlay}.
|
|
227
|
+
* - ADD CONSTRAINT: {@link forwardAddConstraintToOverlay}.
|
|
228
|
+
* - DROP / RENAME CONSTRAINT: {@link forwardConstraintNameChangeToOverlay}.
|
|
229
|
+
*
|
|
230
|
+
* ALTER PRIMARY KEY is excluded from the parameter type: no overlay can follow it in place, so
|
|
231
|
+
* `IsolationModule.replaceOverlayForPrimaryKeyChange` handles that arm instead.
|
|
232
|
+
*
|
|
233
|
+
* The caller wraps this in `IsolationModule.applyInPlaceOverlayChange`, which routes a
|
|
234
|
+
* CONSTRAINT/BUSY failure by who owns the overlay (issuer → INTERNAL drift error; foreign →
|
|
235
|
+
* poison) and rethrows everything else.
|
|
236
|
+
*/
|
|
237
|
+
export declare function migrateOverlayForward(host: AlterMigrationHost, overlayState: ConnectionOverlayState, change: Exclude<SchemaChangeInfo, {
|
|
238
|
+
type: 'alterPrimaryKey';
|
|
239
|
+
}>, plan: AlterMigrationPlan, updatedSchema: TableSchema): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* The whole overlay rows of every deletion marker {@link dropCollapsedPkRekeyMarkers} will
|
|
242
|
+
* discard, computed without mutating anything. Re-planning after the drops finds nothing
|
|
243
|
+
* (each group then holds at most one marker and no marker beside a live row), so the
|
|
244
|
+
* issuer path — which drops in tier 2 for the pre-flight — can safely run the shared
|
|
245
|
+
* migrate-step drop again as a no-op.
|
|
246
|
+
*/
|
|
247
|
+
export declare function planPkRekeyMarkerDrops(host: AlterMigrationHost, overlayState: ConnectionOverlayState, ctx: PkRekeyContext): Promise<Row[]>;
|
|
248
|
+
/** Deletes each planned marker by its OLD primary key through the overlay's ordinary write path. */
|
|
249
|
+
export declare function applyPkRekeyMarkerDrops(overlayState: ConnectionOverlayState, ctx: PkRekeyContext, toDrop: readonly Row[]): Promise<void>;
|
|
250
|
+
/**
|
|
251
|
+
* Undoes {@link applyPkRekeyMarkerDrops} after a refusal that aborts the whole ALTER —
|
|
252
|
+
* the overlay pre-flight's or the underlying's (see the tier-2 re-key block in
|
|
253
|
+
* `IsolationModule.alterTable`). Reinserts each marker row verbatim: a marker minted for a
|
|
254
|
+
* committed row carries NULL at every non-key data column, but one made by deleting a row this
|
|
255
|
+
* transaction had already staged keeps that row's values (the convert-to-tombstone branch
|
|
256
|
+
* of `IsolatedTable.update`), so a reconstructed-from-PK row would not restore what was
|
|
257
|
+
* dropped. The reinserts land in the same savepoint frame the drops did, so the pair nets
|
|
258
|
+
* to nothing.
|
|
259
|
+
*/
|
|
260
|
+
export declare function reinsertPkRekeyMarkers(overlayState: ConnectionOverlayState, droppedRows: readonly Row[]): Promise<void>;
|
|
261
|
+
//# sourceMappingURL=alter-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alter-migration.d.ts","sourceRoot":"","sources":["../../src/alter-migration.ts"],"names":[],"mappings":"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"}
|