@quereus/isolation 4.3.1 → 4.3.2

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 CHANGED
@@ -4,12 +4,12 @@ Generic transaction isolation layer for Quereus virtual table modules.
4
4
 
5
5
  ## Overview
6
6
 
7
- The `@quereus/isolation` package provides MVCC-style transaction isolation semantics for any Quereus virtual table module. It wraps existing modules to add:
7
+ The `@quereus/isolation` package provides connection-level transaction isolation for any Quereus virtual table module. It wraps existing modules to add:
8
8
 
9
9
  - **Read-your-own-writes** — See uncommitted changes within your transaction
10
- - **Snapshot isolation** — Consistent reads throughout the transaction
10
+ - **Read-committed reads of shared state** — Reads merge against the *live* underlying table, so another connection's committed writes can become visible mid-transaction. This is **not** snapshot isolation — reads are not a stable point-in-time view. A stable snapshot, if needed, is the job of whatever module is layered beneath this one (see [Isolation Level](#isolation-level) below).
11
11
  - **Savepoint support** — Nested transaction control
12
- - **ACID semantics** — Full transaction guarantees
12
+ - **No write-write conflict detection** — Concurrent writers to the same row are not detected; the last connection to flush wins.
13
13
 
14
14
  This allows module authors to focus on storage concerns while getting isolation "for free."
15
15
 
@@ -80,6 +80,26 @@ The isolation layer operates at the **row level**, merging query results from tw
80
80
  └─────────────────────────────────────────────────────────┘
81
81
  ```
82
82
 
83
+ ## Isolation Level
84
+
85
+ The guarantee this layer actually provides is closer to **read-committed with
86
+ read-your-own-writes** than to snapshot isolation:
87
+
88
+ - **Read-your-own-writes** — a connection always sees its own uncommitted overlay
89
+ changes.
90
+ - **Live reads of shared state** — the merged read path queries the *live* underlying
91
+ table on every read, not a point-in-time copy taken at `BEGIN`. If another
92
+ connection commits mid-transaction, the next read in this transaction can observe
93
+ that write. Reads are **not** isolated from concurrent commits.
94
+ - **No write-write conflict detection** — two connections that write the same row in
95
+ overlapping transactions are not detected as conflicting; whichever connection
96
+ flushes (commits) last overwrites the other.
97
+ - **Snapshotting is delegated downward** — if a consumer needs a stable, point-in-time
98
+ view of the data, that guarantee must come from the module wrapped *beneath* this
99
+ layer (the `underlying` module), not from `IsolationModule` itself. A future
100
+ optional snapshotting pass-through module may be added below the isolation layer
101
+ for consumers that need this; it does not exist today.
102
+
83
103
  ### Key Features
84
104
 
85
105
  **Per-connection overlay** — Each database instance gets its own overlay storage, ensuring proper isolation between connections.
@@ -90,8 +110,8 @@ The isolation layer operates at the **row level**, merging query results from tw
90
110
 
91
111
  **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
112
 
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.
113
+ - **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.)
114
+ - **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.
95
115
 
96
116
  **Configurable overlay module** — Use memory for fast transactions, or persistent storage for large transactions:
97
117
 
@@ -0,0 +1,34 @@
1
+ import type { FilterInfo, SqlValue, TableIndexSchema } from '@quereus/quereus';
2
+ import { makeFullScanFilterInfo } from '@quereus/quereus';
3
+ /**
4
+ * Creates a FilterInfo for a full table scan (no constraints).
5
+ *
6
+ * Shared by the overlay-merge read paths, the ALTER/DROP INDEX overlay
7
+ * migrations, and the commit flush — a single definition keeps the several
8
+ * scan sites from drifting. Delegates to the engine's builder so the overlay
9
+ * scans present the same access path the planner would.
10
+ */
11
+ export { makeFullScanFilterInfo };
12
+ /**
13
+ * Creates a FilterInfo for a primary key point lookup (equality on all PK
14
+ * columns). Produces O(log n) lookups instead of O(n) full scans.
15
+ *
16
+ * The overlay always names its primary-key index `_primary_`, so the descriptor is
17
+ * built here rather than resolved from a schema.
18
+ */
19
+ export declare function makePkPointLookupFilter(pkIndices: number[], pk: SqlValue[]): FilterInfo;
20
+ /**
21
+ * Creates a FilterInfo for an equality seek over a secondary `index`, binding each of
22
+ * the index's key columns to `newRow`'s value for that column. Produces an O(log n)
23
+ * index lookup for the isolation layer's UNIQUE-conflict check instead of a full scan.
24
+ *
25
+ * The seek values are read in INDEX-KEY order (`index.columns[i].index`), which need
26
+ * not match the UNIQUE constraint's `columns` order — so read off the index, never
27
+ * assume the two arrays agree.
28
+ *
29
+ * The built `constraints` carry an EQ per key column, so a module that ignores the
30
+ * `idxStr` index hint still applies the equalities as a residual filter rather than
31
+ * returning the whole table.
32
+ */
33
+ export declare function makeSecondaryIndexEqSeekFilter(index: TableIndexSchema, newRow: readonly SqlValue[]): FilterInfo;
34
+ //# sourceMappingURL=filter-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-info.d.ts","sourceRoot":"","sources":["../../src/filter-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAmB,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EAAsB,sBAAsB,EAA6B,MAAM,kBAAkB,CAAC;AAEzG;;;;;;;GAOG;AACH,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAElC;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,UAAU,CAQvF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,UAAU,CAU/G"}
@@ -0,0 +1,51 @@
1
+ import { PRIMARY_INDEX_NAME, makeFullScanFilterInfo, makeIndexEqSeekFilterInfo } from '@quereus/quereus';
2
+ /**
3
+ * Creates a FilterInfo for a full table scan (no constraints).
4
+ *
5
+ * Shared by the overlay-merge read paths, the ALTER/DROP INDEX overlay
6
+ * migrations, and the commit flush — a single definition keeps the several
7
+ * scan sites from drifting. Delegates to the engine's builder so the overlay
8
+ * scans present the same access path the planner would.
9
+ */
10
+ export { makeFullScanFilterInfo };
11
+ /**
12
+ * Creates a FilterInfo for a primary key point lookup (equality on all PK
13
+ * columns). Produces O(log n) lookups instead of O(n) full scans.
14
+ *
15
+ * The overlay always names its primary-key index `_primary_`, so the descriptor is
16
+ * built here rather than resolved from a schema.
17
+ */
18
+ export function makePkPointLookupFilter(pkIndices, pk) {
19
+ const index = {
20
+ name: PRIMARY_INDEX_NAME,
21
+ role: 'primary',
22
+ keyColumns: pkIndices.map(columnIndex => ({ columnIndex, desc: false })),
23
+ unique: true,
24
+ };
25
+ return makeIndexEqSeekFilterInfo(index, pkIndices, pk);
26
+ }
27
+ /**
28
+ * Creates a FilterInfo for an equality seek over a secondary `index`, binding each of
29
+ * the index's key columns to `newRow`'s value for that column. Produces an O(log n)
30
+ * index lookup for the isolation layer's UNIQUE-conflict check instead of a full scan.
31
+ *
32
+ * The seek values are read in INDEX-KEY order (`index.columns[i].index`), which need
33
+ * not match the UNIQUE constraint's `columns` order — so read off the index, never
34
+ * assume the two arrays agree.
35
+ *
36
+ * The built `constraints` carry an EQ per key column, so a module that ignores the
37
+ * `idxStr` index hint still applies the equalities as a residual filter rather than
38
+ * returning the whole table.
39
+ */
40
+ export function makeSecondaryIndexEqSeekFilter(index, newRow) {
41
+ const descriptor = {
42
+ name: index.name,
43
+ role: 'secondary',
44
+ keyColumns: index.columns.map(c => ({ columnIndex: c.index, desc: !!c.desc, collation: c.collation })),
45
+ unique: true,
46
+ };
47
+ const seekColumnIndexes = index.columns.map(c => c.index);
48
+ const values = seekColumnIndexes.map(i => newRow[i]);
49
+ return makeIndexEqSeekFilterInfo(descriptor, seekColumnIndexes, values);
50
+ }
51
+ //# sourceMappingURL=filter-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-info.js","sourceRoot":"","sources":["../../src/filter-info.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAEzG;;;;;;;GAOG;AACH,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAmB,EAAE,EAAc;IAC1E,MAAM,KAAK,GAAoB;QAC9B,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,SAAS;QACf,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,MAAM,EAAE,IAAI;KACZ,CAAC;IACF,OAAO,yBAAyB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,8BAA8B,CAAC,KAAuB,EAAE,MAA2B;IAClG,MAAM,UAAU,GAAoB;QACnC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACtG,MAAM,EAAE,IAAI;KACZ,CAAC;IACF,MAAM,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,yBAAyB,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { SqlValue, UpdateResult, RowOp, VirtualTable } from '@quereus/quereus';
2
+ /**
3
+ * Applies every staged overlay row to the underlying table WITHOUT committing.
4
+ *
5
+ * This is the *apply-only* half of the isolation commit flush. It begins a
6
+ * transaction on the underlying table and writes each overlay entry (tombstones
7
+ * as deletes, live rows as insert/update), but deliberately leaves the
8
+ * underlying transaction OPEN. Committing per table here is exactly what tears a
9
+ * multi-table commit (table A durably commits before table B applies), so the
10
+ * commit is factored out to {@link IsolationModule.commitConnectionOverlays},
11
+ * which drives one apply-all pass followed by one commit-all pass across every
12
+ * table the db-transaction touched.
13
+ *
14
+ * Preserves the original per-table flush semantics:
15
+ * - Deletes (tombstones) are applied before inserts/updates so a write colliding
16
+ * on a secondary UNIQUE with a row being deleted in the same flush sees the
17
+ * slot freed first. Each PK appears at most once in the overlay, so there is no
18
+ * same-PK delete-then-insert pair this reordering could invert; sort() is
19
+ * stable in V8/Node, preserving the original PK order within each group.
20
+ * - Data writes use `preCoerced`/`trustedWrite` (the overlay already coerced and
21
+ * constraint-checked them in the merged view).
22
+ * - A post-precheck constraint result is a loud INTERNAL error (see
23
+ * {@link assertFlushWriteOk}), never silently dropped.
24
+ */
25
+ export declare function applyOverlayToUnderlying(underlyingTable: VirtualTable, overlayTable: VirtualTable, tombstoneColumn: string): Promise<void>;
26
+ /**
27
+ * Asserts that an underlying write performed during the commit flush succeeded.
28
+ *
29
+ * The overlay's merged-view pre-checks resolve every constraint before commit, so
30
+ * a `constraint` result here means a real invariant was violated *after* those
31
+ * checks. Historically this result was discarded, silently dropping the colliding
32
+ * write and surfacing as data corruption. Convert it into a loud INTERNAL error;
33
+ * the caller's coordinator rolls back the flush and rethrows.
34
+ */
35
+ export declare function assertFlushWriteOk(result: UpdateResult, operation: RowOp, pk: SqlValue[], tableName: string): void;
36
+ //# sourceMappingURL=flush.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/flush.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAKpF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,wBAAwB,CAC7C,eAAe,EAAE,YAAY,EAC7B,YAAY,EAAE,YAAY,EAC1B,eAAe,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CA2Df;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAOlH"}
@@ -0,0 +1,110 @@
1
+ import { QuereusError, StatusCode, isConstraintViolation } from '@quereus/quereus';
2
+ import { makePkPointLookupFilter } from './filter-info.js';
3
+ import { collectOverlayEntries } from './overlay-rows.js';
4
+ /**
5
+ * Applies every staged overlay row to the underlying table WITHOUT committing.
6
+ *
7
+ * This is the *apply-only* half of the isolation commit flush. It begins a
8
+ * transaction on the underlying table and writes each overlay entry (tombstones
9
+ * as deletes, live rows as insert/update), but deliberately leaves the
10
+ * underlying transaction OPEN. Committing per table here is exactly what tears a
11
+ * multi-table commit (table A durably commits before table B applies), so the
12
+ * commit is factored out to {@link IsolationModule.commitConnectionOverlays},
13
+ * which drives one apply-all pass followed by one commit-all pass across every
14
+ * table the db-transaction touched.
15
+ *
16
+ * Preserves the original per-table flush semantics:
17
+ * - Deletes (tombstones) are applied before inserts/updates so a write colliding
18
+ * on a secondary UNIQUE with a row being deleted in the same flush sees the
19
+ * slot freed first. Each PK appears at most once in the overlay, so there is no
20
+ * same-PK delete-then-insert pair this reordering could invert; sort() is
21
+ * stable in V8/Node, preserving the original PK order within each group.
22
+ * - Data writes use `preCoerced`/`trustedWrite` (the overlay already coerced and
23
+ * constraint-checked them in the merged view).
24
+ * - A post-precheck constraint result is a loud INTERNAL error (see
25
+ * {@link assertFlushWriteOk}), never silently dropped.
26
+ */
27
+ export async function applyOverlayToUnderlying(underlyingTable, overlayTable, tombstoneColumn) {
28
+ if (!overlayTable.query) {
29
+ // No overlay rows to read — still begin so the coordinator has a live
30
+ // underlying transaction to commit/roll back consistently with peers.
31
+ await underlyingTable.begin?.();
32
+ return;
33
+ }
34
+ const underlyingSchema = underlyingTable.tableSchema;
35
+ if (!underlyingSchema) {
36
+ throw new QuereusError('Underlying table has no schema', StatusCode.INTERNAL);
37
+ }
38
+ const pkIndices = underlyingSchema.primaryKeyDefinition.map(pkDef => pkDef.index);
39
+ const tableName = underlyingSchema.name;
40
+ // Collect all overlay entries first.
41
+ const overlayEntries = await collectOverlayEntries(overlayTable, tombstoneColumn, pkIndices);
42
+ // Begin the underlying transaction (idempotent for a shared-coordinator store)
43
+ // before applying — and unconditionally, so the coordinator's applied-table set
44
+ // is accurate even for a (degenerate) empty overlay.
45
+ await underlyingTable.begin?.();
46
+ if (overlayEntries.length === 0)
47
+ return;
48
+ // Deletes before inserts/updates (see doc comment above).
49
+ const ordered = [...overlayEntries].sort((a, b) => (a.isTombstone === b.isTombstone ? 0 : a.isTombstone ? -1 : 1));
50
+ for (const entry of ordered) {
51
+ if (entry.isTombstone) {
52
+ const result = await underlyingTable.update({
53
+ operation: 'delete',
54
+ values: undefined,
55
+ oldKeyValues: entry.pk,
56
+ });
57
+ assertFlushWriteOk(result, 'delete', entry.pk, tableName);
58
+ }
59
+ else {
60
+ // Insert vs update decided by whether the row already exists underlying.
61
+ const existsInUnderlying = await rowExistsInUnderlying(underlyingTable, pkIndices, entry.pk);
62
+ if (existsInUnderlying) {
63
+ const result = await underlyingTable.update({
64
+ operation: 'update',
65
+ values: entry.dataRow,
66
+ oldKeyValues: entry.pk,
67
+ preCoerced: true,
68
+ trustedWrite: true,
69
+ });
70
+ assertFlushWriteOk(result, 'update', entry.pk, tableName);
71
+ }
72
+ else {
73
+ const result = await underlyingTable.update({
74
+ operation: 'insert',
75
+ values: entry.dataRow,
76
+ preCoerced: true,
77
+ trustedWrite: true,
78
+ });
79
+ assertFlushWriteOk(result, 'insert', entry.pk, tableName);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * Checks if a row with the given primary key exists in the underlying table.
86
+ * Uses an O(log n) point lookup via the PK index.
87
+ */
88
+ async function rowExistsInUnderlying(underlyingTable, pkIndices, pk) {
89
+ if (!underlyingTable.query)
90
+ return false;
91
+ for await (const _row of underlyingTable.query(makePkPointLookupFilter(pkIndices, pk))) {
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ /**
97
+ * Asserts that an underlying write performed during the commit flush succeeded.
98
+ *
99
+ * The overlay's merged-view pre-checks resolve every constraint before commit, so
100
+ * a `constraint` result here means a real invariant was violated *after* those
101
+ * checks. Historically this result was discarded, silently dropping the colliding
102
+ * write and surfacing as data corruption. Convert it into a loud INTERNAL error;
103
+ * the caller's coordinator rolls back the flush and rethrows.
104
+ */
105
+ export function assertFlushWriteOk(result, operation, pk, tableName) {
106
+ if (isConstraintViolation(result)) {
107
+ throw new QuereusError(`Isolation flush ${operation} on '${tableName}' (pk=[${pk.join(', ')}]) hit a ${result.constraint} constraint: ${result.message ?? 'no message'}. The overlay merged-view pre-checks should have resolved this before commit; this indicates an isolation-layer invariant violation.`, StatusCode.INTERNAL);
108
+ }
109
+ }
110
+ //# sourceMappingURL=flush.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flush.js","sourceRoot":"","sources":["../../src/flush.ts"],"names":[],"mappings":"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"}