@quereus/isolation 3.3.0 → 4.1.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 +7 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/isolated-table.d.ts +83 -0
- package/dist/src/isolated-table.d.ts.map +1 -1
- package/dist/src/isolated-table.js +248 -54
- package/dist/src/isolated-table.js.map +1 -1
- package/dist/src/isolation-module.d.ts +265 -2
- package/dist/src/isolation-module.d.ts.map +1 -1
- package/dist/src/isolation-module.js +459 -17
- package/dist/src/isolation-module.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -86,6 +86,13 @@ The isolation layer operates at the **row level**, merging query results from tw
|
|
|
86
86
|
|
|
87
87
|
**Lazy overlay creation** — No memory overhead until the first write in a transaction.
|
|
88
88
|
|
|
89
|
+
**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), 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).
|
|
90
|
+
|
|
91
|
+
**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:
|
|
92
|
+
|
|
93
|
+
- **Issuer's own overlay** — `IsolationModule.alterTable` dry-run **validates the issuing connection's own affected overlay's backfill** (per-row `NOT NULL` checks 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 an ADD COLUMN 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.)
|
|
94
|
+
- **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; one that *cannot* (its staged row can't satisfy the new `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.
|
|
95
|
+
|
|
89
96
|
**Configurable overlay module** — Use memory for fast transactions, or persistent storage for large transactions:
|
|
90
97
|
|
|
91
98
|
```typescript
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { MergeEntry, MergeConfig, PKComparator, PKExtractor, SortKeyComparator, SortKeyExtractor } from './merge-types.js';
|
|
2
2
|
export { mergeStreams, createMergeEntry, createTombstone } from './merge-iterator.js';
|
|
3
3
|
export type { IsolationModuleConfig } from './isolation-types.js';
|
|
4
|
-
export type { UnderlyingTableState } from './isolation-module.js';
|
|
4
|
+
export type { UnderlyingTableState, ConnectionOverlayState } from './isolation-module.js';
|
|
5
5
|
export { IsolationModule } from './isolation-module.js';
|
|
6
6
|
export { IsolatedTable } from './isolated-table.js';
|
|
7
7
|
export { IsolatedConnection } from './isolated-connection.js';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChI,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtF,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChI,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtF,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAG1F,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -46,6 +46,16 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
46
46
|
* Gets the connection-scoped overlay state, or undefined if no overlay exists yet.
|
|
47
47
|
*/
|
|
48
48
|
private getOverlayState;
|
|
49
|
+
/**
|
|
50
|
+
* Throws if this connection's overlay was poisoned by a cross-connection ALTER
|
|
51
|
+
* (see `IsolationModule.alterTable`). A poisoned overlay still holds rows in the
|
|
52
|
+
* PRE-alter column layout, so it can neither be merged into a read nor flushed to
|
|
53
|
+
* the now-altered underlying. Called at the data-op chokepoints (write, the merged
|
|
54
|
+
* read branch, and the commit flush) — never on the committed-snapshot read path,
|
|
55
|
+
* which bypasses the overlay entirely and stays safe. The connection recovers by
|
|
56
|
+
* rolling back, which discards the overlay (and its poison).
|
|
57
|
+
*/
|
|
58
|
+
private assertOverlayUsable;
|
|
49
59
|
/**
|
|
50
60
|
* Gets the overlay table, or undefined if no overlay exists yet.
|
|
51
61
|
*/
|
|
@@ -77,8 +87,33 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
77
87
|
* IsolatedConnection, causing DeferredConstraintQueue.findConnection() to find multiple
|
|
78
88
|
* covering candidates and throw. We therefore reuse the first covering connection
|
|
79
89
|
* already registered for this table.
|
|
90
|
+
*
|
|
91
|
+
* Concurrent first-reads coalesce through the module-level in-flight memo
|
|
92
|
+
* (`IsolationModule.coalesceConnectionBuild`, keyed per db+table) rather than a
|
|
93
|
+
* per-instance one: the runtime connects a FRESH `IsolatedTable` per scan, so
|
|
94
|
+
* two concurrent scans of one table land on distinct instances and only a memo
|
|
95
|
+
* that spans all wrappers for the (db, table) can collapse them onto one
|
|
96
|
+
* registered covering connection. The resolved connection is cached in
|
|
97
|
+
* `registeredConnection` so subsequent reads on THIS instance fast-path — this
|
|
98
|
+
* is the only place that field is set when the build was coalesced onto another
|
|
99
|
+
* instance's in-flight promise (that instance's `buildConnection` ran, ours did
|
|
100
|
+
* not).
|
|
80
101
|
*/
|
|
81
102
|
private ensureConnection;
|
|
103
|
+
/**
|
|
104
|
+
* Builds (or reuses) the registered connection for this table. Always called
|
|
105
|
+
* through `IsolationModule.coalesceConnectionBuild` so concurrent callers —
|
|
106
|
+
* across all `IsolatedTable` instances for this (db, table) — share one build.
|
|
107
|
+
*
|
|
108
|
+
* The covering-reuse check stays INSIDE this coalesced body so a connection
|
|
109
|
+
* registered by another instance between calls (e.g. after the memo cleared on
|
|
110
|
+
* settle) is still picked up by the next read.
|
|
111
|
+
* `registeredConnection` is assigned only on the success paths (covering reuse
|
|
112
|
+
* or a completed `registerConnection`); a thrown `registerConnection` /
|
|
113
|
+
* overlay `createConnection` leaves it null and rejects the in-flight promise,
|
|
114
|
+
* which `coalesceConnectionBuild` clears so a later read rebuilds.
|
|
115
|
+
*/
|
|
116
|
+
private buildConnection;
|
|
82
117
|
/**
|
|
83
118
|
* Creates a new isolated connection for transaction support.
|
|
84
119
|
* The connection includes this table as a callback so commit/rollback
|
|
@@ -196,6 +231,19 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
196
231
|
* this overrides only when we have a store-side displacement to report.
|
|
197
232
|
*/
|
|
198
233
|
private attachReplacedUnderlying;
|
|
234
|
+
/**
|
|
235
|
+
* Surface internal REPLACE evictions of **underlying** rows (rows at OTHER PKs the
|
|
236
|
+
* isolation layer's own merged-view detection displaced via a tombstone) as
|
|
237
|
+
* `evictedRows`, so the DML executor runs the full delete pipeline for each. The
|
|
238
|
+
* passed rows come from `findMergedUniqueConflict`, which yields live underlying
|
|
239
|
+
* rows already in user-facing schema, so the tombstone-column slice is a defensive
|
|
240
|
+
* no-op. Preserves any `replacedRow` already attached and **merges** with any
|
|
241
|
+
* `evictedRows` already present (overlay-internal evictions propagated by
|
|
242
|
+
* {@link stripTombstoneFromResult}) — the two eviction sources are disjoint per
|
|
243
|
+
* write today (the conflicting row lives in the overlay XOR the underlying) but
|
|
244
|
+
* merging keeps both correct if that ever changes.
|
|
245
|
+
*/
|
|
246
|
+
private attachEvicted;
|
|
199
247
|
/**
|
|
200
248
|
* Gets a row from the overlay by primary key using O(log n) point lookup.
|
|
201
249
|
*/
|
|
@@ -217,6 +265,24 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
217
265
|
private compileFor;
|
|
218
266
|
private keysEqual;
|
|
219
267
|
private getUnderlyingRow;
|
|
268
|
+
/**
|
|
269
|
+
* Writes a relocated row at `newPK` in the overlay for a PK-changing UPDATE.
|
|
270
|
+
*
|
|
271
|
+
* If the overlay already holds a **tombstone** at newPK (a PK that was freed
|
|
272
|
+
* earlier in this same transaction), overwrite it via `operation: 'update'`
|
|
273
|
+
* rather than `operation: 'insert'` — the overlay is itself a StoreTable whose
|
|
274
|
+
* insert path treats a tombstone row at the target key as a live PK conflict
|
|
275
|
+
* and would throw `_overlay_<table> PK`. Overwriting the tombstone is the
|
|
276
|
+
* logical reuse of the freed PK. This mirrors the plain-INSERT tombstone
|
|
277
|
+
* conversion (~the `existingRow[tombstoneIndex] === 1` branch in `update`).
|
|
278
|
+
*
|
|
279
|
+
* A **live** overlay row at newPK is already rejected upstream by
|
|
280
|
+
* {@link checkMergedPKConflict} (which returns its terminating constraint
|
|
281
|
+
* result) and by the existing-overlay-row PK-conflict branch, so reaching here
|
|
282
|
+
* with a non-tombstone overlay row should not happen; if it ever does, fall
|
|
283
|
+
* through to insert and let the overlay enforce the genuine conflict.
|
|
284
|
+
*/
|
|
285
|
+
private writeRelocatedRow;
|
|
220
286
|
private insertTombstoneForPK;
|
|
221
287
|
/**
|
|
222
288
|
* Checks if newPK conflicts with an underlying row not already shadowed in the overlay.
|
|
@@ -240,6 +306,12 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
240
306
|
/**
|
|
241
307
|
* Checks all non-PK UNIQUE constraints against the merged view.
|
|
242
308
|
* Returns null when all pass or REPLACE evictions succeed.
|
|
309
|
+
*
|
|
310
|
+
* A REPLACE eviction tombstones the conflicting live merged row in the overlay
|
|
311
|
+
* (so the underlying row is deleted at flush) and pushes that row onto `evicted`
|
|
312
|
+
* — surfaced to the DML executor via `evictedRows` so it runs the full delete
|
|
313
|
+
* pipeline (change-tracking, FK cascade, auto-events, and the row-time
|
|
314
|
+
* covering-MV backing maintenance the isolation layer otherwise never drives).
|
|
243
315
|
*/
|
|
244
316
|
private checkMergedUniqueConstraints;
|
|
245
317
|
begin(): Promise<void>;
|
|
@@ -259,6 +331,17 @@ export declare class IsolatedTable extends VirtualTable implements IsolatedTable
|
|
|
259
331
|
* transaction rollbacks.
|
|
260
332
|
*/
|
|
261
333
|
private flushOverlayToUnderlying;
|
|
334
|
+
/**
|
|
335
|
+
* Asserts that an underlying write performed during the commit flush succeeded.
|
|
336
|
+
*
|
|
337
|
+
* The overlay's merged-view pre-checks ({@link checkMergedPKConflict} /
|
|
338
|
+
* {@link checkMergedUniqueConstraints}) resolve every constraint before commit, so a
|
|
339
|
+
* `constraint` result returned here means a real invariant was violated *after* those
|
|
340
|
+
* checks. Historically this result was discarded, silently dropping the colliding
|
|
341
|
+
* write and surfacing as data corruption. Convert it into a loud INTERNAL error; the
|
|
342
|
+
* caller's try/catch rolls back the underlying flush transaction and rethrows.
|
|
343
|
+
*/
|
|
344
|
+
private assertFlushWriteOk;
|
|
262
345
|
/**
|
|
263
346
|
* Checks if a row with the given primary key exists in the underlying table.
|
|
264
347
|
* Uses O(log n) point lookup via the PK index.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolated-table.d.ts","sourceRoot":"","sources":["../../src/isolated-table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,YAAY,EAAE,GAAG,EAAY,gBAAgB,IAAI,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAA0D,UAAU,EAAE,sBAAsB,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"isolated-table.d.ts","sourceRoot":"","sources":["../../src/isolated-table.ts"],"names":[],"mappings":"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"}
|