@quereus/isolation 4.4.0 → 4.5.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 +31 -1
- package/dist/src/flush.d.ts.map +1 -1
- package/dist/src/flush.js +16 -3
- package/dist/src/flush.js.map +1 -1
- package/dist/src/isolated-table.d.ts +23 -9
- package/dist/src/isolated-table.d.ts.map +1 -1
- package/dist/src/isolated-table.js +128 -58
- package/dist/src/isolated-table.js.map +1 -1
- package/dist/src/isolation-module.d.ts +491 -118
- package/dist/src/isolation-module.d.ts.map +1 -1
- package/dist/src/isolation-module.js +1007 -291
- package/dist/src/isolation-module.js.map +1 -1
- package/dist/src/isolation-types.d.ts +8 -0
- package/dist/src/isolation-types.d.ts.map +1 -1
- package/dist/src/overlay-rows.d.ts +10 -1
- package/dist/src/overlay-rows.d.ts.map +1 -1
- package/dist/src/overlay-rows.js +16 -3
- package/dist/src/overlay-rows.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Database, VirtualTableModule, BaseModuleConfig, TableSchema, TableIndexSchema as IndexSchema, ModuleCapabilities, VirtualTable, BestAccessPlanRequest, BestAccessPlanResult, SchemaChangeInfo, Schema, MappingAdvertisement, LensDeploymentSnapshot, VtabConcurrencyMode, VirtualTableConnection, BackingHost, EffectiveRowSource } from '@quereus/quereus';
|
|
1
|
+
import type { Database, VirtualTableModule, BaseModuleConfig, TableSchema, TableIndexSchema as IndexSchema, ModuleCapabilities, VirtualTable, BestAccessPlanRequest, BestAccessPlanResult, SchemaChangeInfo, Schema, MappingAdvertisement, LensDeploymentSnapshot, VtabConcurrencyMode, VirtualTableConnection, BackingHost, EffectiveRowSource, CatalogObjectKind, ViewSchema } from '@quereus/quereus';
|
|
2
2
|
import type { IsolationModuleConfig } from './isolation-types.js';
|
|
3
3
|
import { IsolatedTable } from './isolated-table.js';
|
|
4
4
|
/**
|
|
@@ -40,9 +40,10 @@ export interface ConnectionOverlayState {
|
|
|
40
40
|
* table on ANY discard path, including {@link IsolationModule.destroy} and
|
|
41
41
|
* {@link IsolationModule.closeAll}, which sweep overlays across multiple db ids
|
|
42
42
|
* and so have no single ambient `db` to hand the overlay module's `destroy`.
|
|
43
|
-
* Set at every real creation site (`ensureOverlay`, the
|
|
44
|
-
*
|
|
45
|
-
* `
|
|
43
|
+
* Set at every real creation site (`ensureOverlay`, and the ALTER PRIMARY KEY
|
|
44
|
+
* clean-overlay swap in `replaceOverlayForPrimaryKeyChange`); the default
|
|
45
|
+
* `MemoryTableModule` overlay ignores it, but a host-injected `config.overlay`
|
|
46
|
+
* keyed per-db needs the overlay's OWN db, not the sweeper's.
|
|
46
47
|
*/
|
|
47
48
|
db: Database;
|
|
48
49
|
/**
|
|
@@ -226,7 +227,8 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
226
227
|
* overlay module's table registry rather than leaking there for the life of the
|
|
227
228
|
* `Database`. This is the single sink every overlay-discard path funnels through —
|
|
228
229
|
* without it, `MemoryTableModule.tables` accumulates one dead `_overlay_<table>_<id>`
|
|
229
|
-
* entry per writing transaction (and one more per
|
|
230
|
+
* entry per writing transaction (and one more per `alterPrimaryKey` overlay swap — the
|
|
231
|
+
* only DDL path left that replaces an overlay rather than adopting in place), unbounded.
|
|
230
232
|
*
|
|
231
233
|
* `MemoryTableManager.destroy` rolls back the overlay's own pending layer and clears its
|
|
232
234
|
* connections; a later db-side teardown of the (now-detached) `MemoryVirtualTableConnection`
|
|
@@ -408,6 +410,18 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
408
410
|
* basis module would silently never hear the deployment.
|
|
409
411
|
*/
|
|
410
412
|
notifyLensDeployment(db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot): Promise<void>;
|
|
413
|
+
/**
|
|
414
|
+
* Forwards the view / materialized-view catalog-persistability veto to the underlying
|
|
415
|
+
* module.
|
|
416
|
+
*
|
|
417
|
+
* `allModules()` yields the REGISTERED module, which is this wrapper when a store is
|
|
418
|
+
* isolated — so without this forward an isolation-wrapped store would never be
|
|
419
|
+
* consulted and would keep the silent-drop bug (a view it cannot persist creates
|
|
420
|
+
* "successfully" and vanishes on reopen). Whether a catalog entry is encodable is a
|
|
421
|
+
* property of the definition text, not of the overlay, so a straight delegate is
|
|
422
|
+
* correct; the throw must propagate (that is the entire point of the hook).
|
|
423
|
+
*/
|
|
424
|
+
assertCatalogObjectPersistable(db: Database, kind: CatalogObjectKind, object: ViewSchema | TableSchema): void;
|
|
411
425
|
/**
|
|
412
426
|
* Delegates access plan selection to the underlying module.
|
|
413
427
|
* This ensures the query planner knows about indexes and can generate
|
|
@@ -484,42 +498,24 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
484
498
|
*/
|
|
485
499
|
destroy(db: Database, pAux: unknown, moduleName: string, schemaName: string, tableName: string): Promise<void>;
|
|
486
500
|
/**
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* which must roll back).
|
|
496
|
-
*/
|
|
497
|
-
private insertIntoRebuiltOverlay;
|
|
498
|
-
/**
|
|
499
|
-
* Runs one overlay's rebuild and installs the result, routing a CONSTRAINT failure by who
|
|
500
|
-
* owns the overlay:
|
|
501
|
-
*
|
|
502
|
-
* - **The DDL-issuing connection.** Unreachable: the row source handed to the underlying
|
|
503
|
-
* (see {@link issuerEffectiveRows}) judged a superset of exactly these rows and accepted
|
|
504
|
-
* them, so a rejection here means validation and migration have drifted. Raise INTERNAL —
|
|
505
|
-
* loudly, because the alternative is the silent row loss this guard exists to end.
|
|
506
|
-
* - **A foreign connection.** Reachable and legitimate: its staged rows may violate a
|
|
507
|
-
* constraint another connection just declared. Poison that overlay and leave it
|
|
508
|
-
* unmigrated, so its owner errors on its next read/write/commit and rolls back. The
|
|
509
|
-
* issuer's DDL proceeds. Mirrors the tier-3 NOT NULL handling in {@link alterTable}.
|
|
510
|
-
*
|
|
511
|
-
* A rebuild that throws leaves the OLD overlay installed either way — the new table is
|
|
512
|
-
* simply discarded.
|
|
501
|
+
* The INTERNAL raised when the DDL-ISSUING connection's own overlay rejects the change the
|
|
502
|
+
* DDL just made (routed through {@link applyInPlaceOverlayChange} by the index paths and
|
|
503
|
+
* every ALTER TABLE forward).
|
|
504
|
+
*
|
|
505
|
+
* Unreachable by construction: the row source handed to the underlying (see
|
|
506
|
+
* {@link issuerEffectiveRows}) judged a superset of exactly these rows and accepted them,
|
|
507
|
+
* so a rejection here means validation and migration have drifted. Raise loudly — the
|
|
508
|
+
* alternative is the silent row loss these guards exist to end.
|
|
513
509
|
*/
|
|
514
|
-
private
|
|
510
|
+
private issuerOverlayDriftError;
|
|
515
511
|
/**
|
|
516
|
-
* Builds the poison message stamped onto a foreign overlay whose staged rows cannot
|
|
517
|
-
*
|
|
518
|
-
* {@link
|
|
519
|
-
* the
|
|
520
|
-
* covers a UNIQUE (or any other) violation raised by the
|
|
512
|
+
* Builds the poison message stamped onto a foreign overlay whose staged rows cannot adopt
|
|
513
|
+
* a constraint another connection's DDL just declared (see
|
|
514
|
+
* {@link applyInPlaceOverlayChange}). Companion to {@link buildAlterPoisonMessage}, which
|
|
515
|
+
* covers the data conditions the pre-validation pass rejects before any overlay is touched;
|
|
516
|
+
* this one covers a UNIQUE (or any other) violation raised by the in-place adoption itself.
|
|
521
517
|
*/
|
|
522
|
-
private
|
|
518
|
+
private buildInPlaceAdoptPoisonMessage;
|
|
523
519
|
/**
|
|
524
520
|
* Builds the poison message stamped onto a foreign overlay whose table was dropped out
|
|
525
521
|
* from under it (see {@link destroy}). Names the schema.table so the owning connection's
|
|
@@ -561,9 +557,9 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
561
557
|
*/
|
|
562
558
|
private issuerEffectiveRows;
|
|
563
559
|
/**
|
|
564
|
-
* Creates an index on the underlying table, then
|
|
565
|
-
*
|
|
566
|
-
* the rest of each open transaction.
|
|
560
|
+
* Creates an index on the underlying table, then hands the same index to every
|
|
561
|
+
* per-connection overlay so it (and, for a UNIQUE index, the constraint derived from it)
|
|
562
|
+
* is enforced for the rest of each open transaction.
|
|
567
563
|
*
|
|
568
564
|
* Two things the underlying cannot do for itself:
|
|
569
565
|
*
|
|
@@ -575,8 +571,8 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
575
571
|
* 2. **Enforce the new constraint.** An overlay built before the index knows nothing of it,
|
|
576
572
|
* and `IsolatedTable.findMergedUniqueConflict` only scans the underlying — so a pending
|
|
577
573
|
* row colliding with another pending row is nobody's job until the overlay itself carries
|
|
578
|
-
* the index.
|
|
579
|
-
*
|
|
574
|
+
* the index. Forwarding is also what gives a merged secondary-index scan later in the
|
|
575
|
+
* transaction an overlay that can serve it.
|
|
580
576
|
*
|
|
581
577
|
* We use the stored table instance's createIndex() rather than the module-level method so
|
|
582
578
|
* that the MemoryTable's local tableSchema property stays in sync. That property is what
|
|
@@ -584,18 +580,38 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
584
580
|
*/
|
|
585
581
|
createIndex(db: Database, schemaName: string, tableName: string, indexSchema: IndexSchema, rows?: EffectiveRowSource): Promise<void>;
|
|
586
582
|
/**
|
|
587
|
-
*
|
|
588
|
-
*
|
|
583
|
+
* Adds one index to an already-open overlay, in the overlay's own flavor (predicate
|
|
584
|
+
* narrowed to live rows and rescoped onto the overlay's table name — see
|
|
585
|
+
* {@link createOverlayIndexSchema}).
|
|
586
|
+
*
|
|
587
|
+
* A UNIQUE index the overlay's staged rows violate raises `CONSTRAINT` out of
|
|
588
|
+
* `MemoryTableManager.createIndex`'s pre-validation pass, which
|
|
589
|
+
* {@link applyIndexChangeToOverlays} routes to INTERNAL (issuer) or poison (foreign).
|
|
590
|
+
* The pass runs before any mutation, so a rejected overlay is left exactly as it was.
|
|
591
|
+
*
|
|
592
|
+
* No-ops when the overlay module has no index support, or when the overlay already
|
|
593
|
+
* carries an index of that name (an overlay built after the underlying's create already
|
|
594
|
+
* copied it in through {@link createOverlaySchema}).
|
|
595
|
+
*/
|
|
596
|
+
private createOverlayIndex;
|
|
597
|
+
/**
|
|
598
|
+
* Reads back the underlying table instance's post-`createIndex` schema plus the entry it now
|
|
599
|
+
* carries for the new index — the canonical post-create form each overlay's copy is derived
|
|
600
|
+
* from ({@link createOverlayIndex}).
|
|
589
601
|
*
|
|
590
602
|
* Both bundled underlyings refresh the instance's cached `tableSchema` (memory through
|
|
591
|
-
* `MemoryTable.createIndex`, the store through `StoreTable.updateSchema`)
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
603
|
+
* `MemoryTable.createIndex`, the store through `StoreTable.updateSchema`). A third-party
|
|
604
|
+
* underlying that refreshed only its module-level schema would leave every overlay without
|
|
605
|
+
* the index — silently re-opening the very hole `createIndex`'s forward exists to close — so
|
|
606
|
+
* assert rather than assume.
|
|
595
607
|
*/
|
|
596
608
|
private assertIndexPresent;
|
|
609
|
+
/** `schema`'s index named `indexName` (case-insensitively), if it declares one. */
|
|
610
|
+
private findSchemaIndex;
|
|
611
|
+
/** Case-insensitive "does `schema` declare an index named `indexName`". */
|
|
612
|
+
private schemaHasIndex;
|
|
597
613
|
/**
|
|
598
|
-
* Drops an index on the underlying table.
|
|
614
|
+
* Drops an index on the underlying table, then drops it from every per-connection overlay.
|
|
599
615
|
*
|
|
600
616
|
* Mirrors createIndex: when the underlying VirtualTable exposes an
|
|
601
617
|
* instance-level dropIndex (e.g. MemoryTable, which forwards to its manager
|
|
@@ -603,55 +619,94 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
603
619
|
* to the module-level dropIndex (e.g. StoreModule, which refreshes the
|
|
604
620
|
* StoreTable's cached tableSchema and tears down the index store).
|
|
605
621
|
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* `tableSchemaAtCreation` is frozen at layer-creation time, so the
|
|
611
|
-
* synthesized UNIQUE constraint keeps firing inside the overlay's
|
|
612
|
-
* own UC check on the next write even after the manager's schema is
|
|
613
|
-
* refreshed. Rebuilding gives the new MemoryTable a fresh
|
|
614
|
-
* transaction layer that captures the post-drop schema. Overlays
|
|
615
|
-
* created AFTER this point inherit the post-drop schema from the
|
|
616
|
-
* underlying at ensureOverlay time.
|
|
622
|
+
* Mirrors createIndex's early return when neither hook exists too: an underlying that
|
|
623
|
+
* declares indexes at CREATE TABLE time but cannot drop one still has them, and dropping
|
|
624
|
+
* from the overlays alone would leave each overlay enforcing LESS than the base it flushes
|
|
625
|
+
* into — a silent divergence surfacing only as a commit-time rejection.
|
|
617
626
|
*/
|
|
618
627
|
dropIndex(db: Database, schemaName: string, tableName: string, indexName: string): Promise<void>;
|
|
619
628
|
/**
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
* identical either way, so staged rows copy verbatim.
|
|
624
|
-
*
|
|
625
|
-
* A poisoned overlay (from a cross-connection ALTER, or a DROP TABLE) holds rows in the
|
|
626
|
-
* pre-alter column layout, narrower/wider than `updatedSchema`. Rebuilding it would copy
|
|
627
|
-
* layout-mismatched rows AND drop the poison flag (the new state carries none), silently
|
|
628
|
-
* un-poisoning a connection that must still roll back. Leave it as-is.
|
|
629
|
+
* Drops one index (and the UNIQUE constraint derived from it) from an already-open
|
|
630
|
+
* overlay. No-ops when the overlay module has no index support, or when the overlay never
|
|
631
|
+
* carried the index.
|
|
629
632
|
*/
|
|
630
|
-
private
|
|
633
|
+
private dropOverlayIndex;
|
|
631
634
|
/**
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
635
|
+
* Applies an index change (CREATE INDEX or DROP INDEX) IN PLACE to every non-poisoned
|
|
636
|
+
* per-connection overlay of one table.
|
|
637
|
+
*
|
|
638
|
+
* In place, not by rebuild. Both paths used to discard the overlay and copy its staged
|
|
639
|
+
* rows into a fresh `MemoryTable`, which silently destroyed the overlay's savepoint chain:
|
|
640
|
+
* the copy's first write lazily registers the new overlay's connection, and
|
|
641
|
+
* `Database.registerConnection` replays `begin()` plus the whole active savepoint stack
|
|
642
|
+
* BEFORE the copy runs — so every copied row landed ABOVE the replayed savepoint and the
|
|
643
|
+
* next `rollback to savepoint` discarded rows staged long before that savepoint was taken
|
|
644
|
+
* (`bug-isolation-index-ddl-rebuild-drops-savepoint-writes`).
|
|
645
|
+
*
|
|
646
|
+
* The rebuild was originally forced by a memory-module limitation — an open write
|
|
647
|
+
* `TransactionLayer` froze its schema at creation, so a bare `overlay.dropIndex` left the
|
|
648
|
+
* synthesized UNIQUE constraint firing inside the layer. `TransactionLayer.adoptSchema`
|
|
649
|
+
* now has both an additive and a removal branch, and `MemoryTableManager` calls it for
|
|
650
|
+
* both index directions, so an open layer adopts the change with its savepoint snapshots
|
|
651
|
+
* intact.
|
|
652
|
+
*
|
|
653
|
+
* CONSTRAINT is routed by who owns the overlay via {@link applyInPlaceOverlayChange},
|
|
654
|
+
* shared with every ALTER TABLE forward.
|
|
655
|
+
*
|
|
656
|
+
* A poisoned overlay is skipped: it holds rows in a pre-ALTER column layout and its owner
|
|
657
|
+
* must roll back regardless, so there is nothing to keep enforcing for it.
|
|
636
658
|
*/
|
|
637
|
-
private
|
|
659
|
+
private applyIndexChangeToOverlays;
|
|
638
660
|
/**
|
|
639
|
-
*
|
|
661
|
+
* Applies one in-place DDL mutation to a single overlay, routing a CONSTRAINT failure
|
|
662
|
+
* by who owns the overlay: the issuer's own overlay → INTERNAL (its rows were already
|
|
663
|
+
* judged by the DDL's own validation pass, so a rejection here means validation and
|
|
664
|
+
* migration have drifted — {@link issuerOverlayDriftError}); a foreign overlay →
|
|
665
|
+
* poison and leave it untouched so its owner errors and rolls back
|
|
666
|
+
* ({@link buildInPlaceAdoptPoisonMessage}). Any non-CONSTRAINT failure is a layer-invariant
|
|
667
|
+
* violation, not a data condition, and rethrows for everyone.
|
|
668
|
+
*
|
|
669
|
+
* Shared by the index paths ({@link applyIndexChangeToOverlays}) and the ALTER TABLE
|
|
670
|
+
* column-shape forwards ({@link alterTable}), so the two cannot drift on error routing.
|
|
671
|
+
*/
|
|
672
|
+
private applyInPlaceOverlayChange;
|
|
673
|
+
/**
|
|
674
|
+
* Delegates ALTER TABLE to the underlying module and carries any per-connection
|
|
640
675
|
* overlays to the post-alter schema without discarding staged rows.
|
|
641
676
|
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
677
|
+
* Every change type forwards to each overlay IN PLACE — through the overlay's own
|
|
678
|
+
* `alterSchema` / `createIndex` / ordinary writes — so the overlay's layer chain and
|
|
679
|
+
* savepoint snapshots survive the ALTER and `rollback to savepoint` keeps
|
|
680
|
+
* distinguishing rows staged before the savepoint from rows staged after it (see
|
|
681
|
+
* {@link applyIndexChangeToOverlays} for why the old rebuild — copying staged rows
|
|
682
|
+
* into a fresh staging table — destroyed that distinction). Per change type:
|
|
683
|
+
*
|
|
684
|
+
* - ADD / DROP / RENAME COLUMN: {@link forwardColumnShapeToOverlay}. ADD COLUMN
|
|
685
|
+
* backfills each staged row exactly as the committed path does (literal default,
|
|
686
|
+
* per-row `new.<col>` evaluator, or NULL); tombstone rows get NULL.
|
|
687
|
+
* - ALTER COLUMN: {@link forwardAlterColumnToOverlay}. `set data type` / `set collate`
|
|
688
|
+
* / `set default` forward through the overlay's `alterSchema` (the overlay module
|
|
689
|
+
* converts / re-keys its open layers itself), with one preparation step: a `set collate`
|
|
690
|
+
* that re-keys the PRIMARY KEY first discards any deletion markers the new key collapses
|
|
691
|
+
* onto another staged row ({@link dropCollapsedPkRekeyMarkers}). `set not null` is
|
|
692
|
+
* withheld from the overlay and the staged live rows' NULLs are backfilled via ordinary
|
|
693
|
+
* overlay writes instead.
|
|
694
|
+
* - ADD / DROP / RENAME CONSTRAINT: {@link forwardAddConstraintToOverlay} /
|
|
695
|
+
* {@link forwardConstraintNameChangeToOverlay}. A UNIQUE lands as a
|
|
696
|
+
* tombstone-narrowed unique index; CHECK forwards verbatim; FOREIGN KEY does not
|
|
697
|
+
* forward at all.
|
|
698
|
+
* - ALTER PRIMARY KEY: no overlay can follow (its layer trees are keyed by the old
|
|
699
|
+
* primary key) — the issuer with staged rows is rejected before the underlying
|
|
700
|
+
* mutates, a foreign overlay with staged rows is poisoned, and a clean overlay is
|
|
701
|
+
* swapped for a fresh staging table ({@link replaceOverlayForPrimaryKeyChange}).
|
|
647
702
|
*
|
|
648
703
|
* **Atomicity guarantee.** DDL through Quereus is not transaction-scoped and the
|
|
649
704
|
* underlying (shared, committed) base auto-commits its mutation immediately —
|
|
650
705
|
* there is no frame to unwind, and `dropColumn` / type-converting `alterColumn`
|
|
651
706
|
* are lossy and not invertible, so "revert the underlying on overlay-migration
|
|
652
707
|
* failure" is not viable. Instead this method **pre-validates** every affected
|
|
653
|
-
* overlay's
|
|
654
|
-
* BEFORE calling `underlying.alterTable`. A rejection therefore fires while the
|
|
708
|
+
* overlay's migration (the per-row NOT NULL check, the per-value retype conversion, and
|
|
709
|
+
* the tombstone-present guard) BEFORE calling `underlying.alterTable`. A rejection therefore fires while the
|
|
655
710
|
* underlying, the schema catalog, and every overlay are still untouched, so the
|
|
656
711
|
* ALTER either fails clean or fully applies — base/catalog can no longer diverge.
|
|
657
712
|
* This mirrors the engine's pre-mutation `validateNotNullBackfill` in
|
|
@@ -663,14 +718,35 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
663
718
|
* check before it mutates anything, so the atomic-abort guarantee above still holds.
|
|
664
719
|
*/
|
|
665
720
|
alterTable(db: Database, schemaName: string, tableName: string, change: SchemaChangeInfo, rows?: EffectiveRowSource): Promise<TableSchema>;
|
|
721
|
+
/**
|
|
722
|
+
* Rejects an ADD / RENAME COLUMN that would give a base column the same name as the
|
|
723
|
+
* overlay's private tombstone flag, BEFORE `underlying.alterTable` runs.
|
|
724
|
+
*
|
|
725
|
+
* Every overlay carries one extra column named {@link tombstoneColumn}, so a base column
|
|
726
|
+
* of that name collides with it. Forwarding the change to an open overlay makes the
|
|
727
|
+
* overlay module raise a duplicate-name `ERROR`, which is not a data condition and so
|
|
728
|
+
* rethrows out of {@link applyInPlaceOverlayChange} — after the underlying has already
|
|
729
|
+
* (irreversibly) applied. The catalog then keeps the pre-alter column set while the base
|
|
730
|
+
* holds the new one, and the next write to the table lands values against the wrong
|
|
731
|
+
* columns. Rejecting up front keeps the atomic-abort guarantee this method documents.
|
|
732
|
+
*
|
|
733
|
+
* NOTE: the check is unconditional — not gated on an overlay existing — so the answer
|
|
734
|
+
* does not depend on whether some connection happens to have a transaction open. That
|
|
735
|
+
* also stops a table from acquiring the colliding name while idle and hitting
|
|
736
|
+
* {@link createOverlaySchema}'s (silent) duplicate at the next BEGIN. A CREATE TABLE
|
|
737
|
+
* declaring the name still gets that duplicate — tracked separately.
|
|
738
|
+
*/
|
|
739
|
+
private assertColumnNameNotTombstone;
|
|
666
740
|
/**
|
|
667
741
|
* Builds the poison message stamped onto a foreign overlay whose backfill could not
|
|
668
742
|
* satisfy a cross-connection ALTER (see {@link alterTable} tier 3). Names the
|
|
669
743
|
* schema.table and the offending column so the owning connection's eventual
|
|
670
744
|
* read/write/commit error is self-explanatory. Poison arises on the addColumn NOT NULL
|
|
671
|
-
* path (a new NOT-NULL column with no usable default)
|
|
672
|
-
* path (a staged NULL with no usable default)
|
|
673
|
-
*
|
|
745
|
+
* path (a new NOT-NULL column with no usable default), the `set not null` tightening
|
|
746
|
+
* path (a staged NULL with no usable default), the `set data type` path (a staged value
|
|
747
|
+
* that cannot be converted to the new type), and the primary-key `set collate` path (two
|
|
748
|
+
* staged live rows colliding under the new collation); other change types never reach
|
|
749
|
+
* here but are handled defensively.
|
|
674
750
|
*/
|
|
675
751
|
private buildAlterPoisonMessage;
|
|
676
752
|
/**
|
|
@@ -731,15 +807,6 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
731
807
|
* Returns how many entries moved.
|
|
732
808
|
*/
|
|
733
809
|
private rekeyConnectionScopedMap;
|
|
734
|
-
/**
|
|
735
|
-
* Rebuilds an overlay table under the post-alter schema, translating each
|
|
736
|
-
* staged row to the new column layout.
|
|
737
|
-
*
|
|
738
|
-
* A row the post-alter schema's constraints reject throws CONSTRAINT out of
|
|
739
|
-
* {@link insertIntoRebuiltOverlay} rather than being silently discarded; the caller
|
|
740
|
-
* ({@link adoptRebuiltOverlay}) maps that to INTERNAL or poison.
|
|
741
|
-
*/
|
|
742
|
-
private migrateOverlayForAlter;
|
|
743
810
|
/**
|
|
744
811
|
* Precomputes the per-ALTER constants an `addColumn` overlay backfill needs:
|
|
745
812
|
* the folded literal DEFAULT (the `tryFoldLiteral` of the DEFAULT expr, or `null`
|
|
@@ -764,12 +831,55 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
764
831
|
* needs no staged-row work, and with no overlays there is nothing to backfill or reject.
|
|
765
832
|
*
|
|
766
833
|
* `change` for `set not null` carries no default expression, so the DEFAULT is read from the
|
|
767
|
-
* column's PRE-alter schema (via a to-be-migrated overlay — the same
|
|
768
|
-
*
|
|
834
|
+
* column's PRE-alter schema (via a to-be-migrated overlay — the same layout every migrated
|
|
835
|
+
* overlay shares). Folded exactly as
|
|
769
836
|
* {@link deriveAddColumnBackfill} folds its DEFAULT, so backfill and reject decisions here
|
|
770
837
|
* cannot drift from what the underlying enforces over its committed rows.
|
|
771
838
|
*/
|
|
772
839
|
private deriveSetNotNullBackfill;
|
|
840
|
+
/**
|
|
841
|
+
* Precomputes the per-ALTER constants an `alter column … set data type` overlay conversion
|
|
842
|
+
* needs: the retyped column's index and a per-value `convert`. Returns undefined unless this
|
|
843
|
+
* is a retype with at least one overlay to convert, and undefined for an ALIAS retype (the
|
|
844
|
+
* new logical type IS the old type object — `inferType` flattens `varchar(50)` to
|
|
845
|
+
* `TEXT_TYPE`) — both underlyings gate their value rewrite on that exact identity
|
|
846
|
+
* comparison, so mirroring it here keeps overlay and committed rows moving together should
|
|
847
|
+
* "what counts as a value-rewriting retype" ever change. A same-storage-class retype between
|
|
848
|
+
* different types (text → date) DOES convert: staged values are validated and normalized to
|
|
849
|
+
* the new type's canonical spelling, exactly as the underlyings treat their committed rows.
|
|
850
|
+
*
|
|
851
|
+
* `inferType` never throws (an unknown type name falls through SQLite-style affinity rules),
|
|
852
|
+
* so deriving this BEFORE the underlying mutation is safe. `convert` is the literal mirror of
|
|
853
|
+
* `MemoryTableManager.alterColumn` / `StoreModule.alterColumnSetDataType`: `validateAndParse`,
|
|
854
|
+
* rethrowing failure as the same MISMATCH message, so the two legs cannot drift.
|
|
855
|
+
*/
|
|
856
|
+
private deriveSetDataTypeConvert;
|
|
857
|
+
/**
|
|
858
|
+
* Precomputes the per-ALTER constants a primary-key re-keying `alter column … set collate`
|
|
859
|
+
* overlay migration needs: the PK column positions and a key serializer built over the
|
|
860
|
+
* POST-change collation — the PRE-alter overlay schema with the named column's collation
|
|
861
|
+
* replaced. The overlay's PK definition mirrors the base's (the tombstone flag is appended
|
|
862
|
+
* after the data columns), so the synthesized schema keys exactly as the re-keyed base will.
|
|
863
|
+
*
|
|
864
|
+
* Returns undefined unless the named column is a PRIMARY KEY member and the normalized
|
|
865
|
+
* collation actually differs from the column's current one — mirroring the underlying's
|
|
866
|
+
* metadata-only gate (`MemoryTableManager.alterColumn`'s `nameMatches`), so overlay and
|
|
867
|
+
* committed rows re-key together or not at all. `validateCollationForType` can throw for an
|
|
868
|
+
* unknown collation, but the engine already validated the name before dispatching the ALTER,
|
|
869
|
+
* and a direct module caller gets the same pre-mutation rejection the underlying would give.
|
|
870
|
+
*/
|
|
871
|
+
private derivePkRekey;
|
|
872
|
+
/**
|
|
873
|
+
* Buckets an overlay's staged rows by their PRIMARY KEY as the pending `set collate` will
|
|
874
|
+
* re-key it: one {@link PkRekeyGroup} per post-change key. Shared by the validation dry run
|
|
875
|
+
* (which refuses a group holding two live rows) and the migrate-step marker drop (which
|
|
876
|
+
* discards collapsed markers), so the two passes cannot disagree about what collapses.
|
|
877
|
+
*
|
|
878
|
+
* NOTE: materializes one serialized key + one PK tuple per staged row (plus the marker rows
|
|
879
|
+
* themselves), once per pass of one ALTER. If an overlay with very many staged rows ever
|
|
880
|
+
* makes this ALTER slow, group lazily or reuse the merge `effectiveRowsFor` already builds.
|
|
881
|
+
*/
|
|
882
|
+
private collectPkRekeyGroups;
|
|
773
883
|
/**
|
|
774
884
|
* Dry-runs an overlay's ALTER migration-fallible work without mutating anything,
|
|
775
885
|
* so the caller can run it for every affected overlay BEFORE the irreversible
|
|
@@ -787,14 +897,37 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
787
897
|
* For `set not null` with NO usable DEFAULT (`setNotNullCtx.hasDefault === false`), a staged
|
|
788
898
|
* non-tombstone NULL at the now-NOT-NULL column throws CONSTRAINT here — for the issuer this
|
|
789
899
|
* aborts atomically before the underlying mutates; for a foreign overlay the caller maps it to
|
|
790
|
-
* poison. With a usable DEFAULT the staged NULLs are backfilled by
|
|
791
|
-
* so nothing is rejected here.
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
900
|
+
* poison. With a usable DEFAULT the staged NULLs are backfilled by
|
|
901
|
+
* {@link backfillStagedNotNull}, so nothing is rejected here.
|
|
902
|
+
*
|
|
903
|
+
* For `set data type` (`setDataTypeCtx`), every staged non-NULL value is run through the
|
|
904
|
+
* conversion and the result discarded, so an unconvertible one throws MISMATCH here. For the
|
|
905
|
+
* ISSUER this is belt-and-braces — the underlying's own pre-mutation pass walks the same rows
|
|
906
|
+
* via {@link issuerEffectiveRows} — EXCEPT when an outer wrapper supplied `rows`, in which case
|
|
907
|
+
* this is the only check. For a FOREIGN overlay it is the only pass that ever sees those rows,
|
|
908
|
+
* and it must run before the migration so a failure becomes poison rather than a half-migrated
|
|
909
|
+
* overlay.
|
|
910
|
+
*
|
|
911
|
+
* For a primary-key re-keying `set collate` (`pkRekeyCtx`), the staged rows are grouped by
|
|
912
|
+
* their POST-change key: a deletion marker sharing a key with one live row is that row's
|
|
913
|
+
* before-image (the migrate step discards it — {@link dropCollapsedPkRekeyMarkers}), and
|
|
914
|
+
* markers alone on one key are one deletion — neither is a duplicate. Two LIVE rows on one
|
|
915
|
+
* key ARE, so that throws CONSTRAINT here, mirroring the underlying's own collision message.
|
|
916
|
+
* For the ISSUER this is belt-and-braces (the underlying's pre-mutation pass judges the same
|
|
917
|
+
* staged rows via {@link issuerEffectiveRows}, though this check fires first); for a FOREIGN
|
|
918
|
+
* overlay it is the only pass that sees those rows, and its throw becomes poison.
|
|
919
|
+
*
|
|
920
|
+
* Non-addColumn / non-tightening / non-retype / non-re-key changes (`addColumnCtx ===
|
|
921
|
+
* undefined`, no reject-mode `setNotNullCtx`, no `setDataTypeCtx`, no `pkRekeyCtx`) only run
|
|
922
|
+
* the tombstone guard; their row translation appends/removes nothing fallible on data grounds.
|
|
796
923
|
*/
|
|
797
924
|
private validateOverlayMigration;
|
|
925
|
+
/**
|
|
926
|
+
* The staged rows an overlay holds that represent LIVE data, i.e. every row except the
|
|
927
|
+
* tombstones — those carry placeholder NULLs at every data column and must never be fed to a
|
|
928
|
+
* per-value check, which would read a NULL that is not the row's real value.
|
|
929
|
+
*/
|
|
930
|
+
private stagedLiveRows;
|
|
798
931
|
/**
|
|
799
932
|
* Computes one staged row's value for a freshly added column, mirroring the
|
|
800
933
|
* committed-row backfill (see `base.ts` `recreatePrimaryTreeWithNewColumn` and
|
|
@@ -810,25 +943,230 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
810
943
|
*/
|
|
811
944
|
private computeAddColumnValue;
|
|
812
945
|
/**
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
* `
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
946
|
+
* Forwards one column-shape change (ADD / DROP / RENAME COLUMN) to an already-open
|
|
947
|
+
* overlay IN PLACE via the overlay's optional `alterSchema`. The overlay module (the
|
|
948
|
+
* memory module by default) reshapes its open transaction layers with their savepoint
|
|
949
|
+
* snapshots intact, so `rollback to savepoint` keeps distinguishing rows staged before
|
|
950
|
+
* the savepoint from rows staged after it — the rebuild path destroyed that
|
|
951
|
+
* distinction in both directions (see {@link applyIndexChangeToOverlays}).
|
|
952
|
+
*
|
|
953
|
+
* A missing `alterSchema` is treated as a no-op, the way the index paths treat a
|
|
954
|
+
* missing `createIndex` / `dropIndex`.
|
|
955
|
+
*
|
|
956
|
+
* `dropColumn` / `renameColumn` forward the caller's change unchanged. `addColumn`
|
|
957
|
+
* forwards an overlay-flavoured copy — see {@link buildOverlayAddColumnChange}.
|
|
958
|
+
*/
|
|
959
|
+
private forwardColumnShapeToOverlay;
|
|
960
|
+
/**
|
|
961
|
+
* The overlay-flavoured form of one `addColumn` change:
|
|
962
|
+
*
|
|
963
|
+
* - `insertAtIndex` = the overlay's current tombstone column index, so the new data
|
|
964
|
+
* column lands ahead of the tombstone flag and the flag stays LAST — the layout
|
|
965
|
+
* every read/write path of this package assumes. A bare forward would append after
|
|
966
|
+
* the flag and every value written to the new column would be dropped on read.
|
|
967
|
+
* A caller that named its own position keeps it: the overlay's data columns mirror
|
|
968
|
+
* the base's one-for-one below the flag, so the base's index is the overlay's too.
|
|
969
|
+
* (SQL always appends, so today the caller never names one — but silently ignoring
|
|
970
|
+
* it would diverge the two layouts.)
|
|
971
|
+
* - The column definition is made NULLABLE. The overlay's ADD re-runs the overlay
|
|
972
|
+
* module's own NOT NULL validation against a different row population than the
|
|
973
|
+
* base's: tombstone rows carry placeholder NULL in every non-PK column, so a
|
|
974
|
+
* NOT NULL column the base already accepted would be rejected here. The base's
|
|
975
|
+
* NOT NULL is still enforced where it belongs — by the engine's pre-mutation
|
|
976
|
+
* `validateNotNullBackfill` and the underlying's own ADD (both judged the issuer's
|
|
977
|
+
* effective rows), and per staged row by {@link computeAddColumnValue}'s
|
|
978
|
+
* evaluator-path check.
|
|
979
|
+
* - `backfillEvaluator` receives an OVERLAY row (data columns plus the flag) and
|
|
980
|
+
* routes through {@link computeAddColumnValue} — the same helper
|
|
981
|
+
* {@link validateOverlayMigration} dry-runs, so validation and forward cannot
|
|
982
|
+
* drift: NULL for a tombstone row, else the engine's per-row `new.<col>` evaluator
|
|
983
|
+
* over the flag-stripped row, or the folded literal default.
|
|
984
|
+
*/
|
|
985
|
+
private buildOverlayAddColumnChange;
|
|
986
|
+
/**
|
|
987
|
+
* Forwards one ALTER COLUMN attribute change to an already-open overlay IN PLACE, with the
|
|
988
|
+
* NOT NULL attribute deliberately withheld.
|
|
989
|
+
*
|
|
990
|
+
* `set data type` / `set collate` / `set default` forward through the overlay's own
|
|
991
|
+
* `alterSchema`: the overlay module converts / re-keys its open transaction layers with
|
|
992
|
+
* their savepoint snapshots intact, and NULLs pass a retype untouched, so tombstone rows —
|
|
993
|
+
* placeholder NULLs at every non-key data column — ride through unharmed. One preparation
|
|
994
|
+
* step precedes a PRIMARY-KEY re-keying `set collate` (`pkRekeyCtx`): deletion markers the
|
|
995
|
+
* re-key collapses onto another staged row are discarded first
|
|
996
|
+
* ({@link dropCollapsedPkRekeyMarkers}) — the overlay's PK deliberately covers markers, so
|
|
997
|
+
* without the drop its own `alterSchema` would reject the collapsed pair as a PK collision.
|
|
998
|
+
*
|
|
999
|
+
* Neither NOT NULL direction forwards, and the overlay's `notNull` flag is left where it
|
|
1000
|
+
* stood when the overlay was created:
|
|
1001
|
+
*
|
|
1002
|
+
* - **Tightening (`set not null`).** Forwarding would have the overlay module tighten a
|
|
1003
|
+
* column whose tombstone rows carry NULL at every non-PK data column, so it would either
|
|
1004
|
+
* backfill a deletion marker's placeholder NULL from the DEFAULT (corrupting a row that
|
|
1005
|
+
* is not a row) or reject it outright when no usable DEFAULT exists. The base's NOT NULL
|
|
1006
|
+
* is enforced where it belongs — by the underlying over the issuer's effective rows, and
|
|
1007
|
+
* per overlay by {@link validateOverlayMigration} — and the staged LIVE rows' NULLs are
|
|
1008
|
+
* filled here via {@link backfillStagedNotNull}.
|
|
1009
|
+
* - **Relaxing (`drop not null`).** Nothing to do: the overlay never enforced the flag, and
|
|
1010
|
+
* the rows it stages are written with `preCoerced: true` through the overlay module's own
|
|
1011
|
+
* write path, which does not consult column nullability at all.
|
|
1012
|
+
*
|
|
1013
|
+
* NOTE: both directions therefore leave the overlay's `notNull` flag drifted from the base
|
|
1014
|
+
* for the rest of the transaction. Inert today — nothing on the overlay path reads it. The
|
|
1015
|
+
* one consumer that would, `MemoryTableModule.getBestAccessPlan` (which prunes `IS NULL` on
|
|
1016
|
+
* a NOT NULL column to an empty result), is unreachable because the isolation layer builds
|
|
1017
|
+
* the overlay's `FilterInfo` itself (see `filter-info.ts`) instead of planning against the
|
|
1018
|
+
* overlay module. If overlay scans ever route through the overlay module's planner hook, or
|
|
1019
|
+
* its write path starts enforcing nullability, this has to become a real forward that
|
|
1020
|
+
* excludes tombstones.
|
|
1021
|
+
*
|
|
1022
|
+
* The runtime admits exactly one attribute per ALTER COLUMN, so a `setNotNull` change
|
|
1023
|
+
* carries nothing else to forward. A missing overlay `alterSchema` is a no-op, as in
|
|
1024
|
+
* {@link forwardColumnShapeToOverlay}.
|
|
1025
|
+
*/
|
|
1026
|
+
private forwardAlterColumnToOverlay;
|
|
1027
|
+
/**
|
|
1028
|
+
* Discards the deletion markers a pending PRIMARY-KEY re-key collapses onto another staged
|
|
1029
|
+
* row, through the overlay's ordinary write path so its layer chain and savepoint snapshots
|
|
1030
|
+
* stay intact. Two shapes collapse (see {@link PkRekeyContext}): a marker sharing its
|
|
1031
|
+
* post-change key with a live row is that row's before-image — the live row's flush upsert
|
|
1032
|
+
* is the complete net effect, so the marker is dropped; markers alone together on one key
|
|
1033
|
+
* are one deletion, so all but the first are dropped. A group holding two live rows was
|
|
1034
|
+
* already refused by {@link validateOverlayMigration} before the underlying mutated.
|
|
1035
|
+
*
|
|
1036
|
+
* Runs BEFORE the re-key is forwarded to the overlay, and deletes by each marker's OLD
|
|
1037
|
+
* primary key — the overlay is still keyed under the old collation at that moment.
|
|
1038
|
+
*
|
|
1039
|
+
* NOTE: like {@link backfillStagedNotNull}'s rewrites, the drops land in the overlay's
|
|
1040
|
+
* CURRENT savepoint frame. A later `rollback to savepoint` taken BEFORE this ALTER restores
|
|
1041
|
+
* the marker while the table stays re-keyed — the same divergence class that method's NOTE
|
|
1042
|
+
* documents (`bug-rolled-back-rows-violate-surviving-ddl`), not a new hole opened here.
|
|
1043
|
+
*/
|
|
1044
|
+
private dropCollapsedPkRekeyMarkers;
|
|
1045
|
+
/**
|
|
1046
|
+
* The whole overlay rows of every deletion marker {@link dropCollapsedPkRekeyMarkers} will
|
|
1047
|
+
* discard, computed without mutating anything. Re-planning after the drops finds nothing
|
|
1048
|
+
* (each group then holds at most one marker and no marker beside a live row), so the
|
|
1049
|
+
* issuer path — which drops in tier 2 for the pre-flight — can safely run the shared
|
|
1050
|
+
* migrate-step drop again as a no-op.
|
|
1051
|
+
*/
|
|
1052
|
+
private planPkRekeyMarkerDrops;
|
|
1053
|
+
/** Deletes each planned marker by its OLD primary key through the overlay's ordinary write path. */
|
|
1054
|
+
private applyPkRekeyMarkerDrops;
|
|
1055
|
+
/**
|
|
1056
|
+
* Undoes {@link applyPkRekeyMarkerDrops} after a refusal that aborts the whole ALTER —
|
|
1057
|
+
* the overlay pre-flight's or the underlying's (see the tier-2 re-key block in
|
|
1058
|
+
* {@link alterTable}). Reinserts each marker row verbatim: a marker minted for a committed
|
|
1059
|
+
* row carries NULL at every non-key data column, but one made by deleting a row this
|
|
1060
|
+
* transaction had already staged keeps that row's values (the convert-to-tombstone branch
|
|
1061
|
+
* of `IsolatedTable.update`), so a reconstructed-from-PK row would not restore what was
|
|
1062
|
+
* dropped. The reinserts land in the same savepoint frame the drops did, so the pair nets
|
|
1063
|
+
* to nothing.
|
|
1064
|
+
*/
|
|
1065
|
+
private reinsertPkRekeyMarkers;
|
|
1066
|
+
/** The overlay's tombstone-flag column index, or INTERNAL if the schema lost it. */
|
|
1067
|
+
private requireTombstoneIndex;
|
|
1068
|
+
/**
|
|
1069
|
+
* Backfills a `set not null` tightening into an overlay's staged LIVE rows: every
|
|
1070
|
+
* non-tombstone row holding NULL at the tightened column is rewritten with the column's
|
|
1071
|
+
* folded literal DEFAULT through the overlay's ordinary write path, so the overlay's layer
|
|
1072
|
+
* chain and savepoint snapshots stay intact — the overlay-side mirror of the value rewrite
|
|
1073
|
+
* the underlying applies to its committed rows. Tombstone rows are never touched
|
|
1074
|
+
* ({@link stagedLiveRows}).
|
|
1075
|
+
*
|
|
1076
|
+
* Only called with a usable DEFAULT ({@link SetNotNullBackfillContext.hasDefault}); the
|
|
1077
|
+
* no-DEFAULT case rejects (issuer) or poisons (foreign) in {@link validateOverlayMigration}
|
|
1078
|
+
* before any overlay is touched.
|
|
1079
|
+
*
|
|
1080
|
+
* NOTE: these rewrites land in the overlay's CURRENT savepoint frame — each is a normal
|
|
1081
|
+
* staged write. A later `rollback to savepoint` taken BEFORE this ALTER therefore restores
|
|
1082
|
+
* the pre-backfill NULL while the column stays NOT NULL (DDL is not transactional here).
|
|
1083
|
+
* That is the same class of divergence as any rolled-back row violating surviving DDL — a
|
|
1084
|
+
* row un-inserted past an ADD CONSTRAINT, say — tracked by the backlog ticket
|
|
1085
|
+
* `bug-rolled-back-rows-violate-surviving-ddl`, not a new hole opened by this forward.
|
|
824
1086
|
*/
|
|
825
|
-
private
|
|
1087
|
+
private backfillStagedNotNull;
|
|
1088
|
+
/**
|
|
1089
|
+
* Forwards one ADD CONSTRAINT to an already-open overlay, per constraint class:
|
|
1090
|
+
*
|
|
1091
|
+
* - **UNIQUE** is NOT forwarded as a constraint. The AST `TableConstraint` carries no
|
|
1092
|
+
* partial-predicate field, so a bare forward would enforce uniqueness over tombstone rows
|
|
1093
|
+
* too — a UNIQUE whose columns sit inside the primary key would see two deleted rows
|
|
1094
|
+
* (each carrying its PK and placeholder NULLs elsewhere) as duplicates of each other.
|
|
1095
|
+
* Instead it lands as a tombstone-narrowed **unique index** through the same route CREATE
|
|
1096
|
+
* INDEX takes ({@link installOverlayUniqueConstraint}).
|
|
1097
|
+
* - **CHECK** forwards verbatim: the overlay module appends it schema-only (no row scan, no
|
|
1098
|
+
* physical structure). Enforcement is engine-side at DML plan time against the user
|
|
1099
|
+
* table; the overlay's copy exists so a later DROP / RENAME CONSTRAINT resolves it.
|
|
1100
|
+
* - **FOREIGN KEY** is deliberately NOT forwarded. FK enforcement is engine-side
|
|
1101
|
+
* (planner-synthesized EXISTS checks) — the overlay never enforces it — and the overlay
|
|
1102
|
+
* module's ADD arm validates existing child rows through a catalog query by table name,
|
|
1103
|
+
* which the unregistered `_overlay_*` staging table cannot serve. The presence guard in
|
|
1104
|
+
* {@link forwardConstraintNameChangeToOverlay} keeps a later DROP / RENAME of the
|
|
1105
|
+
* unforwarded FK a clean no-op on the overlay.
|
|
1106
|
+
* - **PRIMARY KEY** cannot reach here — both bundled underlyings reject
|
|
1107
|
+
* `add constraint … primary key` with UNSUPPORTED before any overlay work — so it asserts
|
|
1108
|
+
* INTERNAL rather than leaving a silent gap for a third-party underlying that accepts it
|
|
1109
|
+
* (the overlay could not follow; see the alterPrimaryKey handling in {@link alterTable}).
|
|
1110
|
+
*/
|
|
1111
|
+
private forwardAddConstraintToOverlay;
|
|
1112
|
+
/**
|
|
1113
|
+
* Installs one runtime-added UNIQUE constraint on an overlay as a tombstone-narrowed unique
|
|
1114
|
+
* index (see {@link forwardAddConstraintToOverlay} for why a bare constraint forward is
|
|
1115
|
+
* wrong). Column indices and per-column collations are read from the post-alter underlying
|
|
1116
|
+
* schema — the canonical form, positionally identical to the overlay's data columns (the
|
|
1117
|
+
* tombstone flag is appended last). The index is named `constraint name ?? '_uc_<cols>'`,
|
|
1118
|
+
* the memory module's own covering-index naming rule (`implicitIndexNameFor`), so the
|
|
1119
|
+
* derived UNIQUE the overlay module synthesizes from it resolves under the SAME name a
|
|
1120
|
+
* later DROP / RENAME CONSTRAINT forward carries.
|
|
1121
|
+
*
|
|
1122
|
+
* `MemoryTableManager.createIndex` pre-validates the overlay's effective rows against the
|
|
1123
|
+
* narrowed predicate before any mutation, so a staged duplicate raises CONSTRAINT with the
|
|
1124
|
+
* overlay untouched — routed by the caller to INTERNAL (the issuer, whose merged rows the
|
|
1125
|
+
* underlying already judged via {@link issuerEffectiveRows}) or poison (a foreign overlay).
|
|
1126
|
+
* A missing overlay `createIndex` is a no-op, as in the index paths.
|
|
1127
|
+
*/
|
|
1128
|
+
private installOverlayUniqueConstraint;
|
|
1129
|
+
/**
|
|
1130
|
+
* Forwards DROP / RENAME CONSTRAINT to an already-open overlay, presence-guarded: a
|
|
1131
|
+
* constraint the overlay never carried is skipped silently rather than letting the overlay
|
|
1132
|
+
* module's NOTFOUND abort the issuer's already-applied ALTER. The guard is what makes the
|
|
1133
|
+
* unforwarded classes safe ({@link forwardAddConstraintToOverlay}: FOREIGN KEY always, and
|
|
1134
|
+
* UNIQUE under an overlay module without `createIndex`) — their DROP / RENAME is simply a
|
|
1135
|
+
* no-op on the overlay.
|
|
1136
|
+
*
|
|
1137
|
+
* For a UNIQUE the overlay module drops/renames the constraint AND its covering index in
|
|
1138
|
+
* lock-step — both live under the constraint's name (or the `_uc_<cols>` implicit-name
|
|
1139
|
+
* rule), whether copied at overlay creation ({@link createOverlaySchema}) or installed by
|
|
1140
|
+
* {@link installOverlayUniqueConstraint} — so the base and overlay representations stay
|
|
1141
|
+
* resolvable by one name across the whole constraint lifecycle.
|
|
1142
|
+
*/
|
|
1143
|
+
private forwardConstraintNameChangeToOverlay;
|
|
1144
|
+
/** Case-insensitive "does `schema` declare a named constraint of ANY class (CHECK / UNIQUE / FOREIGN KEY)". */
|
|
1145
|
+
private schemaHasNamedConstraint;
|
|
1146
|
+
/**
|
|
1147
|
+
* Carries one overlay across an ALTER PRIMARY KEY, which no overlay can follow in place:
|
|
1148
|
+
* its layer BTrees are keyed by the OLD primary key, and a staged tombstone identifies the
|
|
1149
|
+
* row it deletes BY that key — under a new key a tombstone's identity columns may be
|
|
1150
|
+
* placeholder NULLs, i.e. garbage. So:
|
|
1151
|
+
*
|
|
1152
|
+
* - An overlay WITH staged rows is either the ISSUER's — unreachable, {@link alterTable}
|
|
1153
|
+
* rejected it before the underlying mutated, so assert INTERNAL — or FOREIGN, which is
|
|
1154
|
+
* poisoned exactly like a foreign overlay whose staged values a retype cannot convert.
|
|
1155
|
+
* - A CLEAN overlay stages nothing; it is swapped for a fresh empty staging table built
|
|
1156
|
+
* from the post-alter schema, so later writes in this transaction key by the new primary
|
|
1157
|
+
* key. The fresh table's connection registers lazily at its first write, replaying the
|
|
1158
|
+
* active savepoint stack — correct for a table with no pre-existing staged rows (the
|
|
1159
|
+
* replay hazard is only ever about rows copied beneath it, and there are none).
|
|
1160
|
+
*/
|
|
1161
|
+
private replaceOverlayForPrimaryKeyChange;
|
|
826
1162
|
/**
|
|
827
1163
|
* Creates overlay schema from underlying schema.
|
|
828
1164
|
* Adds tombstone column and uses unique name to avoid conflicts.
|
|
829
1165
|
*
|
|
830
|
-
* Called by IsolatedTable when lazily creating its overlay, and by the
|
|
831
|
-
*
|
|
1166
|
+
* Called by IsolatedTable when lazily creating its overlay, and by the ALTER PRIMARY KEY
|
|
1167
|
+
* clean-overlay swap (`replaceOverlayForPrimaryKeyChange`). Index DDL and every other ALTER
|
|
1168
|
+
* adopt in place — a new UNIQUE arrives through {@link createOverlayIndexSchema} /
|
|
1169
|
+
* {@link overlayPredicate}, which this method shares.
|
|
832
1170
|
*
|
|
833
1171
|
* Every copied secondary index — and every copied UNIQUE constraint, including the ones
|
|
834
1172
|
* a UNIQUE index derives — is narrowed to a PARTIAL structure over live rows only
|
|
@@ -842,8 +1180,43 @@ export declare class IsolationModule implements VirtualTableModule<IsolatedTable
|
|
|
842
1180
|
*
|
|
843
1181
|
* `IsolatedTable.mergedSecondaryIndexQuery` wants exactly the live overlay rows out of
|
|
844
1182
|
* these indexes, so narrowing them is what it already expects.
|
|
1183
|
+
*
|
|
1184
|
+
* NOTE: a base column already named {@link tombstoneColumn} lands here as a SECOND column
|
|
1185
|
+
* of that name, and `columnIndexMap` resolves the name to the appended flag. Harmless
|
|
1186
|
+
* today — every consumer reaches the flag by index, and reads, merged scans, a unique
|
|
1187
|
+
* secondary index over that column and a staged deletion all behave exactly as they do
|
|
1188
|
+
* for a non-colliding name. If anything ever resolves the overlay's columns BY NAME (a
|
|
1189
|
+
* hand-built overlay predicate, a module that re-derives its own index map from the
|
|
1190
|
+
* column list), give the overlay a collision-free flag name here instead. ALTER into the
|
|
1191
|
+
* name is rejected outright — see {@link assertColumnNameNotTombstone} for why that one
|
|
1192
|
+
* cannot be tolerated.
|
|
845
1193
|
*/
|
|
846
1194
|
createOverlaySchema(baseSchema: TableSchema): TableSchema;
|
|
1195
|
+
/**
|
|
1196
|
+
* The overlay-flavored form of one base `IndexSchema`: same name and columns, predicate
|
|
1197
|
+
* narrowed to live rows and rescoped onto the overlay's table name (see
|
|
1198
|
+
* {@link overlayPredicate}).
|
|
1199
|
+
*
|
|
1200
|
+
* Shared by {@link createOverlaySchema}, which maps the whole index set when an overlay is
|
|
1201
|
+
* first created, and {@link createIndex}, which hands a single index to an overlay that is
|
|
1202
|
+
* already open — so both produce structurally identical entries, and the UNIQUE constraint
|
|
1203
|
+
* `MemoryTableManager.createIndex` synthesizes from a unique index inherits the same
|
|
1204
|
+
* predicate either way.
|
|
1205
|
+
*/
|
|
1206
|
+
private createOverlayIndexSchema;
|
|
1207
|
+
/**
|
|
1208
|
+
* `<base predicate, rescoped to the overlay> AND <tombstone> = 0`.
|
|
1209
|
+
*
|
|
1210
|
+
* A partial-index / UNIQUE predicate copied from the base carries a self-qualifier bound to
|
|
1211
|
+
* the base table's name (e.g. `where t.v > 0`). The overlay renames the table to
|
|
1212
|
+
* `overlayName`, so that qualifier now names a DIFFERENT table than the overlay's
|
|
1213
|
+
* MemoryIndex is scoped to — and `compilePredicate` rejects a foreign qualifier at
|
|
1214
|
+
* index-build time (see partial-index-predicate table-qualifier rejection). Rescope the
|
|
1215
|
+
* self-qualifier to the overlay name so it stays a self-reference. A foreign qualifier
|
|
1216
|
+
* cannot occur here: `compilePredicate` already rejected one when the base index/UNIQUE
|
|
1217
|
+
* was created, so every qualifier present is the base name.
|
|
1218
|
+
*/
|
|
1219
|
+
private overlayPredicate;
|
|
847
1220
|
/**
|
|
848
1221
|
* `<tombstoneColumn> = 0` — the partial-structure predicate that scopes an overlay index
|
|
849
1222
|
* or UNIQUE constraint to live rows. Built as an AST rather than parsed from text because
|