@quereus/quereus 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 +5 -4
- package/dist/src/core/database-events.d.ts +99 -1
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +219 -3
- package/dist/src/core/database-events.js.map +1 -1
- package/dist/src/core/database-materialized-views-analysis.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-analysis.js +1 -0
- package/dist/src/core/database-materialized-views-analysis.js.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.js +29 -2
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts +9 -0
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +9 -0
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database.d.ts +7 -1
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +20 -5
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/func/builtins/aggregate.js +11 -11
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.js +15 -85
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/builtins/conversion.d.ts.map +1 -1
- package/dist/src/func/builtins/conversion.js +10 -9
- package/dist/src/func/builtins/conversion.js.map +1 -1
- package/dist/src/func/builtins/datetime.d.ts.map +1 -1
- package/dist/src/func/builtins/datetime.js +28 -10
- package/dist/src/func/builtins/datetime.js.map +1 -1
- package/dist/src/func/builtins/json.d.ts.map +1 -1
- package/dist/src/func/builtins/json.js +23 -17
- package/dist/src/func/builtins/json.js.map +1 -1
- package/dist/src/func/builtins/mutation.js +2 -2
- package/dist/src/func/builtins/mutation.js.map +1 -1
- package/dist/src/func/builtins/return-types.d.ts +327 -0
- package/dist/src/func/builtins/return-types.d.ts.map +1 -0
- package/dist/src/func/builtins/return-types.js +43 -0
- package/dist/src/func/builtins/return-types.js.map +1 -0
- package/dist/src/func/builtins/scalar.d.ts.map +1 -1
- package/dist/src/func/builtins/scalar.js +193 -37
- package/dist/src/func/builtins/scalar.js.map +1 -1
- package/dist/src/func/builtins/schema.d.ts.map +1 -1
- package/dist/src/func/builtins/schema.js +11 -3
- package/dist/src/func/builtins/schema.js.map +1 -1
- package/dist/src/func/builtins/string.d.ts.map +1 -1
- package/dist/src/func/builtins/string.js +4 -2
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/builtins/timespan.d.ts +3 -0
- package/dist/src/func/builtins/timespan.d.ts.map +1 -1
- package/dist/src/func/builtins/timespan.js +15 -11
- package/dist/src/func/builtins/timespan.js.map +1 -1
- package/dist/src/func/registration.d.ts +39 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +236 -41
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +14 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +14 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/parser.d.ts +46 -0
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +160 -44
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/check-extraction.js +37 -4
- package/dist/src/planner/analysis/check-extraction.js.map +1 -1
- package/dist/src/planner/analysis/coarsened-key.d.ts +4 -3
- package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -1
- package/dist/src/planner/analysis/coarsened-key.js +37 -3
- package/dist/src/planner/analysis/coarsened-key.js.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.d.ts +41 -13
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.js +51 -14
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts +0 -7
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +59 -59
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/coverage-prover.d.ts +13 -2
- package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -1
- package/dist/src/planner/analysis/coverage-prover.js +10 -14
- package/dist/src/planner/analysis/coverage-prover.js.map +1 -1
- package/dist/src/planner/analysis/predicate-conjuncts.d.ts +10 -4
- package/dist/src/planner/analysis/predicate-conjuncts.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-conjuncts.js +28 -7
- package/dist/src/planner/analysis/predicate-conjuncts.js.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +8 -27
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
- package/dist/src/planner/analysis/scalar-subqueries.d.ts +22 -3
- package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -1
- package/dist/src/planner/analysis/scalar-subqueries.js +32 -3
- package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -1
- package/dist/src/planner/analysis/set-op-type-merge.d.ts +64 -0
- package/dist/src/planner/analysis/set-op-type-merge.d.ts.map +1 -0
- package/dist/src/planner/analysis/set-op-type-merge.js +97 -0
- package/dist/src/planner/analysis/set-op-type-merge.js.map +1 -0
- package/dist/src/planner/building/alter-table.d.ts.map +1 -1
- package/dist/src/planner/building/alter-table.js +65 -27
- package/dist/src/planner/building/alter-table.js.map +1 -1
- package/dist/src/planner/building/coercion.d.ts +54 -0
- package/dist/src/planner/building/coercion.d.ts.map +1 -0
- package/dist/src/planner/building/coercion.js +88 -0
- package/dist/src/planner/building/coercion.js.map +1 -0
- package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +5 -18
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/expression.d.ts +17 -0
- package/dist/src/planner/building/expression.d.ts.map +1 -1
- package/dist/src/planner/building/expression.js +23 -115
- package/dist/src/planner/building/expression.js.map +1 -1
- package/dist/src/planner/building/function-call.d.ts.map +1 -1
- package/dist/src/planner/building/function-call.js +13 -0
- package/dist/src/planner/building/function-call.js.map +1 -1
- package/dist/src/planner/building/select-aggregates.d.ts +1 -1
- package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
- package/dist/src/planner/building/select-aggregates.js +53 -2
- package/dist/src/planner/building/select-aggregates.js.map +1 -1
- package/dist/src/planner/building/select-compound.d.ts.map +1 -1
- package/dist/src/planner/building/select-compound.js +10 -6
- package/dist/src/planner/building/select-compound.js.map +1 -1
- package/dist/src/planner/building/select-context.d.ts +7 -0
- package/dist/src/planner/building/select-context.d.ts.map +1 -1
- package/dist/src/planner/building/select-context.js +10 -40
- package/dist/src/planner/building/select-context.js.map +1 -1
- package/dist/src/planner/building/select.d.ts +37 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +112 -51
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/cost/conjunct-cost.d.ts +82 -0
- package/dist/src/planner/cost/conjunct-cost.d.ts.map +1 -0
- package/dist/src/planner/cost/conjunct-cost.js +107 -0
- package/dist/src/planner/cost/conjunct-cost.js.map +1 -0
- package/dist/src/planner/cost/index.d.ts +10 -0
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +15 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/mutation/propagate.d.ts.map +1 -1
- package/dist/src/planner/mutation/propagate.js +1 -0
- package/dist/src/planner/mutation/propagate.js.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.d.ts +6 -0
- package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.js +10 -2
- package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
- package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alias-node.js +2 -1
- package/dist/src/planner/nodes/alias-node.js.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.d.ts +24 -8
- package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
- package/dist/src/planner/nodes/asof-scan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/asof-scan-node.js +4 -1
- package/dist/src/planner/nodes/asof-scan-node.js.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.js +2 -1
- package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.js +19 -8
- package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.d.ts +2 -0
- package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.js +17 -7
- package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
- package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cache-node.js +2 -1
- package/dist/src/planner/nodes/cache-node.js.map +1 -1
- package/dist/src/planner/nodes/distinct-node.d.ts +6 -0
- package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/distinct-node.js +10 -2
- package/dist/src/planner/nodes/distinct-node.js.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.js +2 -1
- package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts +5 -0
- package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js +13 -6
- package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +12 -5
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts +6 -0
- package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.js +10 -2
- package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts +25 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +38 -10
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/join-utils.d.ts +78 -0
- package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-utils.js +73 -0
- package/dist/src/planner/nodes/join-utils.js.map +1 -1
- package/dist/src/planner/nodes/key-set-semi-join-node.d.ts +87 -0
- package/dist/src/planner/nodes/key-set-semi-join-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/key-set-semi-join-node.js +141 -0
- package/dist/src/planner/nodes/key-set-semi-join-node.js.map +1 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js +2 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -1
- package/dist/src/planner/nodes/limit-offset.d.ts +6 -0
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +10 -2
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.d.ts +2 -0
- package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.js +21 -8
- package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts +5 -0
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.js +9 -2
- package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.d.ts +1 -0
- package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.js +1 -0
- package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +24 -0
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/project-node.js +2 -1
- package/dist/src/planner/nodes/project-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +3 -2
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.js +2 -1
- package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +5 -18
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts +68 -16
- package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +147 -26
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/planner/nodes/sort.d.ts.map +1 -1
- package/dist/src/planner/nodes/sort.js +3 -1
- package/dist/src/planner/nodes/sort.js.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts +6 -0
- package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.js +10 -2
- package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts +19 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +18 -2
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-function.js +8 -0
- package/dist/src/planner/nodes/window-function.js.map +1 -1
- package/dist/src/planner/nodes/window-node.d.ts +1 -1
- package/dist/src/planner/nodes/window-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-node.js +3 -1
- package/dist/src/planner/nodes/window-node.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts +9 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +126 -6
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/access/rule-key-set-seek.d.ts +68 -0
- package/dist/src/planner/rules/access/rule-key-set-seek.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-key-set-seek.js +343 -0
- package/dist/src/planner/rules/access/rule-key-set-seek.js.map +1 -0
- package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js +2 -2
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts +46 -0
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +54 -13
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js +9 -0
- package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +6 -4
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +49 -49
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.js +54 -58
- package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
- package/dist/src/planner/rules/join/index-nested-loop.d.ts +49 -0
- package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -0
- package/dist/src/planner/rules/join/index-nested-loop.js +285 -0
- package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -0
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +8 -8
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts +10 -4
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js +97 -29
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +15 -5
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts +43 -0
- package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts.map +1 -0
- package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js +119 -0
- package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js.map +1 -0
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts +44 -3
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +142 -18
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.d.ts +73 -0
- package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.d.ts.map +1 -0
- package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.js +205 -0
- package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.js.map +1 -0
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts +14 -11
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +21 -85
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts +10 -2
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +20 -2
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +18 -3
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/shared/access-leaf.d.ts +39 -0
- package/dist/src/planner/rules/shared/access-leaf.d.ts.map +1 -0
- package/dist/src/planner/rules/shared/access-leaf.js +79 -0
- package/dist/src/planner/rules/shared/access-leaf.js.map +1 -0
- package/dist/src/planner/rules/shared/index-style-context.d.ts +17 -0
- package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.js +7 -0
- package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +18 -8
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts +3 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +27 -10
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +24 -6
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +243 -70
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/planner/rules/window/rule-monotonic-window.d.ts +17 -7
- package/dist/src/planner/rules/window/rule-monotonic-window.d.ts.map +1 -1
- package/dist/src/planner/rules/window/rule-monotonic-window.js +124 -87
- package/dist/src/planner/rules/window/rule-monotonic-window.js.map +1 -1
- package/dist/src/planner/scopes/aliased.d.ts.map +1 -1
- package/dist/src/planner/scopes/aliased.js +6 -0
- package/dist/src/planner/scopes/aliased.js.map +1 -1
- package/dist/src/planner/stats/catalog-stats.d.ts +87 -4
- package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
- package/dist/src/planner/stats/catalog-stats.js +250 -28
- package/dist/src/planner/stats/catalog-stats.js.map +1 -1
- package/dist/src/planner/stats/conjunct-selectivity.d.ts +48 -0
- package/dist/src/planner/stats/conjunct-selectivity.d.ts.map +1 -0
- package/dist/src/planner/stats/conjunct-selectivity.js +168 -0
- package/dist/src/planner/stats/conjunct-selectivity.js.map +1 -0
- package/dist/src/planner/stats/index.d.ts +36 -2
- package/dist/src/planner/stats/index.d.ts.map +1 -1
- package/dist/src/planner/stats/index.js.map +1 -1
- package/dist/src/planner/stats/selectivity-combine.d.ts +53 -0
- package/dist/src/planner/stats/selectivity-combine.d.ts.map +1 -0
- package/dist/src/planner/stats/selectivity-combine.js +75 -0
- package/dist/src/planner/stats/selectivity-combine.js.map +1 -0
- package/dist/src/planner/stats/table-cardinality.d.ts +28 -0
- package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -0
- package/dist/src/planner/stats/table-cardinality.js +29 -0
- package/dist/src/planner/stats/table-cardinality.js.map +1 -0
- package/dist/src/planner/util/column-origins.d.ts +79 -0
- package/dist/src/planner/util/column-origins.d.ts.map +1 -0
- package/dist/src/planner/util/column-origins.js +150 -0
- package/dist/src/planner/util/column-origins.js.map +1 -0
- package/dist/src/planner/util/fd-utils.d.ts +32 -8
- package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
- package/dist/src/planner/util/fd-utils.js +46 -9
- package/dist/src/planner/util/fd-utils.js.map +1 -1
- package/dist/src/planner/util/ind-utils.d.ts +56 -17
- package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
- package/dist/src/planner/util/ind-utils.js +82 -21
- package/dist/src/planner/util/ind-utils.js.map +1 -1
- package/dist/src/planner/util/key-utils.d.ts +20 -2
- package/dist/src/planner/util/key-utils.d.ts.map +1 -1
- package/dist/src/planner/util/key-utils.js +37 -3
- package/dist/src/planner/util/key-utils.js.map +1 -1
- package/dist/src/planner/util/row-estimates.d.ts +28 -0
- package/dist/src/planner/util/row-estimates.d.ts.map +1 -0
- package/dist/src/planner/util/row-estimates.js +40 -0
- package/dist/src/planner/util/row-estimates.js.map +1 -0
- package/dist/src/planner/util/row-population.d.ts +42 -0
- package/dist/src/planner/util/row-population.d.ts.map +1 -0
- package/dist/src/planner/util/row-population.js +71 -0
- package/dist/src/planner/util/row-population.js.map +1 -0
- package/dist/src/runtime/deferred-constraint-queue.d.ts +40 -0
- package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.js +90 -9
- package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +39 -1
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +4 -4
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-schema-event.d.ts +42 -0
- package/dist/src/runtime/emit/alter-schema-event.d.ts.map +1 -0
- package/dist/src/runtime/emit/alter-schema-event.js +28 -0
- package/dist/src/runtime/emit/alter-schema-event.js.map +1 -0
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +386 -154
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
- package/dist/src/runtime/emit/analyze.js +35 -3
- package/dist/src/runtime/emit/analyze.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +8 -3
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +4 -38
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +2 -23
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/bloom-join.js +6 -35
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/case.d.ts.map +1 -1
- package/dist/src/runtime/emit/case.js +40 -6
- package/dist/src/runtime/emit/case.js.map +1 -1
- package/dist/src/runtime/emit/cast.d.ts.map +1 -1
- package/dist/src/runtime/emit/cast.js +4 -19
- package/dist/src/runtime/emit/cast.js.map +1 -1
- package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +32 -8
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +90 -50
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-index.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-index.js +4 -3
- package/dist/src/runtime/emit/drop-index.js.map +1 -1
- package/dist/src/runtime/emit/filter.d.ts +18 -0
- package/dist/src/runtime/emit/filter.d.ts.map +1 -1
- package/dist/src/runtime/emit/filter.js +52 -10
- package/dist/src/runtime/emit/filter.js.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.js +2 -2
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/join-key-extractor.d.ts +46 -0
- package/dist/src/runtime/emit/join-key-extractor.d.ts.map +1 -0
- package/dist/src/runtime/emit/join-key-extractor.js +60 -0
- package/dist/src/runtime/emit/join-key-extractor.js.map +1 -0
- package/dist/src/runtime/emit/join.d.ts.map +1 -1
- package/dist/src/runtime/emit/join.js +8 -46
- package/dist/src/runtime/emit/join.js.map +1 -1
- package/dist/src/runtime/emit/key-set-semi-join.d.ts +29 -0
- package/dist/src/runtime/emit/key-set-semi-join.d.ts.map +1 -0
- package/dist/src/runtime/emit/key-set-semi-join.js +149 -0
- package/dist/src/runtime/emit/key-set-semi-join.js.map +1 -0
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +15 -8
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/operand-comparator.d.ts +70 -0
- package/dist/src/runtime/emit/operand-comparator.d.ts.map +1 -0
- package/dist/src/runtime/emit/operand-comparator.js +93 -0
- package/dist/src/runtime/emit/operand-comparator.js.map +1 -0
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +11 -2
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +30 -0
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +153 -52
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/window.js +36 -23
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +5 -2
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +6 -2
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/register.d.ts.map +1 -1
- package/dist/src/runtime/register.js +2 -0
- package/dist/src/runtime/register.js.map +1 -1
- package/dist/src/runtime/types.d.ts +7 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/catalog-persistability.d.ts +19 -16
- package/dist/src/schema/catalog-persistability.d.ts.map +1 -1
- package/dist/src/schema/catalog-persistability.js +76 -20
- package/dist/src/schema/catalog-persistability.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +67 -2
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +194 -3
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/column.d.ts +26 -3
- package/dist/src/schema/column.d.ts.map +1 -1
- package/dist/src/schema/column.js.map +1 -1
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +19 -6
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/function.d.ts +40 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js +10 -2
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/manager.d.ts +84 -7
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +153 -53
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +104 -28
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/table.d.ts +149 -1
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +248 -1
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +5 -3
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +5 -3
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/types/builtin-types.d.ts +16 -0
- package/dist/src/types/builtin-types.d.ts.map +1 -1
- package/dist/src/types/builtin-types.js +76 -12
- package/dist/src/types/builtin-types.js.map +1 -1
- package/dist/src/types/cast-semantics.d.ts +42 -0
- package/dist/src/types/cast-semantics.d.ts.map +1 -1
- package/dist/src/types/cast-semantics.js +64 -0
- package/dist/src/types/cast-semantics.js.map +1 -1
- package/dist/src/types/comparison-coercion.d.ts +101 -0
- package/dist/src/types/comparison-coercion.d.ts.map +1 -0
- package/dist/src/types/comparison-coercion.js +162 -0
- package/dist/src/types/comparison-coercion.js.map +1 -0
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +2 -2
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +7 -0
- package/dist/src/types/logical-type.d.ts.map +1 -1
- package/dist/src/types/logical-type.js.map +1 -1
- package/dist/src/types/validation.d.ts +56 -0
- package/dist/src/types/validation.d.ts.map +1 -1
- package/dist/src/types/validation.js +68 -0
- package/dist/src/types/validation.js.map +1 -1
- package/dist/src/util/coercion.d.ts +8 -0
- package/dist/src/util/coercion.d.ts.map +1 -1
- package/dist/src/util/coercion.js +8 -0
- package/dist/src/util/coercion.js.map +1 -1
- package/dist/src/util/comparison.d.ts +41 -3
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +52 -5
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/key-serializer.d.ts +46 -0
- package/dist/src/util/key-serializer.d.ts.map +1 -1
- package/dist/src/util/key-serializer.js +47 -0
- package/dist/src/util/key-serializer.js.map +1 -1
- package/dist/src/util/value-set.d.ts +21 -0
- package/dist/src/util/value-set.d.ts.map +1 -0
- package/dist/src/util/value-set.js +22 -0
- package/dist/src/util/value-set.js.map +1 -0
- package/dist/src/vtab/best-access-plan.d.ts +87 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js +87 -0
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/capabilities.d.ts +1 -1
- package/dist/src/vtab/events.d.ts +5 -1
- package/dist/src/vtab/events.d.ts.map +1 -1
- package/dist/src/vtab/events.js.map +1 -1
- package/dist/src/vtab/memory/layer/alter-column.d.ts +147 -0
- package/dist/src/vtab/memory/layer/alter-column.d.ts.map +1 -0
- package/dist/src/vtab/memory/layer/alter-column.js +214 -0
- package/dist/src/vtab/memory/layer/alter-column.js.map +1 -0
- package/dist/src/vtab/memory/layer/base.d.ts +6 -4
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +10 -7
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/interface.d.ts +2 -2
- package/dist/src/vtab/memory/layer/interface.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +232 -39
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +657 -519
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +32 -5
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +111 -16
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +154 -51
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +14 -2
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +53 -40
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts +1 -1
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +15 -3
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/module.d.ts +51 -21
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +10 -2
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js +1 -1
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass,
|
|
1
|
+
import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass, shiftSchemaIndicesForDrop, rekeySchemaPrimaryKey } from '../../../schema/table.js';
|
|
2
2
|
import { keyParts } from '../types.js';
|
|
3
3
|
import { BTree } from 'inheritree';
|
|
4
|
+
import { createValueSet } from '../../../util/value-set.js';
|
|
4
5
|
import { StatusCode } from '../../../common/types.js';
|
|
5
6
|
import { BaseLayer, iteratePrimaryRows, populateIndexFromRows, populateIndexFromRowsAsync } from './base.js';
|
|
6
7
|
import { TransactionLayer } from './transaction.js';
|
|
@@ -8,22 +9,35 @@ import { MemoryTableConnection } from './connection.js';
|
|
|
8
9
|
import { MemoryVirtualTableConnection } from '../connection.js';
|
|
9
10
|
import { QuereusError } from '../../../common/errors.js';
|
|
10
11
|
import { ConflictResolution } from '../../../common/constants.js';
|
|
11
|
-
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError } from '../../../schema/constraint-builder.js';
|
|
12
|
+
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError, formatKeyValue } from '../../../schema/constraint-builder.js';
|
|
12
13
|
import { indexEnforcesUnique, uniqueEnforcementCollations, uniqueEnforcementComparators } from '../../../schema/unique-enforcement.js';
|
|
13
14
|
import { generateIndexDDL, generateDropIndexDDL } from '../../../schema/ddl-generator.js';
|
|
14
|
-
import { compareSqlValues, rowsValueIdentical, normalizeCollationName
|
|
15
|
+
import { compareSqlValues, rowsValueIdentical, normalizeCollationName } from '../../../util/comparison.js';
|
|
15
16
|
import { scanLayer as scanLayerImpl } from './scan-layer.js';
|
|
16
17
|
import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues, primaryKeyArity } from '../utils/primary-key.js';
|
|
17
18
|
import { createMemoryTableLoggers } from '../utils/logging.js';
|
|
18
|
-
import {
|
|
19
|
-
import { validateAndParse, coerceRowToSchema } from '../../../types/validation.js';
|
|
20
|
-
import { inferType } from '../../../types/registry.js';
|
|
19
|
+
import { coerceRowToSchema, foldDefaultToType } from '../../../types/validation.js';
|
|
21
20
|
import { compilePredicate } from '../utils/predicate.js';
|
|
22
21
|
import { renameColumnInIndexPredicates } from '../../../schema/rename-rewriter.js';
|
|
23
22
|
import { MemoryIndex } from '../index.js';
|
|
24
23
|
import { convertRowAtIndex, mapRows, mapRowsAsync } from './row-convert.js';
|
|
24
|
+
import { buildAlterColumnPlan, planColumnAttributeChange } from './alter-column.js';
|
|
25
25
|
let tableManagerCounter = 0;
|
|
26
26
|
const logger = createMemoryTableLoggers('layer:manager');
|
|
27
|
+
/**
|
|
28
|
+
* The `_uc_<cols>` auto-name of the implicit covering structure realizing an UNNAMED
|
|
29
|
+
* UNIQUE constraint, resolved over an EXPLICIT column list rather than the manager's
|
|
30
|
+
* live one — `renameColumn` needs the name under the POST-rename columns, which
|
|
31
|
+
* `this.tableSchema` does not carry yet.
|
|
32
|
+
*
|
|
33
|
+
* The name is derived, never recorded, so it MOVES when a covered column is renamed.
|
|
34
|
+
* Every consumer re-derives it (see the catalog's `implicitIndexNameForColumns`, which
|
|
35
|
+
* this mirrors and must stay equal to, and `quereus-store`'s `implicitUniqueIndexName`);
|
|
36
|
+
* keep this the only spelling of the rule inside this file.
|
|
37
|
+
*/
|
|
38
|
+
function implicitIndexNameOver(uc, columns) {
|
|
39
|
+
return `_uc_${uc.columns.map(i => columns[i]?.name ?? String(i)).join('_')}`;
|
|
40
|
+
}
|
|
27
41
|
/** A copy of `items` with `value` inserted at `at` (`at === items.length` ⇒ append). */
|
|
28
42
|
function insertValueAt(items, at, value) {
|
|
29
43
|
const out = items.slice();
|
|
@@ -178,16 +192,36 @@ export class MemoryTableManager {
|
|
|
178
192
|
// it would enforce this non-derived UC under the index's collation rather
|
|
179
193
|
// than the declared one. A collation-mismatched index falls through to a
|
|
180
194
|
// distinct `_uc_*` covering index and coexists as its own constraint.
|
|
181
|
-
|
|
195
|
+
// A filtered (partial) index only covers rows matching its own predicate, so
|
|
196
|
+
// it's never valid backing for an unfiltered UNIQUE — and a filtered UNIQUE
|
|
197
|
+
// already owns its index, so it isn't part of this search either (mirrors
|
|
198
|
+
// `findReusableIndexForUnique` in quereus-store's implicit-unique-index.ts).
|
|
199
|
+
const matchingIndex = uc.predicate ? undefined : existingIndexes.find(idx => !idx.predicate &&
|
|
200
|
+
idx.columns.length === uc.columns.length &&
|
|
182
201
|
idx.columns.every((col, i) => col.index === uc.columns[i]) &&
|
|
183
202
|
this.indexCollationsMatchDeclared(idx, uc));
|
|
203
|
+
// The name this constraint's own structure would take, and whoever already holds
|
|
204
|
+
// it in `newIndexes` — a pre-existing index, or the structure an EARLIER
|
|
205
|
+
// constraint in this same loop just pushed. A held name is ADOPTED, never pushed
|
|
206
|
+
// a second time: two entries under one name is the state `importDDL` warns about
|
|
207
|
+
// (see `SchemaManager.importCatalog`) — `index_info()` then reports neither,
|
|
208
|
+
// `DROP INDEX` answers `no such index`, and a predicate over the covered column
|
|
209
|
+
// stops filtering. No write path can produce a duplicate unnamed UNIQUE any more
|
|
210
|
+
// (`assertNoDuplicateUniqueConstraints` / `assertUniqueConstraintNotDuplicated`
|
|
211
|
+
// refuse it at every declaration site), so this arm is defence in depth for a
|
|
212
|
+
// catalog written before those guards: it degrades to one shared structure and
|
|
213
|
+
// double enforcement rather than to a corrupt index list.
|
|
214
|
+
const wantedName = uc.name ?? this.implicitIndexNameFor(uc);
|
|
215
|
+
const claimedIndex = newIndexes.find(idx => idx.name.toLowerCase() === wantedName.toLowerCase());
|
|
184
216
|
let indexName;
|
|
185
217
|
if (matchingIndex) {
|
|
186
218
|
indexName = matchingIndex.name;
|
|
187
219
|
}
|
|
220
|
+
else if (claimedIndex) {
|
|
221
|
+
indexName = claimedIndex.name;
|
|
222
|
+
}
|
|
188
223
|
else {
|
|
189
|
-
|
|
190
|
-
indexName = uc.name ?? `_uc_${colNames.join('_')}`;
|
|
224
|
+
indexName = wantedName;
|
|
191
225
|
newIndexes.push({
|
|
192
226
|
name: indexName,
|
|
193
227
|
// Carry each column's declared collation so the auto-index — and the
|
|
@@ -220,10 +254,70 @@ export class MemoryTableManager {
|
|
|
220
254
|
const indexName = uc.name ?? this.implicitIndexNameFor(uc);
|
|
221
255
|
return this.implicitCoveringStructures.get(indexName);
|
|
222
256
|
}
|
|
223
|
-
/** Conventional auto-index name for an unnamed UNIQUE constraint
|
|
257
|
+
/** Conventional auto-index name for an unnamed UNIQUE constraint, over the LIVE columns. */
|
|
224
258
|
implicitIndexNameFor(uc) {
|
|
225
|
-
|
|
226
|
-
|
|
259
|
+
return implicitIndexNameOver(uc, this.tableSchema.columns);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The materialized covering-index renames a column rename forces: one per UNNAMED
|
|
263
|
+
* UNIQUE constraint covering the renamed column, from its `_uc_<old cols>` name to
|
|
264
|
+
* its `_uc_<new cols>` one.
|
|
265
|
+
*
|
|
266
|
+
* The auto-name is derived from the live column names and recomputed by every
|
|
267
|
+
* consumer ({@link implicitIndexNameFor} here, the catalog's exposure map behind
|
|
268
|
+
* `schema()` / `index_info()`), so leaving the entry under its pre-rename name
|
|
269
|
+
* severs the constraint↔structure link: the structure stops being recognized as
|
|
270
|
+
* hidden (it surfaces as a user index and becomes droppable, taking the
|
|
271
|
+
* constraint's enforcement structure with it) and
|
|
272
|
+
* {@link getImplicitCoveringStructure} falls through to its column-set fallback.
|
|
273
|
+
*
|
|
274
|
+
* Skipped for constraints that do not move: a NAMED one's structure takes the
|
|
275
|
+
* constraint's name, a `derivedFromIndex` one IS the user's index, and one realized
|
|
276
|
+
* by a REUSED user index has no `_uc_*` entry to rename (that index keeps its own
|
|
277
|
+
* name).
|
|
278
|
+
*
|
|
279
|
+
* A case-only column rename DOES produce a rename here, even though every name
|
|
280
|
+
* comparison folds case: the covering-structure map is keyed by the exact derived
|
|
281
|
+
* string, so leaving `_uc_a` in place while consumers derive `_uc_A` would break
|
|
282
|
+
* {@link getImplicitCoveringStructure} the same way. It cannot collide — an index
|
|
283
|
+
* named `_uc_A` could never have been created alongside `_uc_a` in the first place.
|
|
284
|
+
*
|
|
285
|
+
* Pure — the caller applies the result to the new index list and, once the schema
|
|
286
|
+
* swap has succeeded, to {@link implicitCoveringStructures}.
|
|
287
|
+
*/
|
|
288
|
+
planImplicitCoveringIndexRenames(updatedCols, renamedColIndex) {
|
|
289
|
+
const renames = [];
|
|
290
|
+
const indexes = this.tableSchema.indexes ?? [];
|
|
291
|
+
for (const uc of this.tableSchema.uniqueConstraints ?? []) {
|
|
292
|
+
if (uc.name !== undefined || uc.derivedFromIndex !== undefined)
|
|
293
|
+
continue;
|
|
294
|
+
if (!uc.columns.includes(renamedColIndex))
|
|
295
|
+
continue;
|
|
296
|
+
const oldName = implicitIndexNameOver(uc, this.tableSchema.columns);
|
|
297
|
+
const newName = implicitIndexNameOver(uc, updatedCols);
|
|
298
|
+
if (oldName === newName)
|
|
299
|
+
continue;
|
|
300
|
+
if (!indexes.some(idx => idx.name.toLowerCase() === oldName.toLowerCase()))
|
|
301
|
+
continue;
|
|
302
|
+
renames.push({ oldName, newName });
|
|
303
|
+
}
|
|
304
|
+
return renames;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Re-keys {@link implicitCoveringStructures} for the renames
|
|
308
|
+
* {@link planImplicitCoveringIndexRenames} produced — the map is keyed by
|
|
309
|
+
* constraint identity, which for an unnamed constraint IS the derived index name.
|
|
310
|
+
* Runs only after the schema swap succeeded, so a failed rename leaves the map
|
|
311
|
+
* agreeing with the restored schema.
|
|
312
|
+
*/
|
|
313
|
+
applyImplicitCoveringStructureRenames(renames) {
|
|
314
|
+
for (const { oldName, newName } of renames) {
|
|
315
|
+
const rec = this.implicitCoveringStructures.get(oldName);
|
|
316
|
+
if (!rec)
|
|
317
|
+
continue;
|
|
318
|
+
this.implicitCoveringStructures.delete(oldName);
|
|
319
|
+
this.implicitCoveringStructures.set(newName, { ...rec, indexName: newName });
|
|
320
|
+
}
|
|
227
321
|
}
|
|
228
322
|
/**
|
|
229
323
|
* True when a same-column-set index's per-column collations are
|
|
@@ -504,21 +598,23 @@ export class MemoryTableManager {
|
|
|
504
598
|
connection.clearSavepoints();
|
|
505
599
|
// Emit data change events after successful commit
|
|
506
600
|
if (changes.length > 0 && this.eventEmitter?.emitDataChange) {
|
|
507
|
-
// The
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
// (
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
601
|
+
// The contract's `key` is the primary key projected out of the event's OWN row
|
|
602
|
+
// image — `newRow` for an insert or an update, `oldRow` for a delete — under the
|
|
603
|
+
// schema current at DELIVERY (docs/usage.md § Subscribing to Data Changes).
|
|
604
|
+
// Projecting here rather than replaying the key each write recorded is what
|
|
605
|
+
// makes an in-place rewrite that changes the key's VALUE but not the row's
|
|
606
|
+
// identity (a NOCASE 'apple' → 'APPLE', which `recordUpsert` files under the
|
|
607
|
+
// pre-image key) hand listeners the post-image the table actually holds. It also
|
|
608
|
+
// re-derives the key across a mid-transaction ALTER PRIMARY KEY for free: the
|
|
609
|
+
// pending-change images are already reshaped to the delivered schema, so
|
|
610
|
+
// projecting them through THAT schema's key is the re-key.
|
|
611
|
+
const pkIndices = this.tableSchema.primaryKeyDefinition.map(def => def.index);
|
|
516
612
|
for (const change of changes) {
|
|
517
613
|
const event = {
|
|
518
614
|
type: change.type,
|
|
519
615
|
schemaName: this.schemaName,
|
|
520
616
|
tableName: this._tableName,
|
|
521
|
-
key:
|
|
617
|
+
key: this.eventKeyFromImage(change, pkIndices),
|
|
522
618
|
oldRow: change.oldRow,
|
|
523
619
|
newRow: change.newRow,
|
|
524
620
|
};
|
|
@@ -535,6 +631,25 @@ export class MemoryTableManager {
|
|
|
535
631
|
logger.debugLog(`[Commit ${connection.connectionId}] Released lock for ${this._tableName}`);
|
|
536
632
|
}
|
|
537
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* The delivered `key` of one pending change: the primary key projected out of the change's
|
|
636
|
+
* own row image (`newRow` for an insert or an update, `oldRow` for a delete) through
|
|
637
|
+
* `pkIndices`, the key columns of the schema current at delivery.
|
|
638
|
+
*
|
|
639
|
+
* Best-effort, exactly like the pending-change image reshape it consumes: an image the
|
|
640
|
+
* reshape had to leave at the retired arity cannot be projected, so the event ships with no
|
|
641
|
+
* key and logs rather than emitting an `undefined` key slot a listener would file a row
|
|
642
|
+
* under. (A `PendingChange` always carries at least one image — `recordUpsert` sets
|
|
643
|
+
* `newRow`, `recordDelete` sets `oldRow` — so a missing image means only that.)
|
|
644
|
+
*/
|
|
645
|
+
eventKeyFromImage(change, pkIndices) {
|
|
646
|
+
const image = change.newRow ?? change.oldRow;
|
|
647
|
+
if (image && pkIndices.every(i => i >= 0 && i < image.length)) {
|
|
648
|
+
return pkIndices.map(i => image[i]);
|
|
649
|
+
}
|
|
650
|
+
logger.warn('Commit Transaction', this._tableName, `${change.type} event has no image the delivered primary key can be projected from; delivering it with no key`);
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
538
653
|
/** All layers in `layer`'s parent chain, including `layer` itself. */
|
|
539
654
|
layerChainSet(layer) {
|
|
540
655
|
const chain = new Set();
|
|
@@ -1057,8 +1172,8 @@ export class MemoryTableManager {
|
|
|
1057
1172
|
// finer index (e.g. BINARY over a NOCASE column) no longer collapses onto
|
|
1058
1173
|
// the constraint; resolving by name is robust to `schema.indexes` order
|
|
1059
1174
|
// (the finer index may be listed earlier, having been created first).
|
|
1060
|
-
// Both fall through to the column-set scan
|
|
1061
|
-
// resolve
|
|
1175
|
+
// Both fall through to the column-set scan below when the name does not
|
|
1176
|
+
// resolve — see there for when that actually happens.
|
|
1062
1177
|
if (uc.derivedFromIndex) {
|
|
1063
1178
|
const index = targetLayer.getSecondaryIndex?.(uc.derivedFromIndex);
|
|
1064
1179
|
if (index)
|
|
@@ -1072,14 +1187,40 @@ export class MemoryTableManager {
|
|
|
1072
1187
|
return { kind: 'memory-index', index };
|
|
1073
1188
|
}
|
|
1074
1189
|
}
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1190
|
+
// Fallback: match the covering index by column-set when the by-name
|
|
1191
|
+
// resolution above did not land. Reached more often than "defensive"
|
|
1192
|
+
// suggests — the reuse arms of `ensureUniqueConstraintIndexes` /
|
|
1193
|
+
// `addUniqueConstraint` register an UNNAMED constraint's structure under the
|
|
1194
|
+
// REUSED index's name, which `getImplicitCoveringStructure` (keyed
|
|
1195
|
+
// `_uc_<cols>`) then cannot find; and a reused index that is later dropped
|
|
1196
|
+
// leaves the recorded name unresolvable.
|
|
1197
|
+
//
|
|
1198
|
+
// So this scan must apply the SAME admissibility rule as those two reuse
|
|
1199
|
+
// searches, or it re-opens the hole they close: a FILTERED index holds only
|
|
1200
|
+
// the rows its predicate admits, and `checkSingleUniqueConstraint` skips the
|
|
1201
|
+
// check outright for a row outside the covering index's predicate — adopting
|
|
1202
|
+
// one for an unfiltered UNIQUE silently narrows enforcement to the
|
|
1203
|
+
// predicate's scope. A collation-mismatched index enforces under ITS
|
|
1204
|
+
// collation rather than the declared one, the same way.
|
|
1205
|
+
//
|
|
1206
|
+
// Returning undefined is always SAFE: the caller falls back to the
|
|
1207
|
+
// predicate- and collation-aware full scan, which is slower but exact. So a
|
|
1208
|
+
// filtered constraint does not guess here at all — its own structure is
|
|
1209
|
+
// resolved by name above.
|
|
1210
|
+
if (uc.predicate)
|
|
1211
|
+
return undefined;
|
|
1077
1212
|
for (const idx of schema.indexes) {
|
|
1078
|
-
if (idx.
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1213
|
+
if (idx.predicate)
|
|
1214
|
+
continue;
|
|
1215
|
+
if (idx.columns.length !== uc.columns.length)
|
|
1216
|
+
continue;
|
|
1217
|
+
if (!idx.columns.every((col, i) => col.index === uc.columns[i]))
|
|
1218
|
+
continue;
|
|
1219
|
+
if (!this.indexCollationsMatchDeclared(idx, uc))
|
|
1220
|
+
continue;
|
|
1221
|
+
const index = targetLayer.getSecondaryIndex?.(idx.name);
|
|
1222
|
+
if (index)
|
|
1223
|
+
return { kind: 'memory-index', index };
|
|
1083
1224
|
}
|
|
1084
1225
|
return undefined;
|
|
1085
1226
|
}
|
|
@@ -1315,27 +1456,6 @@ export class MemoryTableManager {
|
|
|
1315
1456
|
objectName: newName,
|
|
1316
1457
|
});
|
|
1317
1458
|
}
|
|
1318
|
-
/** Iterates all committed rows from the current committed layer (for rebuild). */
|
|
1319
|
-
scanAllRows() {
|
|
1320
|
-
const tree = this._currentCommittedLayer.getModificationTree('primary');
|
|
1321
|
-
if (!tree)
|
|
1322
|
-
return [];
|
|
1323
|
-
const rows = [];
|
|
1324
|
-
for (const path of tree.ascending(tree.first())) {
|
|
1325
|
-
rows.push(tree.at(path));
|
|
1326
|
-
}
|
|
1327
|
-
return rows;
|
|
1328
|
-
}
|
|
1329
|
-
/** Inserts a row directly into the base layer (for rebuild, bypasses transaction).
|
|
1330
|
-
* Throws on duplicate primary key. */
|
|
1331
|
-
insertRow(row) {
|
|
1332
|
-
const key = this.primaryKeyFunctions.extractFromRow(row);
|
|
1333
|
-
const path = this.baseLayer.primaryTree.find(key);
|
|
1334
|
-
if (path.on) {
|
|
1335
|
-
throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} PK.`, StatusCode.CONSTRAINT);
|
|
1336
|
-
}
|
|
1337
|
-
this.baseLayer.primaryTree.insert(row);
|
|
1338
|
-
}
|
|
1339
1459
|
/**
|
|
1340
1460
|
* Atomically replaces the entire committed contents with `rows` by building a
|
|
1341
1461
|
* fresh {@link BaseLayer} and swapping it in under the SchemaChange latch.
|
|
@@ -1504,7 +1624,9 @@ export class MemoryTableManager {
|
|
|
1504
1624
|
oldByKey.insert({ key: this.primaryKeyFunctions.extractFromRow(row), row });
|
|
1505
1625
|
}
|
|
1506
1626
|
// New-row keys (same PK comparator) for the delete pass's membership test.
|
|
1507
|
-
|
|
1627
|
+
// Entry is the bare PK value, so it must be a non-freezing set: a
|
|
1628
|
+
// single-column BLOB PK is a `Uint8Array`, which `Object.freeze` rejects.
|
|
1629
|
+
const newKeys = createValueSet(this.comparePrimaryKeys);
|
|
1508
1630
|
// Insert/update/skip-identical pass, in new-row order.
|
|
1509
1631
|
for (const newRow of op.rows) {
|
|
1510
1632
|
const key = this.primaryKeyFunctions.extractFromRow(newRow);
|
|
@@ -1594,7 +1716,7 @@ export class MemoryTableManager {
|
|
|
1594
1716
|
const result = await this.checkSingleUniqueConstraint(layer, schema, uc, change.newRow, newPrimaryKey, ConflictResolution.ABORT, noEvict, /*allowMvCovering*/ false);
|
|
1595
1717
|
if (result) {
|
|
1596
1718
|
const colNames = uc.columns.map(i => schema.columns[i]?.name ?? String(i));
|
|
1597
|
-
throw maintainedTableUniqueViolationError(this.schemaName, this._tableName, uc.name ??
|
|
1719
|
+
throw maintainedTableUniqueViolationError(this.schemaName, this._tableName, uc.name ?? implicitIndexNameOver(uc, schema.columns), colNames, uc.columns.map(i => change.newRow[i]));
|
|
1598
1720
|
}
|
|
1599
1721
|
}
|
|
1600
1722
|
}
|
|
@@ -1628,13 +1750,14 @@ export class MemoryTableManager {
|
|
|
1628
1750
|
throw new QuereusError(`Duplicate column name: ${newColumnSchema.name}`, StatusCode.ERROR);
|
|
1629
1751
|
}
|
|
1630
1752
|
let defaultValue = null;
|
|
1631
|
-
let defaultIsLiteral = false;
|
|
1632
1753
|
const defaultConstraint = columnDefAst.constraints.find(c => c.type === 'default');
|
|
1633
1754
|
if (defaultConstraint && defaultConstraint.expr) {
|
|
1634
|
-
|
|
1755
|
+
// Fold AND convert to the new column's declared type, so the backfilled cell
|
|
1756
|
+
// is the value a fresh INSERT under the same DEFAULT would store. An
|
|
1757
|
+
// unconvertible literal (`integer default 'abc'`) throws MISMATCH here.
|
|
1758
|
+
const folded = foldDefaultToType(defaultConstraint.expr, newColumnSchema.logicalType, newColumnSchema.name);
|
|
1635
1759
|
if (folded !== undefined) {
|
|
1636
1760
|
defaultValue = folded;
|
|
1637
|
-
defaultIsLiteral = true;
|
|
1638
1761
|
}
|
|
1639
1762
|
else {
|
|
1640
1763
|
// A non-literal expression default (e.g. `new.<col>`) is written as NULL
|
|
@@ -1643,13 +1766,27 @@ export class MemoryTableManager {
|
|
|
1643
1766
|
}
|
|
1644
1767
|
}
|
|
1645
1768
|
// Check for NOT NULL constraint (could be explicit or from default behavior).
|
|
1646
|
-
// Allow NOT NULL without
|
|
1647
|
-
//
|
|
1648
|
-
//
|
|
1649
|
-
//
|
|
1769
|
+
// Allow NOT NULL without a value source if the table is empty (SQLite-compatible).
|
|
1770
|
+
//
|
|
1771
|
+
// `!backfillEvaluator` is the load-bearing clause, and mirrors
|
|
1772
|
+
// `StoreModuleBase.alterAddColumn`: an engine-supplied evaluator — a non-foldable
|
|
1773
|
+
// expression default (`new.<col>`) OR a `generated always as` expression — fills
|
|
1774
|
+
// each existing row right after this returns, and NOT NULL is then enforced on the
|
|
1775
|
+
// computed values (`BaseLayer.recreatePrimaryTreeWithNewColumn`, plus the
|
|
1776
|
+
// pending-row pass below). Asking only which KIND of DEFAULT was written used to
|
|
1777
|
+
// reject a NOT NULL generated column on a non-empty table the evaluator could fill,
|
|
1778
|
+
// and (in the other direction) to admit `default null` on a column that is NOT NULL
|
|
1779
|
+
// only by the session `default_column_nullability`. The gate is value-based now, so
|
|
1780
|
+
// it matches `StoreModuleBase.alterAddColumn` word for word.
|
|
1781
|
+
//
|
|
1782
|
+
// NOTE: that agreement — here, in the store module, and in the engine's own
|
|
1783
|
+
// pre-check in `runAddColumn` — is held together only by section 8 of
|
|
1784
|
+
// `test/logic/41.4-alter-add-column-constraints.sqllogic` running under both
|
|
1785
|
+
// `yarn test` and `yarn test:store`. Nothing enforces it mechanically. If a
|
|
1786
|
+
// fourth site ever needs the same rule, hoist it to one shared predicate
|
|
1787
|
+
// rather than adding another copy.
|
|
1650
1788
|
const tableHasRows = this.baseLayer.primaryTree.at(this.baseLayer.primaryTree.first()) !== undefined;
|
|
1651
|
-
|
|
1652
|
-
if (newColumnSchema.notNull && defaultValue === null && !defaultIsLiteral && !hasDefaultExpr && tableHasRows) {
|
|
1789
|
+
if (newColumnSchema.notNull && defaultValue === null && !backfillEvaluator && tableHasRows) {
|
|
1653
1790
|
throw new QuereusError(`Cannot add NOT NULL column '${newColumnSchema.name}' to non-empty table `
|
|
1654
1791
|
+ `'${this.schemaName}.${this._tableName}' without a DEFAULT value`, StatusCode.CONSTRAINT);
|
|
1655
1792
|
}
|
|
@@ -1709,9 +1846,10 @@ export class MemoryTableManager {
|
|
|
1709
1846
|
return insertValueAt(row, targetIndex, value);
|
|
1710
1847
|
});
|
|
1711
1848
|
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
1712
|
-
// A non-foldable DEFAULT (e.g. `new.<col>`)
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1849
|
+
// A per-row value source — a non-foldable DEFAULT (e.g. `new.<col>`) or a
|
|
1850
|
+
// `generated always as` expression — backfills each existing row from its own
|
|
1851
|
+
// value via the engine-supplied evaluator; a literal/NULL default uses the
|
|
1852
|
+
// single folded `defaultValue` for every row.
|
|
1715
1853
|
await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator, targetIndex);
|
|
1716
1854
|
this.tableSchema = finalNewTableSchema;
|
|
1717
1855
|
this.initializePrimaryKeyFunctions();
|
|
@@ -1758,57 +1896,23 @@ export class MemoryTableManager {
|
|
|
1758
1896
|
if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
|
|
1759
1897
|
throw new QuereusError(`Cannot drop PK column "${columnName}".`, StatusCode.CONSTRAINT);
|
|
1760
1898
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
const updatedPrimaryKeyNames = updatedPkDefinition.map(def => updatedColumnsSchema[def.index]?.name).filter(Boolean);
|
|
1766
|
-
// Prune any UNIQUE constraint over the dropped column. A UNIQUE that includes the
|
|
1767
|
-
// dropped column is removed outright (a UNIQUE missing one of its columns is a
|
|
1768
|
-
// different, stronger constraint, not a silently-narrowed one); the auto-built
|
|
1769
|
-
// covering index it backed is torn down with it (see the index exclusion below).
|
|
1770
|
-
// Remaining constraints have their column indices shifted to track the removed slot.
|
|
1771
|
-
// Without this, dropping a uniquely-constrained column (including the ADD COLUMN +
|
|
1772
|
-
// inline-UNIQUE revert path) would strand a constraint whose column index dangles
|
|
1773
|
-
// past the end of the column array.
|
|
1774
|
-
const oldUniqueConstraints = this.tableSchema.uniqueConstraints ?? [];
|
|
1775
|
-
const droppedUcKeys = oldUniqueConstraints
|
|
1776
|
-
.filter(uc => uc.columns.includes(colIndex))
|
|
1777
|
-
.map(uc => uc.name ?? this.implicitIndexNameFor(uc));
|
|
1778
|
-
const remainingUniqueConstraints = oldUniqueConstraints
|
|
1779
|
-
.filter(uc => !uc.columns.includes(colIndex))
|
|
1780
|
-
.map(uc => ({ ...uc, columns: Object.freeze(uc.columns.map(i => i > colIndex ? i - 1 : i)) }));
|
|
1899
|
+
// Renumber every position-bearing field over the removed slot — shared with the
|
|
1900
|
+
// store module's `alterDropColumn`, the mirror of `shiftSchemaIndicesForInsert`.
|
|
1901
|
+
const shifted = shiftSchemaIndicesForDrop(this.tableSchema, colIndex);
|
|
1902
|
+
const updatedPrimaryKeyNames = shifted.primaryKeyDefinition.map(def => shifted.columns[def.index]?.name).filter(Boolean);
|
|
1781
1903
|
// Drop the implicit covering index of each removed constraint outright (matched by
|
|
1782
1904
|
// the same `uc.name ?? '_uc_<cols>'` convention DROP CONSTRAINT uses, so a user
|
|
1783
|
-
// index that merely shares columns is left untouched)
|
|
1784
|
-
//
|
|
1785
|
-
//
|
|
1786
|
-
//
|
|
1787
|
-
//
|
|
1905
|
+
// index that merely shares columns is left untouched). Only this backend needs the
|
|
1906
|
+
// pass: the covering index deliberately carries no `unique: true` flag (enforcement
|
|
1907
|
+
// routes through `uniqueConstraints`), so `shiftSchemaIndicesForDrop`'s own
|
|
1908
|
+
// drop-unique-indexes-outright rule does not see it. A *single*-column covering index
|
|
1909
|
+
// collapses to empty and is dropped by the helper's `length > 0` filter regardless;
|
|
1910
|
+
// this exclusion is what tears down a *multi*-column one, which would otherwise
|
|
1911
|
+
// survive orphaned — narrowed to its surviving columns — in `index_info` and on every
|
|
1912
|
+
// write.
|
|
1913
|
+
const droppedUcKeys = shifted.removedUniqueConstraints.map(uc => uc.name ?? this.implicitIndexNameFor(uc));
|
|
1788
1914
|
const droppedCoveringIndexNames = new Set(droppedUcKeys.map(k => k.toLowerCase()));
|
|
1789
|
-
const updatedIndexes =
|
|
1790
|
-
.filter(idx => !droppedCoveringIndexNames.has(idx.name.toLowerCase()))
|
|
1791
|
-
.map(idx => ({
|
|
1792
|
-
...idx,
|
|
1793
|
-
columns: idx.columns
|
|
1794
|
-
.filter(ic => ic.index !== colIndex)
|
|
1795
|
-
.map(ic => ({ ...ic, index: ic.index > colIndex ? ic.index - 1 : ic.index }))
|
|
1796
|
-
})).filter(idx => idx.columns.length > 0);
|
|
1797
|
-
// Shift or prune the table's own foreign keys. `foreignKeys[].columns` are indices
|
|
1798
|
-
// into THIS table, so they renumber over the removed slot exactly as the PK and the
|
|
1799
|
-
// UNIQUE constraints above do. A foreign key that loses ANY of its child columns is
|
|
1800
|
-
// removed outright, the same call the UNIQUE pruning makes: a key missing one of its
|
|
1801
|
-
// child columns is a different constraint against the parent's key, not a narrowed
|
|
1802
|
-
// one. Both halves matter — left unshifted, a surviving key's index either dangles
|
|
1803
|
-
// past the end of the column array or, worse, silently slides onto an unrelated
|
|
1804
|
-
// column and starts enforcing against the parent there.
|
|
1805
|
-
//
|
|
1806
|
-
// `referencedColumns` is not touched: enforcement resolves the parent indices on
|
|
1807
|
-
// demand from `referencedColumnNames` against the parent's current schema. See
|
|
1808
|
-
// `shiftSchemaIndicesForInsert`, the ADD-COLUMN-at-a-position mirror of this block.
|
|
1809
|
-
const remainingForeignKeys = (this.tableSchema.foreignKeys ?? [])
|
|
1810
|
-
.filter(fk => !fk.columns.includes(colIndex))
|
|
1811
|
-
.map(fk => ({ ...fk, columns: Object.freeze(fk.columns.map(i => i > colIndex ? i - 1 : i)) }));
|
|
1915
|
+
const updatedIndexes = shifted.indexes.filter(idx => !droppedCoveringIndexNames.has(idx.name.toLowerCase()));
|
|
1812
1916
|
// NOTE: the generated-column bookkeeping (`generatedColumnDependencies` /
|
|
1813
1917
|
// `generatedColumnTopoOrder`, also index-keyed) is likewise unshifted, but the
|
|
1814
1918
|
// engine recomputes it from column names right after this returns
|
|
@@ -1818,17 +1922,13 @@ export class MemoryTableManager {
|
|
|
1818
1922
|
// `primaryKey` below is not a TableSchema field at all — nothing reads it.
|
|
1819
1923
|
const finalNewTableSchema = Object.freeze({
|
|
1820
1924
|
...this.tableSchema,
|
|
1821
|
-
columns:
|
|
1822
|
-
columnIndexMap: buildColumnIndexMap(
|
|
1823
|
-
primaryKeyDefinition:
|
|
1925
|
+
columns: shifted.columns,
|
|
1926
|
+
columnIndexMap: buildColumnIndexMap(shifted.columns),
|
|
1927
|
+
primaryKeyDefinition: shifted.primaryKeyDefinition,
|
|
1824
1928
|
primaryKey: Object.freeze(updatedPrimaryKeyNames),
|
|
1825
1929
|
indexes: Object.freeze(updatedIndexes),
|
|
1826
|
-
uniqueConstraints:
|
|
1827
|
-
|
|
1828
|
-
: undefined,
|
|
1829
|
-
foreignKeys: remainingForeignKeys.length > 0
|
|
1830
|
-
? Object.freeze(remainingForeignKeys)
|
|
1831
|
-
: undefined,
|
|
1930
|
+
uniqueConstraints: shifted.uniqueConstraints,
|
|
1931
|
+
foreignKeys: shifted.foreignKeys,
|
|
1832
1932
|
});
|
|
1833
1933
|
// Phase 1 of reaching the open transaction's own pending rows. The rewrite here is
|
|
1834
1934
|
// a pure filter and cannot throw — the split exists for ADD COLUMN's fallible
|
|
@@ -1891,8 +1991,17 @@ export class MemoryTableManager {
|
|
|
1891
1991
|
const defaultNotNull = defaultNullability === 'not_null';
|
|
1892
1992
|
const newColumnSchemaAtIndex = columnDefToSchema(newColumnDefAst, defaultNotNull, 'BINARY', (n) => this.db.isCollationRegistered(n));
|
|
1893
1993
|
const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newColumnSchemaAtIndex : c);
|
|
1994
|
+
// An unnamed UNIQUE's covering index is named `_uc_<column names>`, so renaming a
|
|
1995
|
+
// covered column moves that name — rewrite the materialized ENTRY, not just its
|
|
1996
|
+
// column references, or the structure is orphaned under the old name (see
|
|
1997
|
+
// {@link planImplicitCoveringIndexRenames}). The engine refuses the statement up
|
|
1998
|
+
// front when the post-rename name is already an index here, so no rename below can
|
|
1999
|
+
// collide with a sibling entry.
|
|
2000
|
+
const coveringRenames = this.planImplicitCoveringIndexRenames(updatedCols, colIndex);
|
|
2001
|
+
const renamedTo = new Map(coveringRenames.map(r => [r.oldName.toLowerCase(), r.newName]));
|
|
1894
2002
|
const updatedIndexes = (this.tableSchema.indexes || []).map(idx => ({
|
|
1895
2003
|
...idx,
|
|
2004
|
+
name: renamedTo.get(idx.name.toLowerCase()) ?? idx.name,
|
|
1896
2005
|
columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, name: newColumnName } : ic)
|
|
1897
2006
|
}));
|
|
1898
2007
|
const finalNewTableSchema = Object.freeze({
|
|
@@ -1925,8 +2034,13 @@ export class MemoryTableManager {
|
|
|
1925
2034
|
predicatesRewritten = true;
|
|
1926
2035
|
renameColumnInIndexPredicates(finalNewTableSchema.indexes, this._tableName, oldName, newColumnName, this.schemaName, resolveColumnInSource);
|
|
1927
2036
|
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
2037
|
+
// Rebuilds every secondary index from the new schema, so a renamed covering index
|
|
2038
|
+
// lands under its new key in the base's index map with no extra rebuild here.
|
|
1928
2039
|
await this.baseLayer.handleColumnRename();
|
|
1929
2040
|
this.tableSchema = finalNewTableSchema;
|
|
2041
|
+
// Only now that the swap has succeeded — the catch below restores the old schema
|
|
2042
|
+
// and the map must keep agreeing with it.
|
|
2043
|
+
this.applyImplicitCoveringStructureRenames(coveringRenames);
|
|
1930
2044
|
this.initializePrimaryKeyFunctions();
|
|
1931
2045
|
// The DDL transaction's own layers froze their schema at creation; hand them the
|
|
1932
2046
|
// renamed one, mirroring `handleColumnRename`'s base-side secondary rebuild —
|
|
@@ -1971,18 +2085,31 @@ export class MemoryTableManager {
|
|
|
1971
2085
|
}
|
|
1972
2086
|
}
|
|
1973
2087
|
/**
|
|
1974
|
-
* Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE).
|
|
2088
|
+
* Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE, COLLATE).
|
|
1975
2089
|
* The caller supplies exactly one populated change; multi-attribute combinations
|
|
1976
2090
|
* are rejected by the runtime before reaching this method.
|
|
2091
|
+
*
|
|
2092
|
+
* The body is the ordering contract, and only that: resolve the column → decide the change
|
|
2093
|
+
* and pre-validate it (`alter-column.ts`, which mutates nothing) → probe the re-keyed
|
|
2094
|
+
* structures ({@link validateAlterColumnPlan}) → rebuild the base
|
|
2095
|
+
* ({@link applyAlterColumnToBase}, the first write) → swap this manager's schema →
|
|
2096
|
+
* propagate to open layers ({@link propagateAlterColumnToOpenLayers}) → emit. Each step's
|
|
2097
|
+
* own reasoning lives on the step.
|
|
2098
|
+
*
|
|
2099
|
+
* `validateOnly` runs everything up to (and including) the pre-mutation validation
|
|
2100
|
+
* passes — the effective-row NULL / conversion scans, the re-keyed UNIQUE probe, and
|
|
2101
|
+
* the primary-key re-key legality/representability passes — and returns before the
|
|
2102
|
+
* first mutation, throwing exactly what the real application would throw. The
|
|
2103
|
+
* isolation layer uses it to pre-flight an overlay's migration before the shared
|
|
2104
|
+
* underlying table mutates irreversibly (see `VirtualTable.alterSchema`).
|
|
1977
2105
|
*/
|
|
1978
|
-
async alterColumn(change, rows) {
|
|
2106
|
+
async alterColumn(change, rows, validateOnly = false) {
|
|
1979
2107
|
if (this.isReadOnly)
|
|
1980
2108
|
throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
|
|
1981
2109
|
const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
|
|
1982
2110
|
const release = await this.db.latches.acquire(lockKey);
|
|
1983
2111
|
const originalManagerSchema = this.tableSchema;
|
|
1984
|
-
|
|
1985
|
-
let basePrimaryTreeBeforeRekey = null;
|
|
2112
|
+
const undo = { basePrimaryTree: null };
|
|
1986
2113
|
try {
|
|
1987
2114
|
await this.ensureSchemaChangeSafety();
|
|
1988
2115
|
const colNameLower = change.columnName.toLowerCase();
|
|
@@ -1990,355 +2117,32 @@ export class MemoryTableManager {
|
|
|
1990
2117
|
if (colIndex === -1) {
|
|
1991
2118
|
throw new QuereusError(`Column '${change.columnName}' not found.`, StatusCode.ERROR);
|
|
1992
2119
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
//
|
|
1996
|
-
//
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
//
|
|
2011
|
-
//
|
|
2012
|
-
//
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
// leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
|
|
2018
|
-
let convertNulls = false;
|
|
2019
|
-
if (change.setCollation !== undefined) {
|
|
2020
|
-
const normalized = validateCollationForType(change.setCollation, oldCol.logicalType, change.columnName, (n) => this.db.isCollationRegistered(n));
|
|
2021
|
-
const nameMatches = normalized === (oldCol.collation || 'BINARY');
|
|
2022
|
-
if (nameMatches && oldCol.collationExplicit) {
|
|
2023
|
-
return; // already explicit in the desired collation — nothing to do
|
|
2024
|
-
}
|
|
2025
|
-
// SET COLLATE is a user declaration with the same standing as a
|
|
2026
|
-
// CREATE-time COLLATE clause, so mark the collation explicit (rank 2 in
|
|
2027
|
-
// the comparison lattice) regardless of the column's creation history —
|
|
2028
|
-
// including SET COLLATE binary. When only the name matches but the column
|
|
2029
|
-
// was not yet explicit (a defaulted collation, or one inherited from
|
|
2030
|
-
// session default_collation), flip the flag as a METADATA-ONLY change:
|
|
2031
|
-
// the collation bytes are unchanged, so keep collationChanged false and
|
|
2032
|
-
// skip the physical re-sort / re-key / UNIQUE re-validation below. A
|
|
2033
|
-
// different name takes the full path AND sets the flag.
|
|
2034
|
-
newCol = { ...oldCol, collation: normalized, collationExplicit: true };
|
|
2035
|
-
collationChanged = !nameMatches;
|
|
2036
|
-
}
|
|
2037
|
-
else if (change.setNotNull !== undefined) {
|
|
2038
|
-
if (change.setNotNull === true && !oldCol.notNull) {
|
|
2039
|
-
// Tightening: scan the DDL transaction's EFFECTIVE rows for NULLs — committed rows
|
|
2040
|
-
// overlaid with this transaction's own pending writes — exactly as the setDataType
|
|
2041
|
-
// arm does. A pending NULL the transaction can SEE rejects the ALTER; one only in a
|
|
2042
|
-
// row it has DELETED does not block it. `rows` is the wrapper-supplied effective set
|
|
2043
|
-
// (the isolation overlay); when absent, effectiveDdlRows() is the layered view.
|
|
2044
|
-
const defaultExpr = oldCol.defaultValue;
|
|
2045
|
-
const defaultLiteral = defaultExpr ? tryFoldLiteral(defaultExpr) : undefined;
|
|
2046
|
-
let anyNull = false;
|
|
2047
|
-
if (rows) {
|
|
2048
|
-
for await (const row of rows()) {
|
|
2049
|
-
if (row[colIndex] === null) {
|
|
2050
|
-
anyNull = true;
|
|
2051
|
-
break;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
else {
|
|
2056
|
-
for (const row of this.effectiveDdlRows()) {
|
|
2057
|
-
if (row[colIndex] === null) {
|
|
2058
|
-
anyNull = true;
|
|
2059
|
-
break;
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
if (anyNull) {
|
|
2064
|
-
if (defaultLiteral === undefined || defaultLiteral === null) {
|
|
2065
|
-
throw new QuereusError(`column ${change.columnName} contains NULL values`, StatusCode.CONSTRAINT);
|
|
2066
|
-
}
|
|
2067
|
-
// Backfill: map null → the folded DEFAULT literal, routed through the SAME
|
|
2068
|
-
// base-replacement + open-layer conversion machinery setDataType uses — no
|
|
2069
|
-
// logical-type change, no PK re-key, no in-place base mutation. The old in-place
|
|
2070
|
-
// `tree.upsert` backfill could not fill the transaction's pending rows (they live
|
|
2071
|
-
// in the pending layer, not the base) and mutated a base the open layers derive
|
|
2072
|
-
// from — both fixed by the unified path.
|
|
2073
|
-
const literal = defaultLiteral;
|
|
2074
|
-
valueConvert = (v) => (v === null ? literal : v);
|
|
2075
|
-
convertNulls = true;
|
|
2076
|
-
}
|
|
2077
|
-
newCol = { ...oldCol, notNull: true };
|
|
2078
|
-
}
|
|
2079
|
-
else if (change.setNotNull === false && oldCol.notNull) {
|
|
2080
|
-
if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
|
|
2081
|
-
throw new QuereusError(`Cannot DROP NOT NULL on PRIMARY KEY column '${change.columnName}'`, StatusCode.CONSTRAINT);
|
|
2082
|
-
}
|
|
2083
|
-
newCol = { ...oldCol, notNull: false };
|
|
2084
|
-
}
|
|
2085
|
-
else {
|
|
2086
|
-
// No-op (already in desired state).
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
else if (change.setDataType !== undefined) {
|
|
2091
|
-
const newLogicalType = inferType(change.setDataType);
|
|
2092
|
-
// Gate on logical-type IDENTITY, not the physical storage class: `inferType`
|
|
2093
|
-
// flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`,
|
|
2094
|
-
// `bigint` IS `INTEGER_TYPE`), so an alias retype is schema-only — no scan, no
|
|
2095
|
-
// rewrite, no re-key. Every OTHER retype validates AND normalizes every value,
|
|
2096
|
-
// whether or not the storage class changes: a same-class move (text → date)
|
|
2097
|
-
// must reject values the new type refuses ('hello') and rewrite the rest to
|
|
2098
|
-
// the new type's canonical spelling ('2024-06-05T00:00:00Z' → '2024-06-05'),
|
|
2099
|
-
// or the column holds values no INSERT could have produced — unfindable by
|
|
2100
|
-
// equality (DATE compares BINARY over the stored text) and invisible to UNIQUE.
|
|
2101
|
-
// NOTE: the rewrite is unconditional once the types differ, even when every
|
|
2102
|
-
// converted value comes back byte-identical (already-canonical ISO durations on a
|
|
2103
|
-
// text → timespan). That costs a full base-tree + secondary-index rebuild here, and
|
|
2104
|
-
// a physical rewrite of every row on the store leg. If ALTER on large tables ever
|
|
2105
|
-
// shows up as slow, have the convert pre-pass record whether any value actually
|
|
2106
|
-
// moved and, when none did, fall through to the comparator-only re-key.
|
|
2107
|
-
if (newLogicalType !== oldCol.logicalType) {
|
|
2108
|
-
// Retyping a PRIMARY KEY column moves the key bytes (a class change), the
|
|
2109
|
-
// tree's ordering (a comparator move), or at minimum the key values'
|
|
2110
|
-
// canonical spelling — and neither the base rewrite below nor the
|
|
2111
|
-
// transaction-layer conversion re-keys the primary tree. The engine already
|
|
2112
|
-
// refuses SET DATA TYPE on any PK column (runtime/emit/alter-table.ts);
|
|
2113
|
-
// this guards direct module calls. Reject rather than corrupt — the
|
|
2114
|
-
// type-change analogue of the SET COLLATE primary-key carve-out
|
|
2115
|
-
// (`alter-collate-pk-in-transaction`).
|
|
2116
|
-
if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
|
|
2117
|
-
throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
|
|
2118
|
-
}
|
|
2119
|
-
// Every MemoryIndex builds its comparator from the column's LOGICAL type
|
|
2120
|
-
// (`createTypedComparator(columnSchema.logicalType, …)` in vtab/memory/index.ts),
|
|
2121
|
-
// so a move to a differently-ordering type (text ↔ timespan: 'PT1H' ≡ 'PT60M';
|
|
2122
|
-
// text ↔ date/time/datetime: binary, ignoring the column's collation) re-sorts
|
|
2123
|
-
// every structure keyed by the column. Tracked separately from the value rewrite
|
|
2124
|
-
// because SET COLLATE shares the re-key machinery without rewriting values.
|
|
2125
|
-
comparatorChanged = comparisonSemanticsDiffer(oldCol.logicalType, newLogicalType);
|
|
2126
|
-
const convert = (v) => {
|
|
2127
|
-
try {
|
|
2128
|
-
return validateAndParse(v, newLogicalType, change.columnName);
|
|
2129
|
-
}
|
|
2130
|
-
catch {
|
|
2131
|
-
throw new QuereusError(`Cannot convert value in '${change.columnName}' to ${change.setDataType}`, StatusCode.MISMATCH);
|
|
2132
|
-
}
|
|
2133
|
-
};
|
|
2134
|
-
// Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows
|
|
2135
|
-
// (committed rows overlaid with this transaction's own pending writes): an
|
|
2136
|
-
// unconvertible value the transaction can SEE rejects the ALTER, and one only in
|
|
2137
|
-
// a row it has DELETED does not block it. A throw here leaves the schema, the base
|
|
2138
|
-
// and the transaction untouched. `rows` is the wrapper-supplied effective set (the
|
|
2139
|
-
// isolation overlay); when absent, `effectiveDdlRows()` is the layered view.
|
|
2140
|
-
if (rows) {
|
|
2141
|
-
for await (const row of rows()) {
|
|
2142
|
-
const val = row[colIndex];
|
|
2143
|
-
if (val !== null)
|
|
2144
|
-
convert(val);
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
else {
|
|
2148
|
-
for (const row of this.effectiveDdlRows()) {
|
|
2149
|
-
const val = row[colIndex];
|
|
2150
|
-
if (val !== null)
|
|
2151
|
-
convert(val);
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
// The base rows are converted below, AFTER `updateSchema`, by REPLACING the base
|
|
2155
|
-
// primary tree with a fresh one (see the `valueConvert` branch) — never by an
|
|
2156
|
-
// in-place mutation, which inheritree forbids while the open transaction's layers
|
|
2157
|
-
// derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
|
|
2158
|
-
// replacement and the transaction-layer conversion.
|
|
2159
|
-
valueConvert = convert;
|
|
2160
|
-
}
|
|
2161
|
-
newCol = { ...oldCol, logicalType: newLogicalType };
|
|
2162
|
-
}
|
|
2163
|
-
else if (change.setDefault !== undefined) {
|
|
2164
|
-
newCol = { ...oldCol, defaultValue: change.setDefault };
|
|
2165
|
-
}
|
|
2166
|
-
else {
|
|
2167
|
-
throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
|
|
2168
|
-
}
|
|
2169
|
-
const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newCol : c);
|
|
2170
|
-
// The changes that move the COMPARATOR the structures are keyed by: SET COLLATE
|
|
2171
|
-
// (values untouched) and a retype into a differently-ordering type (which, unless it
|
|
2172
|
-
// is an alias retype, ALSO rewrites values — `valueConvert` — and its full-rebuild
|
|
2173
|
-
// path below subsumes the comparator-only re-sort). Both need fresh IndexSchema
|
|
2174
|
-
// objects and, when no rewrite runs, the re-validate / re-sort / adopt sequence.
|
|
2175
|
-
const structuresRekeyed = collationChanged || comparatorChanged;
|
|
2176
|
-
// Propagate a collation change into every PK-definition entry and index
|
|
2177
|
-
// column that orders by this column, so their comparators re-key under it.
|
|
2178
|
-
const updatedPkDef = collationChanged
|
|
2179
|
-
? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
|
|
2180
|
-
: this.tableSchema.primaryKeyDefinition;
|
|
2181
|
-
// Every re-keying change also REBUILDS the IndexSchema objects, because their identity is
|
|
2182
|
-
// the discriminator `TransactionLayer.adoptSchema` uses to decide an open layer's
|
|
2183
|
-
// MemoryIndex must be replaced rather than kept (an index it holds under an object the new
|
|
2184
|
-
// schema still carries is left alone). SET COLLATE has a field to write — the index
|
|
2185
|
-
// column's own collation. A comparator-only retype has none: an index column carries no
|
|
2186
|
-
// logical type, it reads the column's from `newSchema.columns`, so the fresh object IS the
|
|
2187
|
-
// whole signal. Without it the base rebuilt its trees under the new comparator while the
|
|
2188
|
-
// DDL transaction's own layers went on comparing the old way — and shadowed the base's
|
|
2189
|
-
// rebuilt trees once the pending layer became the committed head.
|
|
2190
|
-
const updatedIndexes = (structuresRekeyed && this.tableSchema.indexes)
|
|
2191
|
-
? this.tableSchema.indexes.map(idx => ({
|
|
2192
|
-
...idx,
|
|
2193
|
-
columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
|
|
2194
|
-
}))
|
|
2195
|
-
: this.tableSchema.indexes;
|
|
2196
|
-
const finalNewTableSchema = Object.freeze({
|
|
2197
|
-
...this.tableSchema,
|
|
2198
|
-
columns: Object.freeze(updatedCols),
|
|
2199
|
-
columnIndexMap: buildColumnIndexMap(updatedCols),
|
|
2200
|
-
primaryKeyDefinition: Object.freeze(updatedPkDef),
|
|
2201
|
-
indexes: updatedIndexes ? Object.freeze(updatedIndexes) : updatedIndexes,
|
|
2202
|
-
});
|
|
2203
|
-
const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
|
|
2204
|
-
// Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows: a pair
|
|
2205
|
-
// the transaction inserted that collides under the NEW schema must reject the
|
|
2206
|
-
// change, and one it has deleted must not block it. A throw here leaves the schema,
|
|
2207
|
-
// the base layer and the index map exactly as they were.
|
|
2208
|
-
//
|
|
2209
|
-
// A value-REWRITING change (`valueConvert`: SET DATA TYPE, or a SET NOT NULL null →
|
|
2210
|
-
// DEFAULT backfill) probes the CONVERTED rows — the same per-row conversion the base
|
|
2211
|
-
// rewrite below applies — under the NEW schema's comparators, so one call covers both
|
|
2212
|
-
// ways an ALTER can collapse two previously-distinct rows: the VALUES converge
|
|
2213
|
-
// (`'1'`/`'01'` → 1; two NULLs → one DEFAULT; '2024-06-05T00:00:00Z' → '2024-06-05')
|
|
2214
|
-
// and/or the COMPARATOR moves (text → timespan makes 'PT1H'/'PT60M' one value). A
|
|
2215
|
-
// same-storage-class retype sets `valueConvert` AND `structuresRekeyed`, so the
|
|
2216
|
-
// rewrite arm is tested FIRST — its converted-row probe subsumes the comparator-only
|
|
2217
|
-
// one. The rebuild that follows is deliberately non-enforcing, so this is the only
|
|
2218
|
-
// guard.
|
|
2219
|
-
//
|
|
2220
|
-
// The comparator-MOVING arm without a rewrite has one trigger left: SET COLLATE. The
|
|
2221
|
-
// values are untouched — hence no `mapRow` — and only the comparator the structures
|
|
2222
|
-
// are keyed by moves. `newSchema` carries the new collations, so the probe indexes
|
|
2223
|
-
// compare as the rebuilt ones will.
|
|
2224
|
-
//
|
|
2225
|
-
// The primary key: SET COLLATE on a PK member gets a stricter pre-pass
|
|
2226
|
-
// (`validateRekeyedPrimaryKey`): no layer in the chain, base included, may hold a
|
|
2227
|
-
// collision. That is what lets every step below succeed unconditionally, and what
|
|
2228
|
-
// `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass
|
|
2229
|
-
// and cannot shadow one: a retype of a PK column is rejected upstream, the backfill
|
|
2230
|
-
// only fires on a column holding NULLs (which a PK member cannot — the engine
|
|
2231
|
-
// enforces NOT NULL on every PK member regardless of the declared nullability), and
|
|
2232
|
-
// `pkColumnRekeyed` is set only by SET COLLATE, which never sets `valueConvert` — so
|
|
2233
|
-
// whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm still runs.
|
|
2234
|
-
// NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK
|
|
2235
|
-
// collision path (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey`
|
|
2236
|
-
// plus a primary-tree re-key of its own.
|
|
2237
|
-
if (valueConvert) {
|
|
2238
|
-
const convert = valueConvert;
|
|
2239
|
-
const nulls = convertNulls;
|
|
2240
|
-
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows,
|
|
2241
|
-
// Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
|
|
2242
|
-
// the probe sees is visible to the transaction, and the setDataType pre-pass above
|
|
2243
|
-
// already proved each one convertible, so a throw is unreachable; surfacing it as
|
|
2244
|
-
// MISMATCH still beats probing a stale value that could mask a collision.
|
|
2245
|
-
row => convertRowAtIndex(row, colIndex, convert, nulls));
|
|
2246
|
-
}
|
|
2247
|
-
else if (structuresRekeyed) {
|
|
2248
|
-
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
|
|
2249
|
-
// NOTE: `validateRekeyedPrimaryKey` deliberately ignores `rows`. It asserts that no
|
|
2250
|
-
// LAYER of this manager's own chain holds a PK collision under the new comparator —
|
|
2251
|
-
// a physical property of the structures it is about to re-key, which a wrapper's
|
|
2252
|
-
// merged row stream cannot speak to. A wrapper's staged rows live in the wrapper's
|
|
2253
|
-
// own overlay, which enforces the PK itself, so the pair "one staged row + one
|
|
2254
|
-
// committed row that collide only under the new collation" is checked by neither
|
|
2255
|
-
// side. Rejecting it here would need the wrapper to expose its overlay's PK set, not
|
|
2256
|
-
// just its merged rows.
|
|
2257
|
-
if (pkColumnRekeyed)
|
|
2258
|
-
this.validateRekeyedPrimaryKey(finalNewTableSchema);
|
|
2259
|
-
}
|
|
2260
|
-
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
2261
|
-
// Both arms rebuild the base's structures under the schema `updateSchema` above just
|
|
2262
|
-
// swapped in — each MemoryIndex rebuilds its comparator from the CURRENT schema's
|
|
2263
|
-
// collation and logical type. The secondary rebuilds are NON-enforcing (the pre-pass
|
|
2264
|
-
// above owns uniqueness, and the base's rows are not a subset of the effective rows);
|
|
2265
|
-
// the SET COLLATE PK rebuild is strict, since a PK collision cannot be represented at
|
|
2266
|
-
// all — the pre-pass has already proved the base collision-free, so the strict
|
|
2267
|
-
// rebuild is a live invariant check, not the enforcement path. It runs LAST so its
|
|
2268
|
-
// throw leaves the live tree intact for the catch's rollback.
|
|
2269
|
-
if (valueConvert) {
|
|
2270
|
-
// SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
|
|
2271
|
-
// the primary tree with a fresh one holding them (not an in-place upsert — inheritree
|
|
2272
|
-
// forbids mutating a base while the open transaction's layers derive from it).
|
|
2273
|
-
// `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
|
|
2274
|
-
// rows under the new comparators, so index-backed lookups see the new values — which
|
|
2275
|
-
// is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
|
|
2276
|
-
// same-storage-class retype sets `structuresRekeyed` too. The old tree is saved for
|
|
2277
|
-
// the catch's rollback, exactly as the PK-rekey path does.
|
|
2278
|
-
// NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
|
|
2279
|
-
// if a wide-index table ever shows this as slow, filter to indexes whose columns
|
|
2280
|
-
// include colIndex. Mirrors the collationChanged path's unconditional rebuild.
|
|
2281
|
-
//
|
|
2282
|
-
// SET DATA TYPE: a base value that fails to convert is kept as-is — validation over
|
|
2283
|
-
// the effective view already rejected any unconvertible value the transaction can SEE,
|
|
2284
|
-
// so a surviving one is shadowed by a pending delete/overwrite and is never read back
|
|
2285
|
-
// (a ROLLBACK re-exposes it under the new type — DDL is not undone by rollback, the
|
|
2286
|
-
// known behavior of `feat-ddl-transaction-capability`).
|
|
2287
|
-
// SET NOT NULL (`convertNulls`): null base values are mapped to the DEFAULT literal.
|
|
2288
|
-
const convertedBaseRows = this.convertBaseRows(colIndex, valueConvert, convertNulls);
|
|
2289
|
-
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
2290
|
-
this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
|
|
2291
|
-
}
|
|
2292
|
-
else if (structuresRekeyed) {
|
|
2293
|
-
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2294
|
-
if (pkColumnRekeyed) {
|
|
2295
|
-
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
2296
|
-
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
this.tableSchema = finalNewTableSchema;
|
|
2120
|
+
// Decide + validate, in that order and both before any mutation: `planColumnAttributeChange`
|
|
2121
|
+
// runs the attribute's own pre-validation (the NULL scan, the convertibility scan, the
|
|
2122
|
+
// primary-key carve-outs) over the effective rows, and `validateAlterColumnPlan` runs the
|
|
2123
|
+
// re-keyed UNIQUE / primary-key probes over the schema it produced. Nothing above
|
|
2124
|
+
// `applyAlterColumnToBase` writes, so a throw from either leaves the table as it was.
|
|
2125
|
+
const attributeChange = await planColumnAttributeChange({
|
|
2126
|
+
schema: this.tableSchema,
|
|
2127
|
+
tableName: this._tableName,
|
|
2128
|
+
columnName: change.columnName,
|
|
2129
|
+
colIndex,
|
|
2130
|
+
effectiveRows: this.effectiveRowSource(rows),
|
|
2131
|
+
isCollationRegistered: (n) => this.db.isCollationRegistered(n),
|
|
2132
|
+
}, change);
|
|
2133
|
+
if (attributeChange === null)
|
|
2134
|
+
return; // already in the requested state — nothing to do
|
|
2135
|
+
const plan = buildAlterColumnPlan(this.tableSchema, colIndex, attributeChange);
|
|
2136
|
+
await this.validateAlterColumnPlan(plan, rows);
|
|
2137
|
+
// Dry run ends here: everything above validates without mutating (the one earlier
|
|
2138
|
+
// side effect, ensureSchemaChangeSafety's committed-layer drain, is semantically
|
|
2139
|
+
// neutral bookkeeping), and everything below mutates. See the method doc.
|
|
2140
|
+
if (validateOnly)
|
|
2141
|
+
return;
|
|
2142
|
+
this.applyAlterColumnToBase(plan, undo);
|
|
2143
|
+
this.tableSchema = plan.newSchema;
|
|
2300
2144
|
this.initializePrimaryKeyFunctions();
|
|
2301
|
-
|
|
2302
|
-
// comparator; the DDL transaction's own layers still inherit the old ones and froze
|
|
2303
|
-
// the old schema at construction. Re-key them, or the rest of the transaction — and
|
|
2304
|
-
// everything after the pending layer becomes the committed head at commit — keeps
|
|
2305
|
-
// comparing under the old collation / old logical type.
|
|
2306
|
-
//
|
|
2307
|
-
// When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
|
|
2308
|
-
// swapped the base primary tree object out from under those layers' copy-on-write
|
|
2309
|
-
// bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
|
|
2310
|
-
// every secondary index (each derives its PK comparator/encoder from the PK
|
|
2311
|
-
// definition). Outside a transaction there are no open layers and both are no-ops.
|
|
2312
|
-
if (valueConvert) {
|
|
2313
|
-
// SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
|
|
2314
|
-
// transaction's own open layers still froze the old schema and hold their own-written
|
|
2315
|
-
// rows unconverted. Swap the schema and convert those values so the rest of the
|
|
2316
|
-
// transaction — and the committed head at commit — read the new value. For SET NOT
|
|
2317
|
-
// NULL this fills the transaction's OWN pending NULL rows (which live in the pending
|
|
2318
|
-
// layer, never reached by the old in-place base upsert). `convertColumn` installs the
|
|
2319
|
-
// new schema and rebuilds every one of the layer's secondary indexes from it, so it
|
|
2320
|
-
// subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
|
|
2321
|
-
// No-op in autocommit.
|
|
2322
|
-
this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
|
|
2323
|
-
}
|
|
2324
|
-
else if (structuresRekeyed) {
|
|
2325
|
-
this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
|
|
2326
|
-
}
|
|
2327
|
-
else {
|
|
2328
|
-
// The remaining changes (SET DEFAULT, DROP NOT NULL, a SET NOT NULL that needed no
|
|
2329
|
-
// backfill, a retype between aliases of one logical type) touch nothing a layer
|
|
2330
|
-
// DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
|
|
2331
|
-
// comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
|
|
2332
|
-
// above the module, off the catalog schema. But the frozen schema OBJECT is itself
|
|
2333
|
-
// observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
|
|
2334
|
-
// into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
|
|
2335
|
-
// stale, a `rollback to savepoint` taken across one of these ALTERs restores a
|
|
2336
|
-
// snapshot the wrap then skips, and the transaction's staged rows are silently
|
|
2337
|
-
// dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
|
|
2338
|
-
// over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
|
|
2339
|
-
// the schema reference.
|
|
2340
|
-
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
2341
|
-
}
|
|
2145
|
+
this.propagateAlterColumnToOpenLayers(plan);
|
|
2342
2146
|
this.eventEmitter?.emitSchemaChange?.({
|
|
2343
2147
|
type: 'alter',
|
|
2344
2148
|
objectType: 'column',
|
|
@@ -2350,20 +2154,20 @@ export class MemoryTableManager {
|
|
|
2350
2154
|
}
|
|
2351
2155
|
catch (e) {
|
|
2352
2156
|
// Restore the prior schema and primary tree, then re-key the secondary indexes back
|
|
2353
|
-
// to it. Both pre-passes
|
|
2354
|
-
// is expected to throw; the restores are the safety net for
|
|
2355
|
-
// for `rebuildPrimaryTreeStrict`'s invariant check, whose
|
|
2356
|
-
// `validateRekeyedPrimaryKey` has already established).
|
|
2157
|
+
// to it. Both pre-passes run before any mutation, so nothing inside
|
|
2158
|
+
// `applyAlterColumnToBase` is expected to throw; the restores are the safety net for
|
|
2159
|
+
// an unexpected one (and for `rebuildPrimaryTreeStrict`'s invariant check, whose
|
|
2160
|
+
// precondition `validateRekeyedPrimaryKey` has already established).
|
|
2357
2161
|
//
|
|
2358
|
-
// NOTE: a throw from a pre-pass (or from `
|
|
2162
|
+
// NOTE: a throw from a pre-pass (or from `set not null`'s NULL scan) mutated nothing,
|
|
2359
2163
|
// so this rebuild only swaps the base's index trees for fresh, content-identical
|
|
2360
2164
|
// ones — an O(rows) cost on a pure rejection. Harmless (a pending layer keeps
|
|
2361
2165
|
// reading its orphaned but content-correct copy-on-write base), but if a rejected
|
|
2362
2166
|
// ALTER on a large table ever shows up as slow, gate the rebuild on a "mutation
|
|
2363
|
-
// started" flag set just before `
|
|
2167
|
+
// started" flag set just before `applyAlterColumnToBase`.
|
|
2364
2168
|
this.baseLayer.updateSchema(originalManagerSchema);
|
|
2365
|
-
if (
|
|
2366
|
-
this.baseLayer.primaryTree =
|
|
2169
|
+
if (undo.basePrimaryTree)
|
|
2170
|
+
this.baseLayer.primaryTree = undo.basePrimaryTree;
|
|
2367
2171
|
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2368
2172
|
this.tableSchema = originalManagerSchema;
|
|
2369
2173
|
this.initializePrimaryKeyFunctions();
|
|
@@ -2374,6 +2178,278 @@ export class MemoryTableManager {
|
|
|
2374
2178
|
release();
|
|
2375
2179
|
}
|
|
2376
2180
|
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Apply `alter table … alter primary key` in place — the table's rows, the open
|
|
2183
|
+
* transaction's pending writes, and its pending change events all survive under the new
|
|
2184
|
+
* key. Follows {@link alterColumn}'s ordering contract: resolve and pre-validate the new
|
|
2185
|
+
* definition (mutating nothing) → probe the re-keyed primary structures
|
|
2186
|
+
* ({@link validateRekeyedPrimaryKey}, the same pass the `set collate` re-key runs) →
|
|
2187
|
+
* rebuild the base → swap this manager's schema → propagate to the open transaction's
|
|
2188
|
+
* layers → emit. What differs from the `set collate` re-key is WHAT moves: the key's
|
|
2189
|
+
* columns rather than its comparator, so each open layer's net deletions and its pending
|
|
2190
|
+
* event log must be re-derived from row images — a two-phase prepare/install pair
|
|
2191
|
+
* ({@link TransactionLayer.prepareRekeyedPrimaryKeyColumns}), with every prepare running
|
|
2192
|
+
* BEFORE the first mutation because a deletion's image resolves only through the intact
|
|
2193
|
+
* old layer chain.
|
|
2194
|
+
*
|
|
2195
|
+
* `validateOnly` runs everything up to (and including) the pre-mutation validation and
|
|
2196
|
+
* returns before the first mutation, throwing exactly what the real application would
|
|
2197
|
+
* throw — the same dry-run contract as {@link alterColumn}'s, for a wrapper (the
|
|
2198
|
+
* isolation layer) that must pre-flight before a shared underlying mutates irreversibly.
|
|
2199
|
+
*/
|
|
2200
|
+
async alterPrimaryKey(newPkColumns, rows, validateOnly = false) {
|
|
2201
|
+
if (this.isReadOnly)
|
|
2202
|
+
throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
|
|
2203
|
+
const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
|
|
2204
|
+
const release = await this.db.latches.acquire(lockKey);
|
|
2205
|
+
const originalManagerSchema = this.tableSchema;
|
|
2206
|
+
const undo = { basePrimaryTree: null };
|
|
2207
|
+
try {
|
|
2208
|
+
await this.ensureSchemaChangeSafety();
|
|
2209
|
+
// NOTE: no early return for a definition equal to the current one (unlike
|
|
2210
|
+
// `alterColumn`'s already-in-the-requested-state exit) — the statement is rare and
|
|
2211
|
+
// its only producer, the declarative differ, emits it solely on a genuine change.
|
|
2212
|
+
// If a caller ever re-issues an unchanged key, add the comparison here: the work
|
|
2213
|
+
// below is a full O(rows × layers) rebuild.
|
|
2214
|
+
const newSchema = this.buildRekeyedPrimaryKeySchema(newPkColumns);
|
|
2215
|
+
// Pre-validate before any mutation: the effective rows decide whether the change
|
|
2216
|
+
// is LEGAL (CONSTRAINT on a visible collision), the manager's own layer chain
|
|
2217
|
+
// whether the re-keyed trees can PHYSICALLY carry it (BUSY when only rows a
|
|
2218
|
+
// rollback must restore collide). A throw leaves everything as it was.
|
|
2219
|
+
await this.validateRekeyedPrimaryKey(newSchema, rows);
|
|
2220
|
+
// Dry run ends here: everything above validates without mutating, everything
|
|
2221
|
+
// below mutates. See the method doc.
|
|
2222
|
+
if (validateOnly)
|
|
2223
|
+
return;
|
|
2224
|
+
// Compute every open layer's re-derived net writes and event log BEFORE the
|
|
2225
|
+
// first mutation: a net deletion's new key comes from the parent's row image at
|
|
2226
|
+
// the old key, which only the intact old chain can still resolve.
|
|
2227
|
+
const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
|
|
2228
|
+
const plans = this.openTransactionLayersOldestFirst().map(layer => ({
|
|
2229
|
+
layer,
|
|
2230
|
+
prepared: layer.prepareRekeyedPrimaryKeyColumns(newPkFunctions),
|
|
2231
|
+
}));
|
|
2232
|
+
// Base rebuild, mirroring alterColumn's `set collate` arm: secondary indexes
|
|
2233
|
+
// first (each entry's key encoding embeds the primary key), then the strict
|
|
2234
|
+
// primary rebuild LAST so its throw — a live invariant check; the pre-pass has
|
|
2235
|
+
// already proven the base collision-free — leaves the live tree intact for the
|
|
2236
|
+
// rollback below.
|
|
2237
|
+
this.baseLayer.updateSchema(newSchema);
|
|
2238
|
+
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2239
|
+
undo.basePrimaryTree = this.baseLayer.primaryTree;
|
|
2240
|
+
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
2241
|
+
this.tableSchema = newSchema;
|
|
2242
|
+
this.initializePrimaryKeyFunctions();
|
|
2243
|
+
// Oldest-first (the plans were gathered in that order): each layer's rebuilt tree
|
|
2244
|
+
// inherits copy-on-write from its parent's NEW one. Synchronous and mutation-only.
|
|
2245
|
+
for (const { layer, prepared } of plans) {
|
|
2246
|
+
layer.installRekeyedPrimaryKeyColumns(newSchema, prepared);
|
|
2247
|
+
}
|
|
2248
|
+
this.eventEmitter?.emitSchemaChange?.({
|
|
2249
|
+
type: 'alter',
|
|
2250
|
+
objectType: 'table',
|
|
2251
|
+
schemaName: this.schemaName,
|
|
2252
|
+
objectName: this._tableName,
|
|
2253
|
+
});
|
|
2254
|
+
logger.operation('Alter Primary Key', this._tableName, {
|
|
2255
|
+
columns: newSchema.primaryKeyDefinition.map(def => def.index),
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
catch (e) {
|
|
2259
|
+
// Restore the prior schema and primary tree, then re-key the secondary indexes
|
|
2260
|
+
// back to it — the same safety net as alterColumn's catch: both pre-passes run
|
|
2261
|
+
// before any mutation, so nothing in the apply steps is expected to throw.
|
|
2262
|
+
this.baseLayer.updateSchema(originalManagerSchema);
|
|
2263
|
+
if (undo.basePrimaryTree)
|
|
2264
|
+
this.baseLayer.primaryTree = undo.basePrimaryTree;
|
|
2265
|
+
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2266
|
+
this.tableSchema = originalManagerSchema;
|
|
2267
|
+
this.initializePrimaryKeyFunctions();
|
|
2268
|
+
logger.error('Alter Primary Key', this._tableName, e);
|
|
2269
|
+
throw e;
|
|
2270
|
+
}
|
|
2271
|
+
finally {
|
|
2272
|
+
release();
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Resolve `alter primary key`'s column list against the current schema — bounds, no
|
|
2277
|
+
* duplicates, every member NOT NULL — and return the re-keyed schema. The engine's
|
|
2278
|
+
* emitter validates the same three by column NAME before dispatch; re-checking by index
|
|
2279
|
+
* here keeps a wrapper driving the module API directly (the isolation layer) from
|
|
2280
|
+
* installing an unkeyable definition. The schema construction itself (definition +
|
|
2281
|
+
* per-column flag rebuild, member collations carried) is delegated to the shared
|
|
2282
|
+
* {@link rekeySchemaPrimaryKey}, which the store's native arm uses too.
|
|
2283
|
+
*/
|
|
2284
|
+
buildRekeyedPrimaryKeySchema(newPkColumns) {
|
|
2285
|
+
const columns = this.tableSchema.columns;
|
|
2286
|
+
const seen = new Set();
|
|
2287
|
+
for (const pk of newPkColumns) {
|
|
2288
|
+
if (pk.index < 0 || pk.index >= columns.length) {
|
|
2289
|
+
throw new QuereusError(`Primary key column index ${pk.index} is out of bounds for table '${this._tableName}'.`, StatusCode.ERROR);
|
|
2290
|
+
}
|
|
2291
|
+
if (seen.has(pk.index)) {
|
|
2292
|
+
throw new QuereusError(`Duplicate column '${columns[pk.index].name}' in PRIMARY KEY definition`, StatusCode.ERROR);
|
|
2293
|
+
}
|
|
2294
|
+
seen.add(pk.index);
|
|
2295
|
+
if (!columns[pk.index].notNull) {
|
|
2296
|
+
throw new QuereusError(`Column '${columns[pk.index].name}' must be NOT NULL to participate in PRIMARY KEY`, StatusCode.CONSTRAINT);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
return rekeySchemaPrimaryKey(this.tableSchema, newPkColumns);
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* The re-keyed-structure pre-pass for {@link alterColumn}, over the DDL transaction's EFFECTIVE
|
|
2303
|
+
* rows: a pair the transaction inserted that collides under the NEW schema must reject the
|
|
2304
|
+
* change, and one it has deleted must not block it. Runs before any mutation, so a throw leaves
|
|
2305
|
+
* the schema, the base layer and the index map exactly as they were.
|
|
2306
|
+
*
|
|
2307
|
+
* A value-REWRITING change (`set data type`, or a `set not null` null → DEFAULT backfill) probes
|
|
2308
|
+
* the CONVERTED rows — the same per-row conversion the base rewrite applies — under the NEW
|
|
2309
|
+
* schema's comparators, so one call covers both ways an ALTER can collapse two previously-distinct
|
|
2310
|
+
* rows: the VALUES converge (`'1'`/`'01'` → 1; two NULLs → one DEFAULT;
|
|
2311
|
+
* '2024-06-05T00:00:00Z' → '2024-06-05') and/or the COMPARATOR moves (text → timespan makes
|
|
2312
|
+
* 'PT1H'/'PT60M' one value). A same-storage-class retype sets both `rewrite` and
|
|
2313
|
+
* `structuresRekeyed`, so the rewrite arm is tested FIRST — its converted-row probe subsumes the
|
|
2314
|
+
* comparator-only one. The rebuild that follows is deliberately non-enforcing, so this is the
|
|
2315
|
+
* only guard.
|
|
2316
|
+
*
|
|
2317
|
+
* The comparator-MOVING arm without a rewrite has one trigger left: `set collate`. The values are
|
|
2318
|
+
* untouched — hence no `mapRow` — and only the comparator the structures are keyed by moves.
|
|
2319
|
+
* `newSchema` carries the new collations, so the probe indexes compare as the rebuilt ones will.
|
|
2320
|
+
*
|
|
2321
|
+
* The primary key: `set collate` on a PK member gets a stricter pre-pass
|
|
2322
|
+
* ({@link validateRekeyedPrimaryKey}): no layer in the chain, base included, may hold a collision.
|
|
2323
|
+
* That is what lets every apply step succeed unconditionally, and what
|
|
2324
|
+
* `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass and cannot
|
|
2325
|
+
* shadow one: a retype of a PK column is rejected upstream, the backfill only fires on a column
|
|
2326
|
+
* holding NULLs (which a PK member cannot — the engine enforces NOT NULL on every PK member
|
|
2327
|
+
* regardless of the declared nullability), and `pkColumnRekeyed` is set only by `set collate`,
|
|
2328
|
+
* which never sets `rewrite` — so whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm
|
|
2329
|
+
* still runs.
|
|
2330
|
+
* NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK collision path
|
|
2331
|
+
* (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey` plus a primary-tree re-key
|
|
2332
|
+
* of its own.
|
|
2333
|
+
*/
|
|
2334
|
+
async validateAlterColumnPlan(plan, rows) {
|
|
2335
|
+
if (plan.rewrite) {
|
|
2336
|
+
const { convert, convertNulls } = plan.rewrite;
|
|
2337
|
+
await this.validateRekeyedUniqueStructures(plan.newSchema, plan.colIndex, rows,
|
|
2338
|
+
// Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
|
|
2339
|
+
// the probe sees is visible to the transaction, and the `set data type` pre-pass
|
|
2340
|
+
// already proved each one convertible, so a throw is unreachable; surfacing it as
|
|
2341
|
+
// MISMATCH still beats probing a stale value that could mask a collision.
|
|
2342
|
+
row => convertRowAtIndex(row, plan.colIndex, convert, convertNulls));
|
|
2343
|
+
}
|
|
2344
|
+
else if (plan.structuresRekeyed) {
|
|
2345
|
+
await this.validateRekeyedUniqueStructures(plan.newSchema, plan.colIndex, rows);
|
|
2346
|
+
// Unlike the secondary-structure pass above — which only ever judges the effective
|
|
2347
|
+
// rows — the PK pass judges TWO row sets: the effective rows (`rows` when a wrapper
|
|
2348
|
+
// supplies them) decide whether the change is LEGAL, and this manager's own layer
|
|
2349
|
+
// chain decides whether the re-keyed trees can PHYSICALLY carry it. See
|
|
2350
|
+
// `validateRekeyedPrimaryKey` for why the sets differ and which status each raises.
|
|
2351
|
+
if (plan.pkColumnRekeyed)
|
|
2352
|
+
await this.validateRekeyedPrimaryKey(plan.newSchema, rows);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* First mutation of {@link alterColumn}: install the new schema on the base layer and rebuild its
|
|
2357
|
+
* structures under it — each MemoryIndex rebuilds its comparator from the CURRENT schema's
|
|
2358
|
+
* collation and logical type. The secondary rebuilds are NON-enforcing ({@link validateAlterColumnPlan}
|
|
2359
|
+
* owns uniqueness, and the base's rows are not a subset of the effective rows); the `set collate`
|
|
2360
|
+
* PK rebuild is strict, since a PK collision cannot be represented at all — the pre-pass has
|
|
2361
|
+
* already proved the base collision-free, so the strict rebuild is a live invariant check, not
|
|
2362
|
+
* the enforcement path. It runs LAST so its throw leaves the live tree intact for the rollback.
|
|
2363
|
+
*
|
|
2364
|
+
* Records the primary tree it is about to REPLACE in `undo` *before* replacing it, so a throw
|
|
2365
|
+
* from either rebuild still leaves the caller's `catch` a tree to restore.
|
|
2366
|
+
*/
|
|
2367
|
+
applyAlterColumnToBase(plan, undo) {
|
|
2368
|
+
this.baseLayer.updateSchema(plan.newSchema);
|
|
2369
|
+
if (plan.rewrite) {
|
|
2370
|
+
// `set data type` / `set not null` backfill: convert the committed base rows and REPLACE
|
|
2371
|
+
// the primary tree with a fresh one holding them (not an in-place upsert — inheritree
|
|
2372
|
+
// forbids mutating a base while the open transaction's layers derive from it).
|
|
2373
|
+
// `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
|
|
2374
|
+
// rows under the new comparators, so index-backed lookups see the new values — which
|
|
2375
|
+
// is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
|
|
2376
|
+
// same-storage-class retype sets `structuresRekeyed` too.
|
|
2377
|
+
// NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
|
|
2378
|
+
// if a wide-index table ever shows this as slow, filter to indexes whose columns
|
|
2379
|
+
// include colIndex. Mirrors the `set collate` path's unconditional rebuild.
|
|
2380
|
+
//
|
|
2381
|
+
// `set data type`: a base value that fails to convert is kept as-is — validation over
|
|
2382
|
+
// the effective view already rejected any unconvertible value the transaction can SEE,
|
|
2383
|
+
// so a surviving one is shadowed by a pending delete/overwrite and is never read back
|
|
2384
|
+
// (a ROLLBACK re-exposes it under the new type — DDL is not undone by rollback, the
|
|
2385
|
+
// known behavior of `feat-ddl-transaction-capability`).
|
|
2386
|
+
// `set not null` (`convertNulls`): null base values are mapped to the DEFAULT literal.
|
|
2387
|
+
const convertedBaseRows = this.convertBaseRows(plan.colIndex, plan.rewrite.convert, plan.rewrite.convertNulls);
|
|
2388
|
+
undo.basePrimaryTree = this.baseLayer.primaryTree;
|
|
2389
|
+
this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
|
|
2390
|
+
}
|
|
2391
|
+
else if (plan.structuresRekeyed) {
|
|
2392
|
+
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2393
|
+
if (plan.pkColumnRekeyed) {
|
|
2394
|
+
undo.basePrimaryTree = this.baseLayer.primaryTree;
|
|
2395
|
+
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Hand the post-{@link alterColumn} schema to the DDL transaction's own open layers.
|
|
2401
|
+
*
|
|
2402
|
+
* The base rebuild handed every secondary index a fresh tree under the new comparator; those
|
|
2403
|
+
* layers still inherit the old ones and froze the old schema at construction. Re-key them, or
|
|
2404
|
+
* the rest of the transaction — and everything after the pending layer becomes the committed
|
|
2405
|
+
* head at commit — keeps comparing under the old collation / old logical type.
|
|
2406
|
+
*
|
|
2407
|
+
* When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also swapped the
|
|
2408
|
+
* base primary tree object out from under those layers' copy-on-write bases and invalidated their
|
|
2409
|
+
* `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus every secondary index (each derives its PK
|
|
2410
|
+
* comparator/encoder from the PK definition). Outside a transaction there are no open layers and
|
|
2411
|
+
* every arm is a no-op.
|
|
2412
|
+
*/
|
|
2413
|
+
propagateAlterColumnToOpenLayers(plan) {
|
|
2414
|
+
if (plan.rewrite) {
|
|
2415
|
+
// `set data type` / `set not null` backfill converted the base; the open layers still hold
|
|
2416
|
+
// their own-written rows unconverted. Swap the schema and convert those values so the rest
|
|
2417
|
+
// of the transaction — and the committed head at commit — read the new value. For
|
|
2418
|
+
// `set not null` this fills the transaction's OWN pending NULL rows (which live in the
|
|
2419
|
+
// pending layer, never reached by an in-place base upsert). `convertColumn` installs the
|
|
2420
|
+
// new schema and rebuilds every one of the layer's secondary indexes from it, so it
|
|
2421
|
+
// subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
|
|
2422
|
+
this.convertColumnOnOpenLayers(plan.newSchema, plan.colIndex, plan.rewrite.convert, plan.rewrite.convertNulls);
|
|
2423
|
+
}
|
|
2424
|
+
else if (plan.structuresRekeyed) {
|
|
2425
|
+
this.adoptSchemaOnOpenLayers(plan.newSchema, plan.pkColumnRekeyed);
|
|
2426
|
+
}
|
|
2427
|
+
else {
|
|
2428
|
+
// The remaining changes (`set default`, `drop not null`, a `set not null` that needed no
|
|
2429
|
+
// backfill, a retype between aliases of one logical type) touch nothing a layer
|
|
2430
|
+
// DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
|
|
2431
|
+
// comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
|
|
2432
|
+
// above the module, off the catalog schema. But the frozen schema OBJECT is itself
|
|
2433
|
+
// observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
|
|
2434
|
+
// into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
|
|
2435
|
+
// stale, a `rollback to savepoint` taken across one of these ALTERs restores a
|
|
2436
|
+
// snapshot the wrap then skips, and the transaction's staged rows are silently
|
|
2437
|
+
// dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
|
|
2438
|
+
// over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
|
|
2439
|
+
// the schema reference.
|
|
2440
|
+
this.adoptSchemaOnOpenLayers(plan.newSchema);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* The rows the `alter column` pre-validation scans judge: `rows` when a wrapper module supplies
|
|
2445
|
+
* them (the isolation overlay holds the transaction's pending rows outside this manager, so only
|
|
2446
|
+
* it can name the rows the issuing connection actually sees), else this manager's own layered
|
|
2447
|
+
* view ({@link effectiveDdlRows}) — which stays a SYNC iterable on purpose, so a scan over it
|
|
2448
|
+
* cannot be interleaved with another connection's write. See `scanEffectiveRows`.
|
|
2449
|
+
*/
|
|
2450
|
+
effectiveRowSource(rows) {
|
|
2451
|
+
return rows ?? (() => this.effectiveDdlRows());
|
|
2452
|
+
}
|
|
2377
2453
|
async createIndex(newIndexSchemaEntry, ifNotExistsFromAst, rows) {
|
|
2378
2454
|
if (this.isReadOnly)
|
|
2379
2455
|
throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
|
|
@@ -2774,7 +2850,11 @@ export class MemoryTableManager {
|
|
|
2774
2850
|
// would under-enforce this non-derived UNIQUE. A collation mismatch falls
|
|
2775
2851
|
// through to build the distinct `_uc_*` covering index; the user index keeps
|
|
2776
2852
|
// enforcing its own (stricter) uniqueness independently (matches SQLite).
|
|
2777
|
-
|
|
2853
|
+
// A FILTERED (partial) unique index only guarantees uniqueness among the rows
|
|
2854
|
+
// its own predicate admits, so it can't back a full (unfiltered) UNIQUE either —
|
|
2855
|
+
// same reasoning as `ensureUniqueConstraintIndexes` above.
|
|
2856
|
+
const matchingUniqueIndex = uc.predicate ? undefined : existingIndexes.find(idx => idx.unique &&
|
|
2857
|
+
!idx.predicate &&
|
|
2778
2858
|
idx.columns.length === uc.columns.length &&
|
|
2779
2859
|
idx.columns.every((col, i) => col.index === uc.columns[i]) &&
|
|
2780
2860
|
this.indexCollationsMatchDeclared(idx, uc));
|
|
@@ -2794,8 +2874,7 @@ export class MemoryTableManager {
|
|
|
2794
2874
|
this.adoptSchemaOnOpenLayers(newSchema);
|
|
2795
2875
|
return;
|
|
2796
2876
|
}
|
|
2797
|
-
const
|
|
2798
|
-
const indexName = uc.name ?? `_uc_${colNames.join('_')}`;
|
|
2877
|
+
const indexName = uc.name ?? implicitIndexNameOver(uc, columns);
|
|
2799
2878
|
const indexSchema = {
|
|
2800
2879
|
name: indexName,
|
|
2801
2880
|
// Carry per-column collation so enforcement honors e.g. NOCASE (mirrors
|
|
@@ -3113,24 +3192,41 @@ export class MemoryTableManager {
|
|
|
3113
3192
|
}
|
|
3114
3193
|
}
|
|
3115
3194
|
/**
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
3195
|
+
* The primary-key re-key pre-pass, shared by both statements that move the key:
|
|
3196
|
+
* `alter column … set collate` on a PK member (the comparator moves) and
|
|
3197
|
+
* `alter table … alter primary key` (the key columns move — {@link alterPrimaryKey}).
|
|
3198
|
+
* Runs before anything is mutated, so a rejection leaves the table, the schema and the
|
|
3199
|
+
* transaction untouched.
|
|
3118
3200
|
*
|
|
3119
|
-
*
|
|
3120
|
-
* physically hold two rows whose keys collapse under the new comparator. Every layer the
|
|
3121
|
-
* DDL connection still reads through therefore has to be collision-free, not just the
|
|
3122
|
-
* transaction's effective view:
|
|
3201
|
+
* Asks two questions, in order, over two DIFFERENT row sets:
|
|
3123
3202
|
*
|
|
3124
|
-
* 1. **
|
|
3125
|
-
*
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
3203
|
+
* 1. **Is the change legal at all?** — probes the rows the DDL transaction can SEE: the
|
|
3204
|
+
* wrapper-supplied `rows` when a wrapper module (the isolation layer) holds the
|
|
3205
|
+
* transaction's pending rows outside this manager, otherwise this manager's own
|
|
3206
|
+
* layered view ({@link effectiveDdlRows}). A duplicate here is visible to a `select`
|
|
3207
|
+
* in this transaction, so the change is simply illegal → `CONSTRAINT`, naming the
|
|
3208
|
+
* colliding key.
|
|
3209
|
+
* 2. **Can the structures physically carry it?** — probes every layer of this manager's
|
|
3210
|
+
* own chain. The primary tree is a map, not a multi-map, so — unlike a secondary
|
|
3211
|
+
* index — no tree that a `rollback` / `rollback to savepoint` could restore may hold
|
|
3212
|
+
* two rows whose keys collapse under the new comparator. A collision confined to
|
|
3213
|
+
* committed rows the transaction has DELETED is therefore refused by physical
|
|
3214
|
+
* necessity, not because the data is invalid: the base must keep both rows for a
|
|
3215
|
+
* rollback, and a base re-keyed under the new collation could not represent the pair
|
|
3216
|
+
* at all → `BUSY` with the same "commit/rollback and retry" posture as
|
|
3217
|
+
* {@link ensureSchemaChangeSafety}. The persistent store backend asks the same two
|
|
3218
|
+
* questions with the same statuses (`StoreTable.validateRekeyedPrimaryKey` in
|
|
3219
|
+
* quereus-store — its committed rows equally survive a rollback); accepting the
|
|
3220
|
+
* deleted-collider shape instead would take transaction-scoped DDL
|
|
3221
|
+
* (`backlog/feat-transactional-ddl-native-backends`), which Quereus does not have.
|
|
3132
3222
|
*
|
|
3133
|
-
*
|
|
3223
|
+
* The passes judge different sets because a wrapper's effective stream and this manager's
|
|
3224
|
+
* layers genuinely diverge: staged inserts exist only in the stream, deleted committed
|
|
3225
|
+
* rows only in the layers. When `rows` is supplied, the view layer holds the COMMITTED
|
|
3226
|
+
* rows — not a subset of what pass 1 judged — so pass 2 starts AT the view; when `rows`
|
|
3227
|
+
* is absent, pass 1 judged exactly the view's rows and pass 2 starts at its parent.
|
|
3228
|
+
*
|
|
3229
|
+
* Pass 2 is deliberately conservative. The chain holds one immutable layer per statement
|
|
3134
3230
|
* boundary (see `MemoryTableConnection.createSavepoint`'s eager path), so it rejects any
|
|
3135
3231
|
* transaction that has held a colliding pair at ANY statement boundary — even one whose
|
|
3136
3232
|
* final view is clean and whose intermediate layer no savepoint can reach. Narrowing that
|
|
@@ -3138,44 +3234,86 @@ export class MemoryTableManager {
|
|
|
3138
3234
|
* rebase that savepoint snapshots exist to avoid. The tradeoff is a rare false BUSY on a
|
|
3139
3235
|
* statement sequence the user can retry after committing, versus losing a row on rollback.
|
|
3140
3236
|
*
|
|
3141
|
-
* This precondition is also what makes
|
|
3142
|
-
*
|
|
3143
|
-
* in
|
|
3237
|
+
* This precondition is also what makes the open-layer replays sound — both
|
|
3238
|
+
* `TransactionLayer.rekeyPrimaryKey`'s and `installRekeyedPrimaryKeyColumns`'s: with no
|
|
3239
|
+
* collisions anywhere in the chain, every primary key resolves to at most one row in
|
|
3240
|
+
* each layer under the new definition.
|
|
3144
3241
|
*/
|
|
3145
|
-
validateRekeyedPrimaryKey(newSchema) {
|
|
3242
|
+
async validateRekeyedPrimaryKey(newSchema, rows) {
|
|
3146
3243
|
const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
|
|
3147
3244
|
const connection = this.ddlConnection();
|
|
3148
3245
|
const view = connection
|
|
3149
3246
|
? (connection.pendingTransactionLayer ?? connection.readLayer)
|
|
3150
3247
|
: this.baseLayer;
|
|
3151
|
-
this.
|
|
3248
|
+
await this.assertNoPrimaryKeyCollisionInRows(rows ? rows() : this.effectiveDdlRows(), newPkFunctions, primaryKeyArity(newSchema) !== 1);
|
|
3152
3249
|
// `ensureSchemaChangeSafety` has already drained every committed layer into the base and
|
|
3153
|
-
// rejected sibling connections with open work, so
|
|
3154
|
-
//
|
|
3155
|
-
for (let layer = view.getParent(); layer; layer = layer.getParent()) {
|
|
3156
|
-
this.
|
|
3157
|
-
|
|
3158
|
-
|
|
3250
|
+
// rejected sibling connections with open work, so this walk covers the transaction's own
|
|
3251
|
+
// layers plus the base — every tree a rollback could restore.
|
|
3252
|
+
for (let layer = rows ? view : view.getParent(); layer; layer = layer.getParent()) {
|
|
3253
|
+
this.assertNoPrimaryKeyCollisionInLayer(layer, newPkFunctions);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3257
|
+
* A duplicate-key detector over the new primary key functions: feed it rows, and it returns
|
|
3258
|
+
* the key the moment one repeats. Shared by both arms of {@link validateRekeyedPrimaryKey}
|
|
3259
|
+
* so the async (effective-row) arm and the sync (layer-tree) arm cannot disagree about what
|
|
3260
|
+
* counts as a collision.
|
|
3261
|
+
*
|
|
3262
|
+
* NOTE: the probe holds every row it has seen, because the BTree derives its key from the
|
|
3263
|
+
* stored value — so an ALTER over a large table transiently doubles that table's row
|
|
3264
|
+
* references. If a wide table ever makes this the memory peak, key the probe by the PK
|
|
3265
|
+
* encoding (`primaryKeyFunctions.encode`) into a `Set` instead of by the row.
|
|
3266
|
+
*/
|
|
3267
|
+
makePrimaryKeyProbe(pkFunctions) {
|
|
3268
|
+
const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
|
|
3269
|
+
return (row) => {
|
|
3270
|
+
const key = pkFunctions.extractFromRow(row);
|
|
3271
|
+
if (probe.get(key) !== undefined)
|
|
3272
|
+
return key;
|
|
3273
|
+
probe.insert(row);
|
|
3274
|
+
return undefined;
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
/**
|
|
3278
|
+
* Legality arm of {@link validateRekeyedPrimaryKey}: raises `CONSTRAINT`, naming the key,
|
|
3279
|
+
* when two of the rows the DDL transaction can SEE share a primary key under `pkFunctions`.
|
|
3280
|
+
* Takes a wrapper's merged async stream or this manager's own synchronous layered view.
|
|
3281
|
+
*/
|
|
3282
|
+
async assertNoPrimaryKeyCollisionInRows(rows, pkFunctions, keyIsTuple) {
|
|
3283
|
+
const seen = this.makePrimaryKeyProbe(pkFunctions);
|
|
3284
|
+
for await (const row of rows) {
|
|
3285
|
+
const key = seen(row);
|
|
3286
|
+
if (key === undefined)
|
|
3287
|
+
continue;
|
|
3288
|
+
// An empty new key (`alter primary key ()`, the singleton table) has no components
|
|
3289
|
+
// to name: every row IS the one key, so say that rather than render "(key: )".
|
|
3290
|
+
const parts = keyParts(key, keyIsTuple).map(formatKeyValue);
|
|
3291
|
+
const keyDesc = parts.length > 0 ? `(key: ${parts.join(', ')})` : '(the empty key admits one row)';
|
|
3292
|
+
throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} primary key collides under the new key definition ${keyDesc}`, StatusCode.CONSTRAINT);
|
|
3159
3293
|
}
|
|
3160
3294
|
}
|
|
3161
3295
|
/**
|
|
3162
|
-
*
|
|
3296
|
+
* Representability arm of {@link validateRekeyedPrimaryKey}: raises `BUSY` when two of
|
|
3297
|
+
* `layer`'s physical rows share a primary key under `pkFunctions`. Its rows are ones a
|
|
3298
|
+
* rollback could restore, not ones the transaction can see, so this is never a statement
|
|
3299
|
+
* about the data's validity — hence the retryable status and wording.
|
|
3163
3300
|
*
|
|
3164
3301
|
* NOTE: O(rows) per layer, so O(layers × rows) for a whole chain — one more full pass than
|
|
3165
3302
|
* the base rebuild the caller is about to do anyway. Fine for a statement this rare; if a
|
|
3166
3303
|
* deep savepoint stack over a large table ever makes an ALTER slow, note that a layer's rows
|
|
3167
3304
|
* differ from its parent's only at the keys it wrote, so the walk can be narrowed to those.
|
|
3168
3305
|
*/
|
|
3169
|
-
|
|
3306
|
+
assertNoPrimaryKeyCollisionInLayer(layer, pkFunctions) {
|
|
3170
3307
|
const tree = layer.getModificationTree('primary');
|
|
3171
3308
|
if (!tree)
|
|
3172
3309
|
return;
|
|
3173
|
-
const
|
|
3310
|
+
const seen = this.makePrimaryKeyProbe(pkFunctions);
|
|
3174
3311
|
for (const row of iteratePrimaryRows(tree)) {
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3312
|
+
if (seen(row) !== undefined) {
|
|
3313
|
+
throw new QuereusError(`Cannot re-key the primary key of table ${this._tableName}: `
|
|
3314
|
+
+ `rows this transaction has removed still collide under the new key definition and must survive a rollback. `
|
|
3315
|
+
+ `Commit/rollback and retry.`, StatusCode.BUSY);
|
|
3316
|
+
}
|
|
3179
3317
|
}
|
|
3180
3318
|
}
|
|
3181
3319
|
/**
|