@quereus/quereus 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/src/common/types.d.ts +9 -0
- package/dist/src/common/types.d.ts.map +1 -1
- package/dist/src/common/types.js.map +1 -1
- package/dist/src/core/database-events.d.ts +49 -0
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +148 -0
- 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 +2 -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 +26 -5
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
- package/dist/src/core/database-materialized-views-plans.d.ts +6 -3
- package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +3 -2
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +26 -5
- package/dist/src/core/database-materialized-views.js.map +1 -1
- package/dist/src/core/database.d.ts +5 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +21 -1
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/internal-statement-cache.d.ts +18 -0
- package/dist/src/core/internal-statement-cache.d.ts.map +1 -0
- package/dist/src/core/internal-statement-cache.js +211 -0
- package/dist/src/core/internal-statement-cache.js.map +1 -0
- package/dist/src/func/builtins/aggregate.d.ts +13 -12
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +45 -67
- 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 +63 -48
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/builtins/scalar.d.ts.map +1 -1
- package/dist/src/func/builtins/scalar.js +92 -3
- package/dist/src/func/builtins/scalar.js.map +1 -1
- package/dist/src/func/registration.d.ts +18 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +26 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +10 -10
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +13 -12
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/parser.d.ts +6 -0
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +45 -34
- package/dist/src/parser/parser.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 +35 -3
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.js +37 -3
- package/dist/src/planner/analysis/comparison-collation.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/expression-fingerprint.d.ts.map +1 -1
- package/dist/src/planner/analysis/expression-fingerprint.js +29 -0
- package/dist/src/planner/analysis/expression-fingerprint.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 +6 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +19 -29
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
- package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
- package/dist/src/planner/analysis/sat-checker.js +43 -4
- package/dist/src/planner/analysis/sat-checker.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/coercion.d.ts +78 -0
- package/dist/src/planner/building/coercion.d.ts.map +1 -0
- package/dist/src/planner/building/coercion.js +137 -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/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +10 -3
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/expression.d.ts.map +1 -1
- package/dist/src/planner/building/expression.js +13 -35
- package/dist/src/planner/building/expression.js.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts +44 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.js +54 -0
- package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
- package/dist/src/planner/building/function-call.d.ts.map +1 -1
- package/dist/src/planner/building/function-call.js +8 -0
- package/dist/src/planner/building/function-call.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +9 -12
- package/dist/src/planner/building/insert.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/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +13 -4
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/cost/conjunct-cost.d.ts +42 -0
- package/dist/src/planner/cost/conjunct-cost.d.ts.map +1 -0
- package/dist/src/planner/cost/conjunct-cost.js +62 -0
- package/dist/src/planner/cost/conjunct-cost.js.map +1 -0
- package/dist/src/planner/framework/pass.js +3 -3
- 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/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 +10 -5
- package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +0 -9
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts +5 -0
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +8 -1
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/join-utils.d.ts +57 -0
- package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-utils.js +47 -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 +137 -0
- package/dist/src/planner/nodes/key-set-semi-join-node.js.map +1 -0
- 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 +14 -6
- package/dist/src/planner/nodes/merge-join-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/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +26 -3
- 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/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 +13 -2
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +54 -12
- 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 +408 -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 +27 -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 +74 -29
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +23 -4
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +10 -8
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +10 -8
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +67 -41
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.js +72 -36
- package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
- 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 +14 -2
- 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 +11 -1
- 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 +30 -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 +63 -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 +17 -1
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +206 -12
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +8 -1
- 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 +3 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -1
- 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/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-scalar-agg-decorrelation.js +4 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.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 +175 -60
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/planner/stats/catalog-stats.d.ts +52 -0
- package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
- package/dist/src/planner/stats/catalog-stats.js +151 -5
- package/dist/src/planner/stats/catalog-stats.js.map +1 -1
- package/dist/src/planner/stats/index.d.ts +15 -0
- 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/util/column-origins.d.ts +58 -0
- package/dist/src/planner/util/column-origins.d.ts.map +1 -0
- package/dist/src/planner/util/column-origins.js +90 -0
- package/dist/src/planner/util/column-origins.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 +91 -0
- package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.d.ts +49 -0
- package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -0
- package/dist/src/runtime/emit/aggregate-setup.js +99 -0
- package/dist/src/runtime/emit/aggregate-setup.js.map +1 -0
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +4 -69
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +354 -165
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +11 -0
- 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 +7 -12
- 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 +24 -25
- 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 +8 -16
- 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 +10 -23
- 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 +72 -51
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-view.js +6 -0
- package/dist/src/runtime/emit/create-view.js.map +1 -1
- package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
- package/dist/src/runtime/emit/distinct.js +6 -3
- package/dist/src/runtime/emit/distinct.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +188 -84
- package/dist/src/runtime/emit/dml-executor.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 +16 -65
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/insert.d.ts.map +1 -1
- package/dist/src/runtime/emit/insert.js +18 -7
- package/dist/src/runtime/emit/insert.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 +37 -11
- 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 +143 -0
- package/dist/src/runtime/emit/key-set-semi-join.js.map +1 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +14 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +33 -3
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +47 -6
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +32 -15
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/operand-comparator.d.ts +40 -0
- package/dist/src/runtime/emit/operand-comparator.d.ts.map +1 -0
- package/dist/src/runtime/emit/operand-comparator.js +59 -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/set-object-tags.d.ts +8 -0
- package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.js +8 -0
- package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +7 -4
- package/dist/src/runtime/emit/set-operation.js.map +1 -1
- package/dist/src/runtime/emit/sort.d.ts.map +1 -1
- package/dist/src/runtime/emit/sort.js +5 -2
- package/dist/src/runtime/emit/sort.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +113 -13
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts +7 -0
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js +15 -7
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
- package/dist/src/runtime/emit/update.d.ts.map +1 -1
- package/dist/src/runtime/emit/update.js +44 -3
- package/dist/src/runtime/emit/update.js.map +1 -1
- package/dist/src/runtime/emit/window.d.ts.map +1 -1
- package/dist/src/runtime/emit/window.js +72 -55
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.d.ts +36 -1
- package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +238 -116
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +10 -2
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +12 -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 +25 -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 +58 -0
- package/dist/src/schema/catalog-persistability.d.ts.map +1 -0
- package/dist/src/schema/catalog-persistability.js +87 -0
- package/dist/src/schema/catalog-persistability.js.map +1 -0
- package/dist/src/schema/catalog.d.ts +17 -1
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +25 -3
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/constraint-builder.d.ts +20 -22
- package/dist/src/schema/constraint-builder.d.ts.map +1 -1
- package/dist/src/schema/constraint-builder.js +43 -36
- package/dist/src/schema/constraint-builder.js.map +1 -1
- package/dist/src/schema/ddl-generator.d.ts +23 -0
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +27 -0
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/function.d.ts +39 -1
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/manager.d.ts +28 -0
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +82 -14
- 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 +51 -12
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +31 -0
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +37 -1
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/schema/window-function.d.ts +30 -10
- package/dist/src/schema/window-function.d.ts.map +1 -1
- package/dist/src/schema/window-function.js +20 -16
- package/dist/src/schema/window-function.js.map +1 -1
- package/dist/src/types/builtin-types.d.ts.map +1 -1
- package/dist/src/types/builtin-types.js +30 -4
- package/dist/src/types/builtin-types.js.map +1 -1
- package/dist/src/types/cast-semantics.d.ts +43 -0
- package/dist/src/types/cast-semantics.d.ts.map +1 -0
- package/dist/src/types/cast-semantics.js +59 -0
- package/dist/src/types/cast-semantics.js.map +1 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/json-type.d.ts.map +1 -1
- package/dist/src/types/json-type.js +31 -10
- package/dist/src/types/json-type.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +22 -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/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +33 -11
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/types/validation.d.ts +39 -1
- package/dist/src/types/validation.d.ts.map +1 -1
- package/dist/src/types/validation.js +60 -0
- package/dist/src/types/validation.js.map +1 -1
- package/dist/src/util/ast-spine-clone.d.ts +23 -0
- package/dist/src/util/ast-spine-clone.d.ts.map +1 -0
- package/dist/src/util/ast-spine-clone.js +41 -0
- package/dist/src/util/ast-spine-clone.js.map +1 -0
- package/dist/src/util/coercion.d.ts +13 -0
- package/dist/src/util/coercion.d.ts.map +1 -1
- package/dist/src/util/coercion.js +10 -1
- package/dist/src/util/coercion.js.map +1 -1
- package/dist/src/util/comparison.d.ts +114 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +187 -27
- package/dist/src/util/comparison.js.map +1 -1
- 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/manifest.d.ts +7 -0
- package/dist/src/vtab/manifest.d.ts.map +1 -1
- package/dist/src/vtab/memory/connection.d.ts +14 -1
- package/dist/src/vtab/memory/connection.d.ts.map +1 -1
- package/dist/src/vtab/memory/connection.js +20 -2
- package/dist/src/vtab/memory/connection.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +12 -5
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +20 -9
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/connection.d.ts +17 -0
- package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/connection.js +26 -0
- package/dist/src/vtab/memory/layer/connection.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +156 -31
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +695 -158
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts +41 -20
- package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.js +57 -27
- package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
- package/dist/src/vtab/memory/layer/row-convert.d.ts +21 -0
- package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -0
- package/dist/src/vtab/memory/layer/row-convert.js +30 -0
- package/dist/src/vtab/memory/layer/row-convert.js.map +1 -0
- package/dist/src/vtab/memory/layer/scan-layer.d.ts +6 -3
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +48 -34
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +142 -19
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +237 -73
- 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 +60 -41
- 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 +12 -4
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/memory/types.d.ts +27 -1
- package/dist/src/vtab/memory/types.d.ts.map +1 -1
- package/dist/src/vtab/memory/types.js +16 -1
- package/dist/src/vtab/memory/types.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.d.ts +9 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.js +11 -0
- package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
- package/dist/src/vtab/module.d.ts +63 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +33 -7
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +3 -3
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +0 -26
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +0 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +0 -66
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass, validateCollationForType } from '../../../schema/table.js';
|
|
2
|
+
import { keyParts } from '../types.js';
|
|
2
3
|
import { BTree } from 'inheritree';
|
|
3
4
|
import { StatusCode } from '../../../common/types.js';
|
|
4
5
|
import { BaseLayer, iteratePrimaryRows, populateIndexFromRows, populateIndexFromRowsAsync } from './base.js';
|
|
@@ -7,20 +8,84 @@ import { MemoryTableConnection } from './connection.js';
|
|
|
7
8
|
import { MemoryVirtualTableConnection } from '../connection.js';
|
|
8
9
|
import { QuereusError } from '../../../common/errors.js';
|
|
9
10
|
import { ConflictResolution } from '../../../common/constants.js';
|
|
10
|
-
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError } from '../../../schema/constraint-builder.js';
|
|
11
|
-
import { indexEnforcesUnique, uniqueEnforcementCollations } from '../../../schema/unique-enforcement.js';
|
|
12
|
-
import {
|
|
11
|
+
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError, formatKeyValue } from '../../../schema/constraint-builder.js';
|
|
12
|
+
import { indexEnforcesUnique, uniqueEnforcementCollations, uniqueEnforcementComparators } from '../../../schema/unique-enforcement.js';
|
|
13
|
+
import { generateIndexDDL, generateDropIndexDDL } from '../../../schema/ddl-generator.js';
|
|
14
|
+
import { compareSqlValues, rowsValueIdentical, normalizeCollationName, comparisonSemanticsDiffer } from '../../../util/comparison.js';
|
|
13
15
|
import { scanLayer as scanLayerImpl } from './scan-layer.js';
|
|
14
|
-
import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues } from '../utils/primary-key.js';
|
|
16
|
+
import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues, primaryKeyArity } from '../utils/primary-key.js';
|
|
15
17
|
import { createMemoryTableLoggers } from '../utils/logging.js';
|
|
16
18
|
import { tryFoldLiteral } from '../../../parser/utils.js';
|
|
17
|
-
import { validateAndParse } from '../../../types/validation.js';
|
|
19
|
+
import { validateAndParse, coerceRowToSchema } from '../../../types/validation.js';
|
|
18
20
|
import { inferType } from '../../../types/registry.js';
|
|
19
21
|
import { compilePredicate } from '../utils/predicate.js';
|
|
20
22
|
import { renameColumnInIndexPredicates } from '../../../schema/rename-rewriter.js';
|
|
21
23
|
import { MemoryIndex } from '../index.js';
|
|
24
|
+
import { convertRowAtIndex, mapRows, mapRowsAsync } from './row-convert.js';
|
|
22
25
|
let tableManagerCounter = 0;
|
|
23
26
|
const logger = createMemoryTableLoggers('layer:manager');
|
|
27
|
+
/** A copy of `items` with `value` inserted at `at` (`at === items.length` ⇒ append). */
|
|
28
|
+
function insertValueAt(items, at, value) {
|
|
29
|
+
const out = items.slice();
|
|
30
|
+
out.splice(at, 0, value);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** Index of a column after a new one is inserted at `at`. */
|
|
34
|
+
const shiftForInsert = (index, at) => index >= at ? index + 1 : index;
|
|
35
|
+
/**
|
|
36
|
+
* The index-bearing fields of `schema` renumbered for a column inserted at `at`, to be
|
|
37
|
+
* spread over the schema alongside the new `columns` array. Only reached when `at` is a
|
|
38
|
+
* real insert point (an append leaves every existing index valid), and the mirror image
|
|
39
|
+
* of the shift `dropColumn` performs — except that an insert can never empty an index or
|
|
40
|
+
* a constraint, so none of `dropColumn`'s prune/filter logic has a counterpart here.
|
|
41
|
+
*
|
|
42
|
+
* `checkConstraints` and the manager's implicit covering structures are name/AST-keyed
|
|
43
|
+
* and need nothing. `statistics.columnStats` is keyed by column name. `columnIndexMap` is
|
|
44
|
+
* rebuilt from the new column list by the caller.
|
|
45
|
+
*
|
|
46
|
+
* A foreign key's `columns` are indices into THIS table and always shift. Its
|
|
47
|
+
* `referencedColumns` are NOT touched: every constructor of a foreign key leaves that
|
|
48
|
+
* array empty (`constraint-builder.ts`, `schema/manager.ts`) and enforcement resolves the
|
|
49
|
+
* parent indices on demand from `referencedColumnNames` against the parent's CURRENT
|
|
50
|
+
* schema (`resolveReferencedColumns`) — so even a self-referential key needs no shift here.
|
|
51
|
+
*
|
|
52
|
+
* `derivation.logicalKey` / `derivation.ordering` also hold indices into this table and are
|
|
53
|
+
* NOT shifted, in either direction: they exist only on a maintained table (a materialized
|
|
54
|
+
* view's backing table), whose shape is defined by its body, and the emitter rejects every
|
|
55
|
+
* structural ALTER on one before dispatch.
|
|
56
|
+
*/
|
|
57
|
+
function shiftSchemaIndicesForInsert(schema, at) {
|
|
58
|
+
const shift = (index) => shiftForInsert(index, at);
|
|
59
|
+
const primaryKeyDefinition = schema.primaryKeyDefinition.map(def => ({ ...def, index: shift(def.index) }));
|
|
60
|
+
const indexes = schema.indexes?.map(idx => ({
|
|
61
|
+
...idx,
|
|
62
|
+
columns: idx.columns.map(ic => ({ ...ic, index: shift(ic.index) })),
|
|
63
|
+
}));
|
|
64
|
+
const uniqueConstraints = schema.uniqueConstraints?.map(uc => ({
|
|
65
|
+
...uc,
|
|
66
|
+
columns: Object.freeze(uc.columns.map(shift)),
|
|
67
|
+
}));
|
|
68
|
+
const foreignKeys = schema.foreignKeys?.map(fk => ({
|
|
69
|
+
...fk,
|
|
70
|
+
columns: Object.freeze(fk.columns.map(shift)),
|
|
71
|
+
}));
|
|
72
|
+
// Generated-column bookkeeping is keyed BY column index and holds column indices.
|
|
73
|
+
// Nothing inside the memory module reads it — the engine recomputes the graph after
|
|
74
|
+
// its own ADD COLUMN — but a wrapper driving this API directly gets the schema back
|
|
75
|
+
// verbatim, so leaving it unshifted would hand out a schema whose generated-column
|
|
76
|
+
// edges point at the wrong columns.
|
|
77
|
+
const generatedColumnDependencies = schema.generatedColumnDependencies
|
|
78
|
+
&& new Map(Array.from(schema.generatedColumnDependencies, ([col, deps]) => [shift(col), deps.map(shift)]));
|
|
79
|
+
const generatedColumnTopoOrder = schema.generatedColumnTopoOrder?.map(shift);
|
|
80
|
+
return {
|
|
81
|
+
primaryKeyDefinition: Object.freeze(primaryKeyDefinition),
|
|
82
|
+
...(indexes && { indexes: Object.freeze(indexes) }),
|
|
83
|
+
...(uniqueConstraints && { uniqueConstraints: Object.freeze(uniqueConstraints) }),
|
|
84
|
+
...(foreignKeys && { foreignKeys: Object.freeze(foreignKeys) }),
|
|
85
|
+
...(generatedColumnDependencies && { generatedColumnDependencies }),
|
|
86
|
+
...(generatedColumnTopoOrder && { generatedColumnTopoOrder: Object.freeze(generatedColumnTopoOrder) }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
24
89
|
export class MemoryTableManager {
|
|
25
90
|
managerId;
|
|
26
91
|
db;
|
|
@@ -305,10 +370,10 @@ export class MemoryTableManager {
|
|
|
305
370
|
// `baseLayer` by ALTER TABLE) or carries an out-of-date schema, leave
|
|
306
371
|
// it alone — committing such a layer would supplant the schema-aware
|
|
307
372
|
// committed head with stale data.
|
|
373
|
+
let refusedSnapshotWrap = false;
|
|
308
374
|
if (!connection.pendingTransactionLayer
|
|
309
375
|
&& connection.readLayer !== this._currentCommittedLayer
|
|
310
|
-
&& connection.readLayer instanceof TransactionLayer
|
|
311
|
-
&& connection.readLayer.getSchema() === this.tableSchema) {
|
|
376
|
+
&& connection.readLayer instanceof TransactionLayer) {
|
|
312
377
|
let walker = connection.readLayer.getParent();
|
|
313
378
|
let isAhead = false;
|
|
314
379
|
while (walker) {
|
|
@@ -319,17 +384,32 @@ export class MemoryTableManager {
|
|
|
319
384
|
walker = walker.getParent();
|
|
320
385
|
}
|
|
321
386
|
if (isAhead) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
387
|
+
if (connection.readLayer.getSchema() === this.tableSchema) {
|
|
388
|
+
connection.pendingTransactionLayer = new TransactionLayer(connection.readLayer);
|
|
389
|
+
if (this.eventEmitter?.hasDataListeners?.()) {
|
|
390
|
+
connection.pendingTransactionLayer.enableChangeTracking();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// A snapshot AHEAD of the head whose schema object nonetheless predates the
|
|
395
|
+
// manager's: some schema-mutating arm failed to hand its new TableSchema to
|
|
396
|
+
// the open layers (`adoptSchemaOnOpenLayers` and friends). Refusing the wrap
|
|
397
|
+
// is still the safe call, but it discards every row the transaction staged,
|
|
398
|
+
// so flag it for the warning below rather than let COMMIT report success over
|
|
399
|
+
// a silent data loss.
|
|
400
|
+
refusedSnapshotWrap = true;
|
|
325
401
|
}
|
|
326
402
|
}
|
|
327
403
|
}
|
|
328
404
|
const pendingLayer = connection.pendingTransactionLayer;
|
|
329
405
|
if (!pendingLayer) {
|
|
406
|
+
if (refusedSnapshotWrap) {
|
|
407
|
+
logger.warn('Commit Transaction', this._tableName, 'Discarding staged rows: savepoint snapshot carries a schema object older than the table\'s. A schema-mutating arm did not adopt its new schema on the open transaction layers.', { schemaName: this.schemaName, connectionId: connection.connectionId });
|
|
408
|
+
}
|
|
330
409
|
// No pending — refresh readLayer to the current committed head so a
|
|
331
410
|
// stale ancestor (post-schema-change) doesn't leak into the next
|
|
332
|
-
// statement's view.
|
|
411
|
+
// statement's view. (The ordinary read-only / no-writes commit lands
|
|
412
|
+
// here too, which is why the warning above is gated.)
|
|
333
413
|
connection.readLayer = this._currentCommittedLayer;
|
|
334
414
|
return;
|
|
335
415
|
}
|
|
@@ -382,6 +462,15 @@ export class MemoryTableManager {
|
|
|
382
462
|
// since the pending layer forked. Replaying stale-schema rows onto
|
|
383
463
|
// the new-schema head is unsafe — abort with BUSY rather than corrupt
|
|
384
464
|
// the committed head (mirrors the stale-ancestor caution above).
|
|
465
|
+
//
|
|
466
|
+
// NOTE: `adoptSchemaOnOpenLayers` only walks the DDL connection's own chain,
|
|
467
|
+
// so a SIBLING connection's pending layer keeps its creation-time schema
|
|
468
|
+
// object even after a metadata-only change like `RENAME TO` that moves no
|
|
469
|
+
// rows — which would make this arm fire spuriously. Not reachable from SQL
|
|
470
|
+
// today: `renameTable` never calls `ensureSchemaChangeSafety`, and a single
|
|
471
|
+
// Database runs one transaction at a time. If sibling connections ever hold
|
|
472
|
+
// concurrent pending layers over one manager, compare column shape here
|
|
473
|
+
// rather than schema-object identity.
|
|
385
474
|
if (pendingLayer.getSchema() !== this.tableSchema) {
|
|
386
475
|
connection.pendingTransactionLayer = null;
|
|
387
476
|
connection.clearSavepoints();
|
|
@@ -415,12 +504,21 @@ export class MemoryTableManager {
|
|
|
415
504
|
connection.clearSavepoints();
|
|
416
505
|
// Emit data change events after successful commit
|
|
417
506
|
if (changes.length > 0 && this.eventEmitter?.emitDataChange) {
|
|
507
|
+
// The event contract carries the PK as a component array. Derive the stored
|
|
508
|
+
// key's shape from the PK arity, never from `Array.isArray` — a JSON PK whose
|
|
509
|
+
// value is a document like `[1]` is a SCALAR key that happens to be a JS
|
|
510
|
+
// array, and sniffing it would emit `[1]` where the contract wants `[[1]]`.
|
|
511
|
+
// NOTE: on the tuple path this hands listeners the STORED key array itself
|
|
512
|
+
// (`VTableDataChangeEvent.key` is mutable `SqlValue[]`, as it always was).
|
|
513
|
+
// If a listener is ever seen mutating `event.key`, copy here — an in-place
|
|
514
|
+
// edit would re-order the primary tree under the comparator.
|
|
515
|
+
const eventKeyIsTuple = primaryKeyArity(this.tableSchema) !== 1;
|
|
418
516
|
for (const change of changes) {
|
|
419
517
|
const event = {
|
|
420
518
|
type: change.type,
|
|
421
519
|
schemaName: this.schemaName,
|
|
422
520
|
tableName: this._tableName,
|
|
423
|
-
key:
|
|
521
|
+
key: keyParts(change.pk, eventKeyIsTuple),
|
|
424
522
|
oldRow: change.oldRow,
|
|
425
523
|
newRow: change.newRow,
|
|
426
524
|
};
|
|
@@ -640,7 +738,7 @@ export class MemoryTableManager {
|
|
|
640
738
|
const result = primaryTree.get(key);
|
|
641
739
|
return result === undefined ? null : result;
|
|
642
740
|
}
|
|
643
|
-
async performMutation(connection, operation, values, oldKeyValues, onConflict) {
|
|
741
|
+
async performMutation(connection, operation, values, oldKeyValues, onConflict, preCoerced) {
|
|
644
742
|
this.validateMutationPermissions(operation);
|
|
645
743
|
const wasExplicitTransaction = connection.explicitTransaction;
|
|
646
744
|
this.ensureTransactionLayer(connection);
|
|
@@ -648,10 +746,10 @@ export class MemoryTableManager {
|
|
|
648
746
|
let result;
|
|
649
747
|
switch (operation) {
|
|
650
748
|
case 'insert':
|
|
651
|
-
result = await this.performInsert(targetLayer, values, onConflict);
|
|
749
|
+
result = await this.performInsert(targetLayer, values, onConflict, preCoerced);
|
|
652
750
|
break;
|
|
653
751
|
case 'update':
|
|
654
|
-
result = await this.performUpdate(targetLayer, values, oldKeyValues, onConflict);
|
|
752
|
+
result = await this.performUpdate(targetLayer, values, oldKeyValues, onConflict, preCoerced);
|
|
655
753
|
break;
|
|
656
754
|
case 'delete':
|
|
657
755
|
result = await this.performDelete(targetLayer, oldKeyValues);
|
|
@@ -692,20 +790,23 @@ export class MemoryTableManager {
|
|
|
692
790
|
// transaction is auto-created (autocommit mode). Leave explicitTransaction flag as-is.
|
|
693
791
|
}
|
|
694
792
|
}
|
|
695
|
-
async performInsert(targetLayer, values, onConflict) {
|
|
793
|
+
async performInsert(targetLayer, values, onConflict, preCoerced) {
|
|
696
794
|
if (!values) {
|
|
697
795
|
throw new QuereusError("INSERT requires values.", StatusCode.MISUSE);
|
|
698
796
|
}
|
|
699
|
-
// Validate and parse values according to column types
|
|
797
|
+
// Validate and parse values according to column types. Skip when the caller
|
|
798
|
+
// already converted to declared form and says so via preCoerced — the DML
|
|
799
|
+
// executor (whose emitters convert by static type at the top of the pipeline)
|
|
800
|
+
// and the isolation overlay's flush/tombstone writes. JSON conversion is not
|
|
801
|
+
// idempotent, so re-running it can throw or silently change the value. Direct
|
|
802
|
+
// API callers leave the flag unset and this conversion remains their contract.
|
|
803
|
+
// NOTE: skipping it also skips that helper's "too many values" width guard. Every
|
|
804
|
+
// preCoerced caller today builds its row from this same schema's column order
|
|
805
|
+
// (the DML builders project the source into full table-column shape), so the
|
|
806
|
+
// width is structural; if an externally-shaped row ever reaches here preCoerced,
|
|
807
|
+
// hoist the width check out of coerceRowToSchema and run it unconditionally.
|
|
700
808
|
const schema = targetLayer.getSchema();
|
|
701
|
-
const
|
|
702
|
-
if (index >= schema.columns.length) {
|
|
703
|
-
throw new QuereusError(`Too many values for INSERT into ${this._tableName}: expected ${schema.columns.length}, got ${values.length}`, StatusCode.ERROR);
|
|
704
|
-
}
|
|
705
|
-
const column = schema.columns[index];
|
|
706
|
-
return validateAndParse(value, column.logicalType, column.name);
|
|
707
|
-
});
|
|
708
|
-
const newRowData = validatedRow;
|
|
809
|
+
const newRowData = preCoerced ? values : coerceRowToSchema(values, schema.columns, `INSERT into ${this._tableName}`);
|
|
709
810
|
const primaryKey = this.primaryKeyFromRow(newRowData);
|
|
710
811
|
const existingRow = this.lookupEffectiveRow(primaryKey, targetLayer);
|
|
711
812
|
if (existingRow !== null) {
|
|
@@ -736,20 +837,14 @@ export class MemoryTableManager {
|
|
|
736
837
|
targetLayer.recordUpsert(primaryKey, newRowData, null);
|
|
737
838
|
return { status: 'ok', row: newRowData, evictedRows: evicted.length > 0 ? evicted : undefined };
|
|
738
839
|
}
|
|
739
|
-
async performUpdate(targetLayer, values, oldKeyValues, onConflict) {
|
|
840
|
+
async performUpdate(targetLayer, values, oldKeyValues, onConflict, preCoerced) {
|
|
740
841
|
if (!values || !oldKeyValues) {
|
|
741
842
|
throw new QuereusError("UPDATE requires new values and old key values.", StatusCode.MISUSE);
|
|
742
843
|
}
|
|
743
|
-
// Validate and parse values according to column types
|
|
844
|
+
// Validate and parse values according to column types. Skip when preCoerced
|
|
845
|
+
// (see performInsert).
|
|
744
846
|
const schema = targetLayer.getSchema();
|
|
745
|
-
const
|
|
746
|
-
if (index >= schema.columns.length) {
|
|
747
|
-
throw new QuereusError(`Too many values for UPDATE on ${this._tableName}: expected ${schema.columns.length}, got ${values.length}`, StatusCode.ERROR);
|
|
748
|
-
}
|
|
749
|
-
const column = schema.columns[index];
|
|
750
|
-
return validateAndParse(value, column.logicalType, column.name);
|
|
751
|
-
});
|
|
752
|
-
const newRowData = validatedRow;
|
|
847
|
+
const newRowData = preCoerced ? values : coerceRowToSchema(values, schema.columns, `UPDATE on ${this._tableName}`);
|
|
753
848
|
const targetPrimaryKey = buildPrimaryKeyFromValues(oldKeyValues, schema.primaryKeyDefinition);
|
|
754
849
|
const oldRowData = this.lookupEffectiveRow(targetPrimaryKey, targetLayer);
|
|
755
850
|
if (!oldRowData) {
|
|
@@ -833,6 +928,14 @@ export class MemoryTableManager {
|
|
|
833
928
|
* Returns true if any column covered by a UNIQUE constraint changed between
|
|
834
929
|
* old and new rows, or if any column referenced by a partial-UNIQUE predicate
|
|
835
930
|
* changed (which may transition the row into or out of the predicate's scope).
|
|
931
|
+
*
|
|
932
|
+
* NOTE: the per-column test is byte-level `compareSqlValues`, not the enforcement
|
|
933
|
+
* comparator, so it OVER-triggers for a semantic-ordering column: rewriting a
|
|
934
|
+
* TIMESPAN 'PT1H' to 'PT60M' reports "changed" and re-runs the UNIQUE check, which
|
|
935
|
+
* then excludes the row's own primary key and passes. Correct — this only gates
|
|
936
|
+
* whether to re-check — just not minimal. If UPDATE-heavy workloads over
|
|
937
|
+
* semantic-ordering UNIQUE columns ever show the redundant re-check as hot, route
|
|
938
|
+
* this through `uniqueEnforcementComparators` too.
|
|
836
939
|
*/
|
|
837
940
|
uniqueColumnsChanged(schema, oldRow, newRow) {
|
|
838
941
|
if (!schema.uniqueConstraints)
|
|
@@ -988,8 +1091,11 @@ export class MemoryTableManager {
|
|
|
988
1091
|
if (existingPKs.length === 0)
|
|
989
1092
|
return null;
|
|
990
1093
|
// Resolve the per-column enforcement collations once, ahead of the candidate loop
|
|
991
|
-
// (which collation governs, and why, is spelled out at the compare below)
|
|
1094
|
+
// (which collation governs, and why, is spelled out at the compare below), then
|
|
1095
|
+
// build the per-column comparators from them through the shared helper the store
|
|
1096
|
+
// and isolation re-validators also use.
|
|
992
1097
|
const enforcementCollations = uc.columns.map((col, i) => this.collationResolver(index.specColumns[i]?.collation ?? schema.columns[col].collation ?? 'BINARY'));
|
|
1098
|
+
const compares = uniqueEnforcementComparators(schema.columns, uc.columns, enforcementCollations);
|
|
993
1099
|
for (const existingPK of existingPKs) {
|
|
994
1100
|
if (this.comparePrimaryKeys(newPrimaryKey, existingPK) === 0)
|
|
995
1101
|
continue;
|
|
@@ -1010,7 +1116,12 @@ export class MemoryTableManager {
|
|
|
1010
1116
|
// the enforcing structure, and an explicit `create unique index …
|
|
1011
1117
|
// (col collate nocase)` may declare a coarser collation than the
|
|
1012
1118
|
// column — re-checking under the column's collation would skip the
|
|
1013
|
-
// case-variant candidates the index legitimately unifies.
|
|
1119
|
+
// case-variant candidates the index legitimately unifies. A
|
|
1120
|
+
// semantic-ordering column (TIMESPAN, JSON) compares through its
|
|
1121
|
+
// declared type's `compare` instead, so the re-check agrees with the
|
|
1122
|
+
// typed BTree that produced the candidate — without it a 'PT60M' probe
|
|
1123
|
+
// found the 'PT1H' row's PK and then discarded it as unequal, and the
|
|
1124
|
+
// duplicate was admitted.
|
|
1014
1125
|
// This is the authoritative LIVE-index source for the per-column
|
|
1015
1126
|
// enforcement collation. The shared `uniqueEnforcementCollations(schema,
|
|
1016
1127
|
// uc)` helper (which store/isolation import, and checkUniqueViaMaterializedView
|
|
@@ -1028,7 +1139,7 @@ export class MemoryTableManager {
|
|
|
1028
1139
|
const conflictingRow = this.lookupEffectiveRow(existingPK, targetLayer);
|
|
1029
1140
|
if (!conflictingRow)
|
|
1030
1141
|
continue;
|
|
1031
|
-
if (!uc.columns.every((col, i) =>
|
|
1142
|
+
if (!uc.columns.every((col, i) => compares[i](newRowData[col], conflictingRow[col]) === 0))
|
|
1032
1143
|
continue;
|
|
1033
1144
|
if (index.predicate && !index.rowMatchesPredicate(conflictingRow))
|
|
1034
1145
|
continue;
|
|
@@ -1072,7 +1183,13 @@ export class MemoryTableManager {
|
|
|
1072
1183
|
* backing delete still lands within this statement.
|
|
1073
1184
|
*/
|
|
1074
1185
|
async checkUniqueViaMaterializedView(targetLayer, schema, uc, mv, newRowData, newPrimaryKey, onConflict, evicted) {
|
|
1075
|
-
|
|
1186
|
+
// Component-array view of the source PK. Shape comes from the PK arity, never
|
|
1187
|
+
// from `Array.isArray` — a single-column JSON PK holding a document like `[1]`
|
|
1188
|
+
// is a scalar key that is also a JS array (see the `BTreeKey` invariant).
|
|
1189
|
+
// A wrong shape here only WIDENS the candidate set (the row fails its own
|
|
1190
|
+
// self-exclusion), and the loop below re-excludes self through the real PK
|
|
1191
|
+
// comparator — which is why no query result can observe this line.
|
|
1192
|
+
const newSourcePk = keyParts(newPrimaryKey, primaryKeyArity(schema) !== 1);
|
|
1076
1193
|
const conflicts = await this.db._lookupCoveringConflicts(mv, uc, newRowData, newSourcePk);
|
|
1077
1194
|
// Re-validate under each column's enforcement collation — the index's per-column
|
|
1078
1195
|
// COLLATE for an index-derived UNIQUE, else the declared column collation
|
|
@@ -1084,7 +1201,11 @@ export class MemoryTableManager {
|
|
|
1084
1201
|
// index-derived UNIQUE whose declared candidate set could be a subset is declined
|
|
1085
1202
|
// upstream by findRowTimeCoveringStructure's collation gate, so only BINARY-floor
|
|
1086
1203
|
// or equal-collation MVs ever reach here.
|
|
1204
|
+
// A semantic-ordering column (TIMESPAN, JSON) re-checks through its declared
|
|
1205
|
+
// type's `compare` rather than the collation — same rule the index path and both
|
|
1206
|
+
// out-of-package re-validators follow, via the shared helper.
|
|
1087
1207
|
const collations = uniqueEnforcementCollations(schema, uc).map(name => this.collationResolver(name ?? 'BINARY'));
|
|
1208
|
+
const compares = uniqueEnforcementComparators(schema.columns, uc.columns, collations);
|
|
1088
1209
|
for (const conflict of conflicts) {
|
|
1089
1210
|
const existingPK = buildPrimaryKeyFromValues(conflict.pk, schema.primaryKeyDefinition);
|
|
1090
1211
|
if (this.comparePrimaryKeys(newPrimaryKey, existingPK) === 0)
|
|
@@ -1093,7 +1214,7 @@ export class MemoryTableManager {
|
|
|
1093
1214
|
const conflictingRow = this.lookupEffectiveRow(existingPK, targetLayer);
|
|
1094
1215
|
if (!conflictingRow)
|
|
1095
1216
|
continue;
|
|
1096
|
-
if (!uc.columns.every((col, i) =>
|
|
1217
|
+
if (!uc.columns.every((col, i) => compares[i](newRowData[col], conflictingRow[col]) === 0))
|
|
1097
1218
|
continue;
|
|
1098
1219
|
if (onConflict === ConflictResolution.IGNORE) {
|
|
1099
1220
|
return { status: 'ok', row: undefined };
|
|
@@ -1123,8 +1244,19 @@ export class MemoryTableManager {
|
|
|
1123
1244
|
const predicate = uc.predicate
|
|
1124
1245
|
? compilePredicate(uc.predicate, schema.columns, schema.name)
|
|
1125
1246
|
: undefined;
|
|
1126
|
-
// One resolve per column, not per scanned row.
|
|
1247
|
+
// One resolve per column, not per scanned row. A semantic-ordering column
|
|
1248
|
+
// (TIMESPAN, JSON) compares through its declared type's `compare` instead of the
|
|
1249
|
+
// collation — the shared rule across all three backends.
|
|
1250
|
+
//
|
|
1251
|
+
// NOTE: the collations come from the DECLARED column collation, not from
|
|
1252
|
+
// `uniqueEnforcementCollations(schema, uc)` as the index and MV paths use. For an
|
|
1253
|
+
// index-derived UNIQUE (`create unique index … collate x`) the two disagree — this
|
|
1254
|
+
// path would enforce under the column's collation instead of the index's. Harmless
|
|
1255
|
+
// today: this fallback fires only when no covering structure resolves at all, and
|
|
1256
|
+
// an index-derived UC always resolves its own index. If a shape ever reaches here
|
|
1257
|
+
// with `uc.derivedFromIndex` set, switch this line to the shared resolver.
|
|
1127
1258
|
const collations = uc.columns.map(colIdx => this.collationResolver(schema.columns[colIdx].collation ?? 'BINARY'));
|
|
1259
|
+
const compares = uniqueEnforcementComparators(schema.columns, uc.columns, collations);
|
|
1128
1260
|
for (const path of primaryTree.ascending(primaryTree.first())) {
|
|
1129
1261
|
const existingRow = primaryTree.at(path);
|
|
1130
1262
|
const existingPK = this.primaryKeyFromRow(existingRow);
|
|
@@ -1132,7 +1264,7 @@ export class MemoryTableManager {
|
|
|
1132
1264
|
continue;
|
|
1133
1265
|
if (predicate && predicate.evaluate(existingRow) !== true)
|
|
1134
1266
|
continue;
|
|
1135
|
-
const allMatch = uc.columns.every((colIdx, i) =>
|
|
1267
|
+
const allMatch = uc.columns.every((colIdx, i) => compares[i](newRowData[colIdx], existingRow[colIdx]) === 0);
|
|
1136
1268
|
if (!allMatch)
|
|
1137
1269
|
continue;
|
|
1138
1270
|
if (onConflict === ConflictResolution.IGNORE) {
|
|
@@ -1156,10 +1288,25 @@ export class MemoryTableManager {
|
|
|
1156
1288
|
}
|
|
1157
1289
|
renameTable(newName) {
|
|
1158
1290
|
logger.operation('Rename Table', this._tableName, { newName });
|
|
1159
|
-
this._tableName = newName;
|
|
1160
1291
|
const renamed = Object.freeze({ ...this.tableSchema, name: newName });
|
|
1292
|
+
// Re-key the Database connection registry FIRST, while it still answers to the old
|
|
1293
|
+
// name. Every registry lookup below (`adoptSchemaOnOpenLayers` walks
|
|
1294
|
+
// `openTransactionLayersOldestFirst` -> `ddlConnection` -> `registeredConnections`)
|
|
1295
|
+
// keys on `<schema>.<current name>`, so a sweep run after `_tableName` moves would
|
|
1296
|
+
// match nothing and silently no-op.
|
|
1297
|
+
this.rekeyRegisteredConnections(this._tableName, newName);
|
|
1298
|
+
this._tableName = newName;
|
|
1161
1299
|
this.tableSchema = renamed;
|
|
1162
1300
|
this.baseLayer.tableSchema = renamed;
|
|
1301
|
+
// Hand the renamed schema to the open transaction's layers, like every other
|
|
1302
|
+
// schema-mutating arm. A rename rebuilds no `IndexSchema`, so `adoptSchema` is the
|
|
1303
|
+
// right (cheapest) level — each layer keeps its `MemoryIndex` and only swaps the
|
|
1304
|
+
// schema pointer; the reshape pair ADD/DROP COLUMN need would be pure overhead.
|
|
1305
|
+
// Skipping it leaves an eager savepoint snapshot on its frozen pre-rename schema
|
|
1306
|
+
// object, which fails `commitTransaction`'s snapshot-wrap identity check
|
|
1307
|
+
// (`readLayer.getSchema() === this.tableSchema`) and drops the transaction's staged
|
|
1308
|
+
// rows at COMMIT.
|
|
1309
|
+
this.adoptSchemaOnOpenLayers(renamed);
|
|
1163
1310
|
// Emit schema change event
|
|
1164
1311
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1165
1312
|
type: 'alter',
|
|
@@ -1453,7 +1600,15 @@ export class MemoryTableManager {
|
|
|
1453
1600
|
}
|
|
1454
1601
|
}
|
|
1455
1602
|
// --- Schema Operations (simplified with inherited BTrees) ---
|
|
1456
|
-
|
|
1603
|
+
/**
|
|
1604
|
+
* ADD COLUMN. `insertAtIndex` — when supplied — puts the new column at that slot
|
|
1605
|
+
* instead of appending; every existing column at or after it shifts right by one,
|
|
1606
|
+
* and each index-bearing schema field is renumbered to match (see
|
|
1607
|
+
* {@link shiftSchemaIndicesForInsert}). Omitted ⇒ append, which is what SQL
|
|
1608
|
+
* `alter table … add column` always asks for and which leaves the schema rebuild
|
|
1609
|
+
* on exactly the path it took before positions existed.
|
|
1610
|
+
*/
|
|
1611
|
+
async addColumn(columnDefAst, backfillEvaluator, insertAtIndex) {
|
|
1457
1612
|
if (this.isReadOnly)
|
|
1458
1613
|
throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
|
|
1459
1614
|
const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
|
|
@@ -1498,20 +1653,76 @@ export class MemoryTableManager {
|
|
|
1498
1653
|
throw new QuereusError(`Cannot add NOT NULL column '${newColumnSchema.name}' to non-empty table `
|
|
1499
1654
|
+ `'${this.schemaName}.${this._tableName}' without a DEFAULT value`, StatusCode.CONSTRAINT);
|
|
1500
1655
|
}
|
|
1501
|
-
const
|
|
1656
|
+
const appendIndex = this.tableSchema.columns.length;
|
|
1657
|
+
const targetIndex = insertAtIndex ?? appendIndex;
|
|
1658
|
+
if (!Number.isInteger(targetIndex) || targetIndex < 0 || targetIndex > appendIndex) {
|
|
1659
|
+
throw new QuereusError(`Cannot add column '${newColumnSchema.name}' at position ${insertAtIndex}: `
|
|
1660
|
+
+ `expected an integer in [0, ${appendIndex}]`, StatusCode.MISUSE);
|
|
1661
|
+
}
|
|
1662
|
+
const updatedColumnsSchema = Object.freeze(insertValueAt(this.tableSchema.columns, targetIndex, newColumnSchema));
|
|
1663
|
+
// Appending never invalidates an existing column index, so the append path keeps
|
|
1664
|
+
// carrying every index-bearing field through the spread unchanged; only a real
|
|
1665
|
+
// insert pays for the renumber.
|
|
1666
|
+
const shiftedIndexFields = targetIndex < appendIndex
|
|
1667
|
+
? shiftSchemaIndicesForInsert(this.tableSchema, targetIndex)
|
|
1668
|
+
: undefined;
|
|
1502
1669
|
const finalNewTableSchema = Object.freeze({
|
|
1503
1670
|
...this.tableSchema,
|
|
1671
|
+
...shiftedIndexFields,
|
|
1504
1672
|
columns: updatedColumnsSchema,
|
|
1505
1673
|
columnIndexMap: buildColumnIndexMap(updatedColumnsSchema),
|
|
1506
1674
|
});
|
|
1675
|
+
// Phase 1 of reaching the open transaction's own pending rows: compute their
|
|
1676
|
+
// post-ADD shape before ANY mutation, since the per-row backfill below can throw
|
|
1677
|
+
// on a pending row (and there is no undo for a half-reshaped layer chain).
|
|
1678
|
+
//
|
|
1679
|
+
// The NOT NULL check mirrors the base backfill's per-row check in
|
|
1680
|
+
// `BaseLayer.recreatePrimaryTreeWithNewColumn`, extended to pending rows. It is
|
|
1681
|
+
// deliberately NOT gated on `backfillEvaluator`, which today makes the
|
|
1682
|
+
// literal-default arm redundant: `validateNotNullBackfill`
|
|
1683
|
+
// (runtime/emit/alter-table.ts) already rejects NOT NULL without a usable DEFAULT
|
|
1684
|
+
// by querying the DDL connection's EFFECTIVE rows — pending ones included — so the
|
|
1685
|
+
// only violation that reaches here is a per-row evaluator yielding NULL. Kept
|
|
1686
|
+
// ungated so the module enforces its own invariant instead of relying on that gate.
|
|
1687
|
+
const reshapePlans = await this.prepareReshapeOnOpenLayers(async (row) => {
|
|
1688
|
+
const value = backfillEvaluator ? await backfillEvaluator(row) : defaultValue;
|
|
1689
|
+
if (newColumnSchema.notNull && value === null) {
|
|
1690
|
+
throw new QuereusError(`NOT NULL constraint failed: adding column '${this._tableName}.${newColumnSchema.name}' would leave NULL in a row pending in the open transaction`, StatusCode.CONSTRAINT);
|
|
1691
|
+
}
|
|
1692
|
+
return insertValueAt(row, targetIndex, value);
|
|
1693
|
+
},
|
|
1694
|
+
// Event-log variant: same map applied to the log's historical images (oldRow
|
|
1695
|
+
// included — the evaluator is a function of a row and the pre-image is a row),
|
|
1696
|
+
// but BEST-EFFORT: an image the evaluator (or its CHECKs) rejects gets NULL —
|
|
1697
|
+
// the honest "column did not exist yet" placeholder — and the NOT NULL throw
|
|
1698
|
+
// above is omitted, so no historical image can abort the ALTER.
|
|
1699
|
+
async (row) => {
|
|
1700
|
+
let value = defaultValue;
|
|
1701
|
+
if (backfillEvaluator) {
|
|
1702
|
+
try {
|
|
1703
|
+
value = await backfillEvaluator(row);
|
|
1704
|
+
}
|
|
1705
|
+
catch {
|
|
1706
|
+
value = null;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return insertValueAt(row, targetIndex, value);
|
|
1710
|
+
});
|
|
1507
1711
|
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
1508
1712
|
// A non-foldable DEFAULT (e.g. `new.<col>`) backfills each existing row from
|
|
1509
1713
|
// its own value via the engine-supplied evaluator; a literal/NULL default
|
|
1510
1714
|
// uses the single folded `defaultValue` for every row.
|
|
1511
|
-
await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator);
|
|
1715
|
+
await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator, targetIndex);
|
|
1512
1716
|
this.tableSchema = finalNewTableSchema;
|
|
1513
1717
|
this.initializePrimaryKeyFunctions();
|
|
1514
|
-
|
|
1718
|
+
this.installReshapeOnOpenLayers(finalNewTableSchema, reshapePlans);
|
|
1719
|
+
// Emit schema change event.
|
|
1720
|
+
// NOTE: the event names the added column but not its position, which is fine while
|
|
1721
|
+
// the only non-append caller is an in-process wrapper that rebuilds its view from the
|
|
1722
|
+
// post-change schema. An append never invalidates an existing column index; an insert
|
|
1723
|
+
// does. If a position ever becomes SQL-reachable, anything that caches a column index
|
|
1724
|
+
// across this swap (planner caches, prepared statements, a wrapper's own schema
|
|
1725
|
+
// snapshot) needs to learn the insert point — so the event would have to carry it.
|
|
1515
1726
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1516
1727
|
type: 'alter',
|
|
1517
1728
|
objectType: 'column',
|
|
@@ -1583,6 +1794,28 @@ export class MemoryTableManager {
|
|
|
1583
1794
|
.filter(ic => ic.index !== colIndex)
|
|
1584
1795
|
.map(ic => ({ ...ic, index: ic.index > colIndex ? ic.index - 1 : ic.index }))
|
|
1585
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)) }));
|
|
1812
|
+
// NOTE: the generated-column bookkeeping (`generatedColumnDependencies` /
|
|
1813
|
+
// `generatedColumnTopoOrder`, also index-keyed) is likewise unshifted, but the
|
|
1814
|
+
// engine recomputes it from column names right after this returns
|
|
1815
|
+
// (`withGeneratedColumnGraph` in `runDropColumn`), so only a caller driving the
|
|
1816
|
+
// module API directly ever observes the stale map.
|
|
1817
|
+
//
|
|
1818
|
+
// `primaryKey` below is not a TableSchema field at all — nothing reads it.
|
|
1586
1819
|
const finalNewTableSchema = Object.freeze({
|
|
1587
1820
|
...this.tableSchema,
|
|
1588
1821
|
columns: Object.freeze(updatedColumnsSchema),
|
|
@@ -1593,7 +1826,16 @@ export class MemoryTableManager {
|
|
|
1593
1826
|
uniqueConstraints: remainingUniqueConstraints.length > 0
|
|
1594
1827
|
? Object.freeze(remainingUniqueConstraints)
|
|
1595
1828
|
: undefined,
|
|
1829
|
+
foreignKeys: remainingForeignKeys.length > 0
|
|
1830
|
+
? Object.freeze(remainingForeignKeys)
|
|
1831
|
+
: undefined,
|
|
1596
1832
|
});
|
|
1833
|
+
// Phase 1 of reaching the open transaction's own pending rows. The rewrite here is
|
|
1834
|
+
// a pure filter and cannot throw — the split exists for ADD COLUMN's fallible
|
|
1835
|
+
// backfill — but running it before any mutation keeps the two paths uniform. The
|
|
1836
|
+
// same filter reshapes the pending-change event log (second arg).
|
|
1837
|
+
const dropSlot = (row) => row.filter((_, idx) => idx !== colIndex);
|
|
1838
|
+
const reshapePlans = await this.prepareReshapeOnOpenLayers(dropSlot, dropSlot);
|
|
1597
1839
|
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
1598
1840
|
await this.baseLayer.dropColumnFromBase(colIndex);
|
|
1599
1841
|
this.tableSchema = finalNewTableSchema;
|
|
@@ -1602,6 +1844,7 @@ export class MemoryTableManager {
|
|
|
1602
1844
|
for (const key of droppedUcKeys)
|
|
1603
1845
|
this.implicitCoveringStructures.delete(key);
|
|
1604
1846
|
this.initializePrimaryKeyFunctions();
|
|
1847
|
+
this.installReshapeOnOpenLayers(finalNewTableSchema, reshapePlans);
|
|
1605
1848
|
// Emit schema change event
|
|
1606
1849
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1607
1850
|
type: 'drop',
|
|
@@ -1685,6 +1928,20 @@ export class MemoryTableManager {
|
|
|
1685
1928
|
await this.baseLayer.handleColumnRename();
|
|
1686
1929
|
this.tableSchema = finalNewTableSchema;
|
|
1687
1930
|
this.initializePrimaryKeyFunctions();
|
|
1931
|
+
// The DDL transaction's own layers froze their schema at creation; hand them the
|
|
1932
|
+
// renamed one, mirroring `handleColumnRename`'s base-side secondary rebuild —
|
|
1933
|
+
// `updatedIndexes` above rebuilt every `IndexSchema` object, which is exactly the
|
|
1934
|
+
// identity discriminator `adoptSchema` rebuilds a layer's `MemoryIndex` on.
|
|
1935
|
+
//
|
|
1936
|
+
// A rename touches neither the column set nor the primary key, so `adoptSchema` is
|
|
1937
|
+
// the right level (not the `prepareReshapeOnOpenLayers` / `installReshapeOnOpenLayers`
|
|
1938
|
+
// pair `add column` / `drop column` need) — but it is just as mandatory: without it an
|
|
1939
|
+
// eager savepoint snapshot keeps its frozen pre-rename schema, fails
|
|
1940
|
+
// `commitTransaction`'s snapshot-wrap identity check
|
|
1941
|
+
// (`readLayer.getSchema() === this.tableSchema`), and the transaction's staged rows are
|
|
1942
|
+
// silently dropped at COMMIT. Covered by
|
|
1943
|
+
// `test/logic/41.8-alter-savepoint-staged-rows.sqllogic`.
|
|
1944
|
+
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
1688
1945
|
// Emit schema change event
|
|
1689
1946
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1690
1947
|
type: 'alter',
|
|
@@ -1717,8 +1974,15 @@ export class MemoryTableManager {
|
|
|
1717
1974
|
* Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE).
|
|
1718
1975
|
* The caller supplies exactly one populated change; multi-attribute combinations
|
|
1719
1976
|
* are rejected by the runtime before reaching this method.
|
|
1977
|
+
*
|
|
1978
|
+
* `validateOnly` runs everything up to (and including) the pre-mutation validation
|
|
1979
|
+
* passes — the effective-row NULL / conversion scans, the re-keyed UNIQUE probe, and
|
|
1980
|
+
* the primary-key re-key legality/representability passes — and returns before the
|
|
1981
|
+
* first mutation, throwing exactly what the real application would throw. The
|
|
1982
|
+
* isolation layer uses it to pre-flight an overlay's migration before the shared
|
|
1983
|
+
* underlying table mutates irreversibly (see `VirtualTable.alterSchema`).
|
|
1720
1984
|
*/
|
|
1721
|
-
async alterColumn(change, rows) {
|
|
1985
|
+
async alterColumn(change, rows, validateOnly = false) {
|
|
1722
1986
|
if (this.isReadOnly)
|
|
1723
1987
|
throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
|
|
1724
1988
|
const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
|
|
@@ -1738,12 +2002,23 @@ export class MemoryTableManager {
|
|
|
1738
2002
|
// A collation change re-keys any PK / UNIQUE / index that orders by this
|
|
1739
2003
|
// column, so it needs the structure re-sort + uniqueness re-validation below.
|
|
1740
2004
|
let collationChanged = false;
|
|
2005
|
+
// Set when a SET DATA TYPE moves to a type that ORDERS values differently
|
|
2006
|
+
// (comparisonSemanticsDiffer). Same consequence as a collation change — every
|
|
2007
|
+
// structure keyed by the column has to be re-sorted and its uniqueness re-judged —
|
|
2008
|
+
// but a separate flag, because the index-column collation propagation and the
|
|
2009
|
+
// `pkColumnRekeyed` primary-tree path below belong to SET COLLATE alone. Usually
|
|
2010
|
+
// coincides with `valueConvert` (every non-alias retype sets that); the rebuild
|
|
2011
|
+
// chains below test `valueConvert` FIRST, whose full-rebuild path subsumes the
|
|
2012
|
+
// comparator-only re-sort.
|
|
2013
|
+
let comparatorChanged = false;
|
|
1741
2014
|
// Set to the per-value conversion function when a physical value rewrite is needed:
|
|
1742
|
-
// SET DATA TYPE (
|
|
1743
|
-
//
|
|
1744
|
-
//
|
|
1745
|
-
//
|
|
1746
|
-
//
|
|
2015
|
+
// SET DATA TYPE (every non-alias retype — values normalize to the new type's
|
|
2016
|
+
// canonical spelling even when the storage class is unchanged) or SET NOT NULL
|
|
2017
|
+
// backfill (map null → DEFAULT). Both REPLACE the base primary tree with a
|
|
2018
|
+
// freshly-built one (never an in-place mutation, which inheritree forbids while the
|
|
2019
|
+
// open transaction's layers derive from it) and convert the transaction's
|
|
2020
|
+
// own-written rows in every open layer, after `updateSchema` (see the rebuild
|
|
2021
|
+
// section and convertColumnOnOpenLayers).
|
|
1747
2022
|
let valueConvert = null;
|
|
1748
2023
|
// SET NOT NULL backfill also routes NULL values through `valueConvert`; SET DATA TYPE
|
|
1749
2024
|
// leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
|
|
@@ -1821,19 +2096,40 @@ export class MemoryTableManager {
|
|
|
1821
2096
|
}
|
|
1822
2097
|
else if (change.setDataType !== undefined) {
|
|
1823
2098
|
const newLogicalType = inferType(change.setDataType);
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2099
|
+
// Gate on logical-type IDENTITY, not the physical storage class: `inferType`
|
|
2100
|
+
// flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`,
|
|
2101
|
+
// `bigint` IS `INTEGER_TYPE`), so an alias retype is schema-only — no scan, no
|
|
2102
|
+
// rewrite, no re-key. Every OTHER retype validates AND normalizes every value,
|
|
2103
|
+
// whether or not the storage class changes: a same-class move (text → date)
|
|
2104
|
+
// must reject values the new type refuses ('hello') and rewrite the rest to
|
|
2105
|
+
// the new type's canonical spelling ('2024-06-05T00:00:00Z' → '2024-06-05'),
|
|
2106
|
+
// or the column holds values no INSERT could have produced — unfindable by
|
|
2107
|
+
// equality (DATE compares BINARY over the stored text) and invisible to UNIQUE.
|
|
2108
|
+
// NOTE: the rewrite is unconditional once the types differ, even when every
|
|
2109
|
+
// converted value comes back byte-identical (already-canonical ISO durations on a
|
|
2110
|
+
// text → timespan). That costs a full base-tree + secondary-index rebuild here, and
|
|
2111
|
+
// a physical rewrite of every row on the store leg. If ALTER on large tables ever
|
|
2112
|
+
// shows up as slow, have the convert pre-pass record whether any value actually
|
|
2113
|
+
// moved and, when none did, fall through to the comparator-only re-key.
|
|
2114
|
+
if (newLogicalType !== oldCol.logicalType) {
|
|
2115
|
+
// Retyping a PRIMARY KEY column moves the key bytes (a class change), the
|
|
2116
|
+
// tree's ordering (a comparator move), or at minimum the key values'
|
|
2117
|
+
// canonical spelling — and neither the base rewrite below nor the
|
|
2118
|
+
// transaction-layer conversion re-keys the primary tree. The engine already
|
|
2119
|
+
// refuses SET DATA TYPE on any PK column (runtime/emit/alter-table.ts);
|
|
2120
|
+
// this guards direct module calls. Reject rather than corrupt — the
|
|
2121
|
+
// type-change analogue of the SET COLLATE primary-key carve-out
|
|
2122
|
+
// (`alter-collate-pk-in-transaction`).
|
|
1834
2123
|
if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
|
|
1835
2124
|
throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
|
|
1836
2125
|
}
|
|
2126
|
+
// Every MemoryIndex builds its comparator from the column's LOGICAL type
|
|
2127
|
+
// (`createTypedComparator(columnSchema.logicalType, …)` in vtab/memory/index.ts),
|
|
2128
|
+
// so a move to a differently-ordering type (text ↔ timespan: 'PT1H' ≡ 'PT60M';
|
|
2129
|
+
// text ↔ date/time/datetime: binary, ignoring the column's collation) re-sorts
|
|
2130
|
+
// every structure keyed by the column. Tracked separately from the value rewrite
|
|
2131
|
+
// because SET COLLATE shares the re-key machinery without rewriting values.
|
|
2132
|
+
comparatorChanged = comparisonSemanticsDiffer(oldCol.logicalType, newLogicalType);
|
|
1837
2133
|
const convert = (v) => {
|
|
1838
2134
|
try {
|
|
1839
2135
|
return validateAndParse(v, newLogicalType, change.columnName);
|
|
@@ -1868,8 +2164,8 @@ export class MemoryTableManager {
|
|
|
1868
2164
|
// derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
|
|
1869
2165
|
// replacement and the transaction-layer conversion.
|
|
1870
2166
|
valueConvert = convert;
|
|
1871
|
-
newCol = { ...oldCol, logicalType: newLogicalType };
|
|
1872
2167
|
}
|
|
2168
|
+
newCol = { ...oldCol, logicalType: newLogicalType };
|
|
1873
2169
|
}
|
|
1874
2170
|
else if (change.setDefault !== undefined) {
|
|
1875
2171
|
newCol = { ...oldCol, defaultValue: change.setDefault };
|
|
@@ -1878,15 +2174,30 @@ export class MemoryTableManager {
|
|
|
1878
2174
|
throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
|
|
1879
2175
|
}
|
|
1880
2176
|
const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newCol : c);
|
|
2177
|
+
// The changes that move the COMPARATOR the structures are keyed by: SET COLLATE
|
|
2178
|
+
// (values untouched) and a retype into a differently-ordering type (which, unless it
|
|
2179
|
+
// is an alias retype, ALSO rewrites values — `valueConvert` — and its full-rebuild
|
|
2180
|
+
// path below subsumes the comparator-only re-sort). Both need fresh IndexSchema
|
|
2181
|
+
// objects and, when no rewrite runs, the re-validate / re-sort / adopt sequence.
|
|
2182
|
+
const structuresRekeyed = collationChanged || comparatorChanged;
|
|
1881
2183
|
// Propagate a collation change into every PK-definition entry and index
|
|
1882
2184
|
// column that orders by this column, so their comparators re-key under it.
|
|
1883
2185
|
const updatedPkDef = collationChanged
|
|
1884
2186
|
? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
|
|
1885
2187
|
: this.tableSchema.primaryKeyDefinition;
|
|
1886
|
-
|
|
2188
|
+
// Every re-keying change also REBUILDS the IndexSchema objects, because their identity is
|
|
2189
|
+
// the discriminator `TransactionLayer.adoptSchema` uses to decide an open layer's
|
|
2190
|
+
// MemoryIndex must be replaced rather than kept (an index it holds under an object the new
|
|
2191
|
+
// schema still carries is left alone). SET COLLATE has a field to write — the index
|
|
2192
|
+
// column's own collation. A comparator-only retype has none: an index column carries no
|
|
2193
|
+
// logical type, it reads the column's from `newSchema.columns`, so the fresh object IS the
|
|
2194
|
+
// whole signal. Without it the base rebuilt its trees under the new comparator while the
|
|
2195
|
+
// DDL transaction's own layers went on comparing the old way — and shadowed the base's
|
|
2196
|
+
// rebuilt trees once the pending layer became the committed head.
|
|
2197
|
+
const updatedIndexes = (structuresRekeyed && this.tableSchema.indexes)
|
|
1887
2198
|
? this.tableSchema.indexes.map(idx => ({
|
|
1888
2199
|
...idx,
|
|
1889
|
-
columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, collation: newCol.collation } : ic),
|
|
2200
|
+
columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
|
|
1890
2201
|
}))
|
|
1891
2202
|
: this.tableSchema.indexes;
|
|
1892
2203
|
const finalNewTableSchema = Object.freeze({
|
|
@@ -1898,48 +2209,81 @@ export class MemoryTableManager {
|
|
|
1898
2209
|
});
|
|
1899
2210
|
const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
|
|
1900
2211
|
// Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows: a pair
|
|
1901
|
-
// the transaction inserted that collides under the NEW
|
|
2212
|
+
// the transaction inserted that collides under the NEW schema must reject the
|
|
1902
2213
|
// change, and one it has deleted must not block it. A throw here leaves the schema,
|
|
1903
2214
|
// the base layer and the index map exactly as they were.
|
|
1904
2215
|
//
|
|
1905
|
-
//
|
|
1906
|
-
//
|
|
1907
|
-
//
|
|
1908
|
-
|
|
2216
|
+
// A value-REWRITING change (`valueConvert`: SET DATA TYPE, or a SET NOT NULL null →
|
|
2217
|
+
// DEFAULT backfill) probes the CONVERTED rows — the same per-row conversion the base
|
|
2218
|
+
// rewrite below applies — under the NEW schema's comparators, so one call covers both
|
|
2219
|
+
// ways an ALTER can collapse two previously-distinct rows: the VALUES converge
|
|
2220
|
+
// (`'1'`/`'01'` → 1; two NULLs → one DEFAULT; '2024-06-05T00:00:00Z' → '2024-06-05')
|
|
2221
|
+
// and/or the COMPARATOR moves (text → timespan makes 'PT1H'/'PT60M' one value). A
|
|
2222
|
+
// same-storage-class retype sets `valueConvert` AND `structuresRekeyed`, so the
|
|
2223
|
+
// rewrite arm is tested FIRST — its converted-row probe subsumes the comparator-only
|
|
2224
|
+
// one. The rebuild that follows is deliberately non-enforcing, so this is the only
|
|
2225
|
+
// guard.
|
|
2226
|
+
//
|
|
2227
|
+
// The comparator-MOVING arm without a rewrite has one trigger left: SET COLLATE. The
|
|
2228
|
+
// values are untouched — hence no `mapRow` — and only the comparator the structures
|
|
2229
|
+
// are keyed by moves. `newSchema` carries the new collations, so the probe indexes
|
|
2230
|
+
// compare as the rebuilt ones will.
|
|
2231
|
+
//
|
|
2232
|
+
// The primary key: SET COLLATE on a PK member gets a stricter pre-pass
|
|
2233
|
+
// (`validateRekeyedPrimaryKey`): no layer in the chain, base included, may hold a
|
|
2234
|
+
// collision. That is what lets every step below succeed unconditionally, and what
|
|
2235
|
+
// `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass
|
|
2236
|
+
// and cannot shadow one: a retype of a PK column is rejected upstream, the backfill
|
|
2237
|
+
// only fires on a column holding NULLs (which a PK member cannot — the engine
|
|
2238
|
+
// enforces NOT NULL on every PK member regardless of the declared nullability), and
|
|
2239
|
+
// `pkColumnRekeyed` is set only by SET COLLATE, which never sets `valueConvert` — so
|
|
2240
|
+
// whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm still runs.
|
|
2241
|
+
// NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK
|
|
2242
|
+
// collision path (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey`
|
|
2243
|
+
// plus a primary-tree re-key of its own.
|
|
2244
|
+
if (valueConvert) {
|
|
2245
|
+
const convert = valueConvert;
|
|
2246
|
+
const nulls = convertNulls;
|
|
2247
|
+
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows,
|
|
2248
|
+
// Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
|
|
2249
|
+
// the probe sees is visible to the transaction, and the setDataType pre-pass above
|
|
2250
|
+
// already proved each one convertible, so a throw is unreachable; surfacing it as
|
|
2251
|
+
// MISMATCH still beats probing a stale value that could mask a collision.
|
|
2252
|
+
row => convertRowAtIndex(row, colIndex, convert, nulls));
|
|
2253
|
+
}
|
|
2254
|
+
else if (structuresRekeyed) {
|
|
1909
2255
|
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
|
|
1910
|
-
//
|
|
1911
|
-
//
|
|
1912
|
-
//
|
|
1913
|
-
//
|
|
1914
|
-
//
|
|
1915
|
-
// committed row that collide only under the new collation" is checked by neither
|
|
1916
|
-
// side. Rejecting it here would need the wrapper to expose its overlay's PK set, not
|
|
1917
|
-
// just its merged rows.
|
|
2256
|
+
// Unlike the secondary-structure pass above — which only ever judges the effective
|
|
2257
|
+
// rows — the PK pass judges TWO row sets: the effective rows (`rows` when a wrapper
|
|
2258
|
+
// supplies them) decide whether the change is LEGAL, and this manager's own layer
|
|
2259
|
+
// chain decides whether the re-keyed trees can PHYSICALLY carry it. See
|
|
2260
|
+
// `validateRekeyedPrimaryKey` for why the sets differ and which status each raises.
|
|
1918
2261
|
if (pkColumnRekeyed)
|
|
1919
|
-
this.validateRekeyedPrimaryKey(finalNewTableSchema);
|
|
2262
|
+
await this.validateRekeyedPrimaryKey(finalNewTableSchema, rows);
|
|
1920
2263
|
}
|
|
2264
|
+
// Dry run ends here: everything above validates without mutating (the one earlier
|
|
2265
|
+
// side effect, ensureSchemaChangeSafety's committed-layer drain, is semantically
|
|
2266
|
+
// neutral bookkeeping), and everything below mutates. See the method doc.
|
|
2267
|
+
if (validateOnly)
|
|
2268
|
+
return;
|
|
1921
2269
|
this.baseLayer.updateSchema(finalNewTableSchema);
|
|
1922
|
-
//
|
|
1923
|
-
//
|
|
1924
|
-
//
|
|
1925
|
-
//
|
|
1926
|
-
//
|
|
1927
|
-
//
|
|
1928
|
-
//
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
if (pkColumnRekeyed) {
|
|
1932
|
-
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
1933
|
-
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
else if (valueConvert) {
|
|
2270
|
+
// Both arms rebuild the base's structures under the schema `updateSchema` above just
|
|
2271
|
+
// swapped in — each MemoryIndex rebuilds its comparator from the CURRENT schema's
|
|
2272
|
+
// collation and logical type. The secondary rebuilds are NON-enforcing (the pre-pass
|
|
2273
|
+
// above owns uniqueness, and the base's rows are not a subset of the effective rows);
|
|
2274
|
+
// the SET COLLATE PK rebuild is strict, since a PK collision cannot be represented at
|
|
2275
|
+
// all — the pre-pass has already proved the base collision-free, so the strict
|
|
2276
|
+
// rebuild is a live invariant check, not the enforcement path. It runs LAST so its
|
|
2277
|
+
// throw leaves the live tree intact for the catch's rollback.
|
|
2278
|
+
if (valueConvert) {
|
|
1937
2279
|
// SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
|
|
1938
2280
|
// the primary tree with a fresh one holding them (not an in-place upsert — inheritree
|
|
1939
2281
|
// forbids mutating a base while the open transaction's layers derive from it).
|
|
1940
2282
|
// `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
|
|
1941
|
-
// rows, so index-backed lookups see the new values
|
|
1942
|
-
//
|
|
2283
|
+
// rows under the new comparators, so index-backed lookups see the new values — which
|
|
2284
|
+
// is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
|
|
2285
|
+
// same-storage-class retype sets `structuresRekeyed` too. The old tree is saved for
|
|
2286
|
+
// the catch's rollback, exactly as the PK-rekey path does.
|
|
1943
2287
|
// NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
|
|
1944
2288
|
// if a wide-index table ever shows this as slow, filter to indexes whose columns
|
|
1945
2289
|
// include colIndex. Mirrors the collationChanged path's unconditional rebuild.
|
|
@@ -1954,31 +2298,56 @@ export class MemoryTableManager {
|
|
|
1954
2298
|
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
1955
2299
|
this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
|
|
1956
2300
|
}
|
|
2301
|
+
else if (structuresRekeyed) {
|
|
2302
|
+
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2303
|
+
if (pkColumnRekeyed) {
|
|
2304
|
+
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
2305
|
+
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
1957
2308
|
this.tableSchema = finalNewTableSchema;
|
|
1958
2309
|
this.initializePrimaryKeyFunctions();
|
|
1959
2310
|
// The base rebuild handed every secondary index a fresh tree under the new
|
|
1960
|
-
//
|
|
2311
|
+
// comparator; the DDL transaction's own layers still inherit the old ones and froze
|
|
1961
2312
|
// the old schema at construction. Re-key them, or the rest of the transaction — and
|
|
1962
2313
|
// everything after the pending layer becomes the committed head at commit — keeps
|
|
1963
|
-
// comparing under the old collation.
|
|
2314
|
+
// comparing under the old collation / old logical type.
|
|
1964
2315
|
//
|
|
1965
2316
|
// When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
|
|
1966
2317
|
// swapped the base primary tree object out from under those layers' copy-on-write
|
|
1967
2318
|
// bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
|
|
1968
2319
|
// every secondary index (each derives its PK comparator/encoder from the PK
|
|
1969
2320
|
// definition). Outside a transaction there are no open layers and both are no-ops.
|
|
1970
|
-
if (
|
|
1971
|
-
this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
|
|
1972
|
-
}
|
|
1973
|
-
else if (valueConvert) {
|
|
2321
|
+
if (valueConvert) {
|
|
1974
2322
|
// SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
|
|
1975
2323
|
// transaction's own open layers still froze the old schema and hold their own-written
|
|
1976
2324
|
// rows unconverted. Swap the schema and convert those values so the rest of the
|
|
1977
2325
|
// transaction — and the committed head at commit — read the new value. For SET NOT
|
|
1978
2326
|
// NULL this fills the transaction's OWN pending NULL rows (which live in the pending
|
|
1979
|
-
// layer, never reached by the old in-place base upsert).
|
|
2327
|
+
// layer, never reached by the old in-place base upsert). `convertColumn` installs the
|
|
2328
|
+
// new schema and rebuilds every one of the layer's secondary indexes from it, so it
|
|
2329
|
+
// subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
|
|
2330
|
+
// No-op in autocommit.
|
|
1980
2331
|
this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
|
|
1981
2332
|
}
|
|
2333
|
+
else if (structuresRekeyed) {
|
|
2334
|
+
this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
|
|
2335
|
+
}
|
|
2336
|
+
else {
|
|
2337
|
+
// The remaining changes (SET DEFAULT, DROP NOT NULL, a SET NOT NULL that needed no
|
|
2338
|
+
// backfill, a retype between aliases of one logical type) touch nothing a layer
|
|
2339
|
+
// DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
|
|
2340
|
+
// comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
|
|
2341
|
+
// above the module, off the catalog schema. But the frozen schema OBJECT is itself
|
|
2342
|
+
// observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
|
|
2343
|
+
// into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
|
|
2344
|
+
// stale, a `rollback to savepoint` taken across one of these ALTERs restores a
|
|
2345
|
+
// snapshot the wrap then skips, and the transaction's staged rows are silently
|
|
2346
|
+
// dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
|
|
2347
|
+
// over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
|
|
2348
|
+
// the schema reference.
|
|
2349
|
+
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
2350
|
+
}
|
|
1982
2351
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1983
2352
|
type: 'alter',
|
|
1984
2353
|
objectType: 'column',
|
|
@@ -2067,12 +2436,15 @@ export class MemoryTableManager {
|
|
|
2067
2436
|
// The DDL transaction's own layers froze their schema at creation; hand them the
|
|
2068
2437
|
// new one so the rest of the transaction scans and enforces the new index.
|
|
2069
2438
|
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
2070
|
-
// Emit schema change event
|
|
2439
|
+
// Emit schema change event. The `ddl` is what a sync peer re-executes to
|
|
2440
|
+
// replicate the index; rendered against the POST-create schema so it matches
|
|
2441
|
+
// what a receiving peer regenerates when comparing definitions.
|
|
2071
2442
|
this.eventEmitter?.emitSchemaChange?.({
|
|
2072
2443
|
type: 'create',
|
|
2073
2444
|
objectType: 'index',
|
|
2074
2445
|
schemaName: this.schemaName,
|
|
2075
2446
|
objectName: indexName,
|
|
2447
|
+
ddl: generateIndexDDL(newIndexSchemaEntry, finalNewTableSchema),
|
|
2076
2448
|
});
|
|
2077
2449
|
logger.operation('Create Index', this._tableName, { indexName });
|
|
2078
2450
|
}
|
|
@@ -2135,6 +2507,7 @@ export class MemoryTableManager {
|
|
|
2135
2507
|
objectType: 'index',
|
|
2136
2508
|
schemaName: this.schemaName,
|
|
2137
2509
|
objectName: indexName,
|
|
2510
|
+
ddl: generateDropIndexDDL(this.schemaName, indexName),
|
|
2138
2511
|
});
|
|
2139
2512
|
logger.operation('Drop Index', this._tableName, { indexName });
|
|
2140
2513
|
}
|
|
@@ -2285,6 +2658,14 @@ export class MemoryTableManager {
|
|
|
2285
2658
|
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2286
2659
|
this.tableSchema = newSchema;
|
|
2287
2660
|
this.initializePrimaryKeyFunctions();
|
|
2661
|
+
// Hand the new schema to the DDL transaction's own frozen layers, as `dropConstraint`
|
|
2662
|
+
// does: a renamed covering index has to move under its new key in each layer's index
|
|
2663
|
+
// map (the old key is dropped, the new one rebuilt over the parent's already-renamed
|
|
2664
|
+
// tree — hence oldest-first), and even the pure schema-only classes (CHECK / FK) must
|
|
2665
|
+
// re-freeze the new schema OBJECT, or `commitTransaction`'s snapshot-wrap identity
|
|
2666
|
+
// check (`readLayer.getSchema() === this.tableSchema`) skips a savepoint snapshot
|
|
2667
|
+
// restored across this ALTER and drops the transaction's staged rows at COMMIT.
|
|
2668
|
+
this.adoptSchemaOnOpenLayers(newSchema);
|
|
2288
2669
|
this.eventEmitter?.emitSchemaChange?.({
|
|
2289
2670
|
type: 'alter',
|
|
2290
2671
|
objectType: 'table',
|
|
@@ -2529,6 +2910,19 @@ export class MemoryTableManager {
|
|
|
2529
2910
|
// bug. A detached connection always has `pendingTransactionLayer === null` (disconnect
|
|
2530
2911
|
// defers while a pending layer is uncommitted), so this never discards in-flight writes.
|
|
2531
2912
|
this.repointRegisteredConnections();
|
|
2913
|
+
// Re-pointing `readLayer` is not enough: a savepoint taken while a connection held no
|
|
2914
|
+
// pending layer stored the layer it was READING as the view to restore. Consolidation
|
|
2915
|
+
// has just drained that layer into the base, and the caller is about to reshape the
|
|
2916
|
+
// base's rows — so the stored reference is a pre-change snapshot of the committed rows.
|
|
2917
|
+
// Left alone, the transaction's `rollback to savepoint` reinstates it and the old column
|
|
2918
|
+
// shape commits over the new one.
|
|
2919
|
+
//
|
|
2920
|
+
// Every connection, not just the re-pointed ones: the DDL issuer is exempt from the
|
|
2921
|
+
// sweeps above (its read view holds its own uncommitted rows) but its own lazy markers
|
|
2922
|
+
// are just as stale.
|
|
2923
|
+
for (const connection of this.knownConnections()) {
|
|
2924
|
+
connection.repointLazySavepointsToCommittedHead(this.baseLayer);
|
|
2925
|
+
}
|
|
2532
2926
|
logger.debugLog(`Schema change safety check passed for ${this._tableName}. Current committed layer is base.`);
|
|
2533
2927
|
}
|
|
2534
2928
|
/**
|
|
@@ -2548,6 +2942,34 @@ export class MemoryTableManager {
|
|
|
2548
2942
|
mc.readLayer = this.baseLayer;
|
|
2549
2943
|
}
|
|
2550
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Moves this manager's Database-registered connections from `<schema>.<oldName>` to
|
|
2947
|
+
* `<schema>.<newName>`, for `ALTER TABLE ... RENAME TO`.
|
|
2948
|
+
*
|
|
2949
|
+
* `MemoryVirtualTableConnection.tableName` is set once at registration, and
|
|
2950
|
+
* `Database.getConnectionsForTable` matches on exactly that string — so without this
|
|
2951
|
+
* sweep a rename makes the transaction's own connection invisible to
|
|
2952
|
+
* {@link registeredConnections} and everything built on it: {@link ddlConnection},
|
|
2953
|
+
* {@link knownConnections}, {@link repointRegisteredConnections},
|
|
2954
|
+
* {@link openTransactionLayersOldestFirst}. The visible symptom is a second ALTER in
|
|
2955
|
+
* the same transaction failing with BUSY — {@link ensureSchemaChangeSafety} exempts
|
|
2956
|
+
* `ddlConnection()` from its "nobody else may hold open work" sweep, and with the
|
|
2957
|
+
* registry stale that is `undefined`, so the transaction's own connection is judged a
|
|
2958
|
+
* stranger. `MemoryTable.ensureConnection` would likewise stop reusing it and register
|
|
2959
|
+
* a second connection for the same table.
|
|
2960
|
+
*
|
|
2961
|
+
* Must run BEFORE `_tableName` moves — it looks the connections up under the old name.
|
|
2962
|
+
*/
|
|
2963
|
+
rekeyRegisteredConnections(oldName, newName) {
|
|
2964
|
+
const newQualifiedName = `${this.schemaName}.${newName}`;
|
|
2965
|
+
for (const c of this.db.getConnectionsForTable(`${this.schemaName}.${oldName}`)) {
|
|
2966
|
+
if (!(c instanceof MemoryVirtualTableConnection))
|
|
2967
|
+
continue;
|
|
2968
|
+
if (c.getMemoryConnection().tableManager !== this)
|
|
2969
|
+
continue;
|
|
2970
|
+
c.rename(newQualifiedName);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2551
2973
|
/** Every Database-registered {@link MemoryTableConnection} backed by this manager. */
|
|
2552
2974
|
*registeredConnections() {
|
|
2553
2975
|
const qualifiedName = `${this.schemaName}.${this._tableName}`;
|
|
@@ -2625,20 +3047,12 @@ export class MemoryTableManager {
|
|
|
2625
3047
|
convertBaseRows(colIndex, convert, convertNulls = false) {
|
|
2626
3048
|
const out = [];
|
|
2627
3049
|
for (const row of iteratePrimaryRows(this.baseLayer.primaryTree)) {
|
|
2628
|
-
const oldVal = row[colIndex];
|
|
2629
|
-
if (oldVal === null && !convertNulls) {
|
|
2630
|
-
out.push(row);
|
|
2631
|
-
continue;
|
|
2632
|
-
}
|
|
2633
|
-
let newVal;
|
|
2634
3050
|
try {
|
|
2635
|
-
|
|
3051
|
+
out.push(convertRowAtIndex(row, colIndex, convert, convertNulls));
|
|
2636
3052
|
}
|
|
2637
3053
|
catch {
|
|
2638
3054
|
out.push(row); // shadowed unconvertible value — keep as-is
|
|
2639
|
-
continue;
|
|
2640
3055
|
}
|
|
2641
|
-
out.push(row.map((v, i) => i === colIndex ? newVal : v));
|
|
2642
3056
|
}
|
|
2643
3057
|
return out;
|
|
2644
3058
|
}
|
|
@@ -2652,31 +3066,47 @@ export class MemoryTableManager {
|
|
|
2652
3066
|
* the transaction's pending rows outside this manager, so only it can name the rows the
|
|
2653
3067
|
* issuing connection actually sees; when it supplies them, they are the judged set and this
|
|
2654
3068
|
* manager's own committed rows are ignored. See `vtab/module.ts` {@link EffectiveRowSource}.
|
|
3069
|
+
*
|
|
3070
|
+
* `mapRow` judges the rows in a form they do not yet have on disk: an `alter column` that
|
|
3071
|
+
* REWRITES values passes the per-row conversion here, so the probe sees the post-ALTER
|
|
3072
|
+
* values. Applied to whichever stream is used; the mapping is a read-side wrap and mutates
|
|
3073
|
+
* nothing.
|
|
2655
3074
|
*/
|
|
2656
|
-
async validateUniqueOverEffectiveRows(indexSchema, schema, rows) {
|
|
3075
|
+
async validateUniqueOverEffectiveRows(indexSchema, schema, rows, mapRow) {
|
|
2657
3076
|
const probe = new MemoryIndex(indexSchema, schema.columns, this.collationResolver, this.primaryKeyFunctions.compare, this.primaryKeyFunctions.encode, schema.name);
|
|
2658
3077
|
if (rows) {
|
|
2659
|
-
|
|
3078
|
+
const stream = mapRow ? mapRowsAsync(rows(), mapRow) : rows();
|
|
3079
|
+
await populateIndexFromRowsAsync(stream, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
|
|
2660
3080
|
return;
|
|
2661
3081
|
}
|
|
2662
|
-
|
|
3082
|
+
const own = this.effectiveDdlRows();
|
|
3083
|
+
populateIndexFromRows(mapRow ? mapRows(own, mapRow) : own, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
|
|
2663
3084
|
}
|
|
2664
3085
|
/**
|
|
2665
|
-
* `ALTER COLUMN
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
3086
|
+
* `ALTER COLUMN` arm of {@link validateUniqueOverEffectiveRows}: rejects the change when it
|
|
3087
|
+
* makes two of the DDL transaction's effective rows collide under any uniqueness-enforcing
|
|
3088
|
+
* structure that covers the altered column. Runs before anything is mutated, so a rejection
|
|
3089
|
+
* leaves the table and schema untouched and the transaction usable.
|
|
2669
3090
|
*
|
|
2670
|
-
*
|
|
2671
|
-
*
|
|
2672
|
-
* keys
|
|
3091
|
+
* Serves both families of change that can collapse two distinct values onto one:
|
|
3092
|
+
*
|
|
3093
|
+
* - `SET COLLATE` re-keys the structures under a new comparator; the stored values are
|
|
3094
|
+
* untouched, so no `mapRow` is passed.
|
|
3095
|
+
* - a value REWRITE (`SET DATA TYPE`, or a `SET NOT NULL` null → DEFAULT backfill) leaves
|
|
3096
|
+
* the comparators alone but changes the values; the caller passes a `mapRow` that applies
|
|
3097
|
+
* the same per-row conversion the base rewrite will, so the probe judges the post-ALTER
|
|
3098
|
+
* values.
|
|
3099
|
+
*
|
|
3100
|
+
* `newSchema` carries the post-change per-column collations AND logical types, so each probe
|
|
3101
|
+
* index compares exactly as the rebuilt structure will (it matters for e.g. `text → real`).
|
|
3102
|
+
* Indexes that do not mention the column keep their keys and need no re-check.
|
|
2673
3103
|
*
|
|
2674
3104
|
* NOTE: walks `schema.indexes`, so a UNIQUE constraint covered by a row-time materialized
|
|
2675
3105
|
* view rather than its auto-index (`findIndexForConstraint` prefers the MV) is not re-checked
|
|
2676
3106
|
* here. The auto-index always exists alongside, so the structure is still validated — but if
|
|
2677
3107
|
* an MV-only covering shape ever becomes reachable, this walk must follow it.
|
|
2678
3108
|
*/
|
|
2679
|
-
async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows) {
|
|
3109
|
+
async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows, mapRow) {
|
|
2680
3110
|
// NOTE: the probe index carries the manager's PRE-change `primaryKeyFunctions` (the new
|
|
2681
3111
|
// ones cannot exist before the schema swaps). Only the probe's per-entry PK bookkeeping
|
|
2682
3112
|
// uses them, and every effective row already has a distinct PK under the old encoder, so
|
|
@@ -2688,28 +3118,42 @@ export class MemoryTableManager {
|
|
|
2688
3118
|
if (!indexEnforcesUnique(newSchema, indexSchema))
|
|
2689
3119
|
continue;
|
|
2690
3120
|
// `rows` is re-callable, so each structure gets a fresh stream.
|
|
2691
|
-
await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows);
|
|
3121
|
+
await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows, mapRow);
|
|
2692
3122
|
}
|
|
2693
3123
|
}
|
|
2694
3124
|
/**
|
|
2695
3125
|
* PRIMARY KEY arm of the `alter column … set collate` pre-pass. Runs before anything is
|
|
2696
3126
|
* mutated, so a rejection leaves the table, the schema and the transaction untouched.
|
|
2697
3127
|
*
|
|
2698
|
-
*
|
|
2699
|
-
*
|
|
2700
|
-
*
|
|
2701
|
-
*
|
|
3128
|
+
* Asks two questions, in order, over two DIFFERENT row sets:
|
|
3129
|
+
*
|
|
3130
|
+
* 1. **Is the change legal at all?** — probes the rows the DDL transaction can SEE: the
|
|
3131
|
+
* wrapper-supplied `rows` when a wrapper module (the isolation layer) holds the
|
|
3132
|
+
* transaction's pending rows outside this manager, otherwise this manager's own
|
|
3133
|
+
* layered view ({@link effectiveDdlRows}). A duplicate here is visible to a `select`
|
|
3134
|
+
* in this transaction, so the change is simply illegal → `CONSTRAINT`, naming the
|
|
3135
|
+
* colliding key.
|
|
3136
|
+
* 2. **Can the structures physically carry it?** — probes every layer of this manager's
|
|
3137
|
+
* own chain. The primary tree is a map, not a multi-map, so — unlike a secondary
|
|
3138
|
+
* index — no tree that a `rollback` / `rollback to savepoint` could restore may hold
|
|
3139
|
+
* two rows whose keys collapse under the new comparator. A collision confined to
|
|
3140
|
+
* committed rows the transaction has DELETED is therefore refused by physical
|
|
3141
|
+
* necessity, not because the data is invalid: the base must keep both rows for a
|
|
3142
|
+
* rollback, and a base re-keyed under the new collation could not represent the pair
|
|
3143
|
+
* at all → `BUSY` with the same "commit/rollback and retry" posture as
|
|
3144
|
+
* {@link ensureSchemaChangeSafety}. The persistent store backend refuses the same
|
|
3145
|
+
* shape for the same reason (its committed rows equally survive a rollback — see
|
|
3146
|
+
* `backlog/bug-store-pk-collate-rejects-deleted-row-collision`); accepting it would
|
|
3147
|
+
* take transaction-scoped DDL (`backlog/feat-transactional-ddl-native-backends`),
|
|
3148
|
+
* which Quereus does not have.
|
|
2702
3149
|
*
|
|
2703
|
-
*
|
|
2704
|
-
*
|
|
2705
|
-
*
|
|
2706
|
-
*
|
|
2707
|
-
*
|
|
2708
|
-
* represent the pair at all. Committing (or rolling back) settles the transaction and
|
|
2709
|
-
* the ALTER can be retried — the same "commit/rollback and retry" posture as
|
|
2710
|
-
* {@link ensureSchemaChangeSafety}.
|
|
3150
|
+
* The passes judge different sets because a wrapper's effective stream and this manager's
|
|
3151
|
+
* layers genuinely diverge: staged inserts exist only in the stream, deleted committed
|
|
3152
|
+
* rows only in the layers. When `rows` is supplied, the view layer holds the COMMITTED
|
|
3153
|
+
* rows — not a subset of what pass 1 judged — so pass 2 starts AT the view; when `rows`
|
|
3154
|
+
* is absent, pass 1 judged exactly the view's rows and pass 2 starts at its parent.
|
|
2711
3155
|
*
|
|
2712
|
-
*
|
|
3156
|
+
* Pass 2 is deliberately conservative. The chain holds one immutable layer per statement
|
|
2713
3157
|
* boundary (see `MemoryTableConnection.createSavepoint`'s eager path), so it rejects any
|
|
2714
3158
|
* transaction that has held a colliding pair at ANY statement boundary — even one whose
|
|
2715
3159
|
* final view is clean and whose intermediate layer no savepoint can reach. Narrowing that
|
|
@@ -2721,40 +3165,78 @@ export class MemoryTableManager {
|
|
|
2721
3165
|
* with no collisions anywhere in the chain, every primary key resolves to at most one row
|
|
2722
3166
|
* in each layer under the new comparator.
|
|
2723
3167
|
*/
|
|
2724
|
-
validateRekeyedPrimaryKey(newSchema) {
|
|
3168
|
+
async validateRekeyedPrimaryKey(newSchema, rows) {
|
|
2725
3169
|
const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
|
|
2726
3170
|
const connection = this.ddlConnection();
|
|
2727
3171
|
const view = connection
|
|
2728
3172
|
? (connection.pendingTransactionLayer ?? connection.readLayer)
|
|
2729
3173
|
: this.baseLayer;
|
|
2730
|
-
this.
|
|
3174
|
+
await this.assertNoPrimaryKeyCollisionInRows(rows ? rows() : this.effectiveDdlRows(), newPkFunctions, primaryKeyArity(newSchema) !== 1);
|
|
2731
3175
|
// `ensureSchemaChangeSafety` has already drained every committed layer into the base and
|
|
2732
|
-
// rejected sibling connections with open work, so
|
|
2733
|
-
//
|
|
2734
|
-
for (let layer = view.getParent(); layer; layer = layer.getParent()) {
|
|
2735
|
-
this.
|
|
2736
|
-
|
|
2737
|
-
|
|
3176
|
+
// rejected sibling connections with open work, so this walk covers the transaction's own
|
|
3177
|
+
// layers plus the base — every tree a rollback could restore.
|
|
3178
|
+
for (let layer = rows ? view : view.getParent(); layer; layer = layer.getParent()) {
|
|
3179
|
+
this.assertNoPrimaryKeyCollisionInLayer(layer, newPkFunctions);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* A duplicate-key detector over the new primary key functions: feed it rows, and it returns
|
|
3184
|
+
* the key the moment one repeats. Shared by both arms of {@link validateRekeyedPrimaryKey}
|
|
3185
|
+
* so the async (effective-row) arm and the sync (layer-tree) arm cannot disagree about what
|
|
3186
|
+
* counts as a collision.
|
|
3187
|
+
*
|
|
3188
|
+
* NOTE: the probe holds every row it has seen, because the BTree derives its key from the
|
|
3189
|
+
* stored value — so an ALTER over a large table transiently doubles that table's row
|
|
3190
|
+
* references. If a wide table ever makes this the memory peak, key the probe by the PK
|
|
3191
|
+
* encoding (`primaryKeyFunctions.encode`) into a `Set` instead of by the row.
|
|
3192
|
+
*/
|
|
3193
|
+
makePrimaryKeyProbe(pkFunctions) {
|
|
3194
|
+
const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
|
|
3195
|
+
return (row) => {
|
|
3196
|
+
const key = pkFunctions.extractFromRow(row);
|
|
3197
|
+
if (probe.get(key) !== undefined)
|
|
3198
|
+
return key;
|
|
3199
|
+
probe.insert(row);
|
|
3200
|
+
return undefined;
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Legality arm of {@link validateRekeyedPrimaryKey}: raises `CONSTRAINT`, naming the key,
|
|
3205
|
+
* when two of the rows the DDL transaction can SEE share a primary key under `pkFunctions`.
|
|
3206
|
+
* Takes a wrapper's merged async stream or this manager's own synchronous layered view.
|
|
3207
|
+
*/
|
|
3208
|
+
async assertNoPrimaryKeyCollisionInRows(rows, pkFunctions, keyIsTuple) {
|
|
3209
|
+
const seen = this.makePrimaryKeyProbe(pkFunctions);
|
|
3210
|
+
for await (const row of rows) {
|
|
3211
|
+
const key = seen(row);
|
|
3212
|
+
if (key === undefined)
|
|
3213
|
+
continue;
|
|
3214
|
+
const keyDesc = keyParts(key, keyIsTuple).map(formatKeyValue).join(', ');
|
|
3215
|
+
throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} primary key collides under new collation (key: ${keyDesc})`, StatusCode.CONSTRAINT);
|
|
2738
3216
|
}
|
|
2739
3217
|
}
|
|
2740
3218
|
/**
|
|
2741
|
-
*
|
|
3219
|
+
* Representability arm of {@link validateRekeyedPrimaryKey}: raises `BUSY` when two of
|
|
3220
|
+
* `layer`'s physical rows share a primary key under `pkFunctions`. Its rows are ones a
|
|
3221
|
+
* rollback could restore, not ones the transaction can see, so this is never a statement
|
|
3222
|
+
* about the data's validity — hence the retryable status and wording.
|
|
2742
3223
|
*
|
|
2743
3224
|
* NOTE: O(rows) per layer, so O(layers × rows) for a whole chain — one more full pass than
|
|
2744
3225
|
* the base rebuild the caller is about to do anyway. Fine for a statement this rare; if a
|
|
2745
3226
|
* deep savepoint stack over a large table ever makes an ALTER slow, note that a layer's rows
|
|
2746
3227
|
* differ from its parent's only at the keys it wrote, so the walk can be narrowed to those.
|
|
2747
3228
|
*/
|
|
2748
|
-
|
|
3229
|
+
assertNoPrimaryKeyCollisionInLayer(layer, pkFunctions) {
|
|
2749
3230
|
const tree = layer.getModificationTree('primary');
|
|
2750
3231
|
if (!tree)
|
|
2751
3232
|
return;
|
|
2752
|
-
const
|
|
3233
|
+
const seen = this.makePrimaryKeyProbe(pkFunctions);
|
|
2753
3234
|
for (const row of iteratePrimaryRows(tree)) {
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3235
|
+
if (seen(row) !== undefined) {
|
|
3236
|
+
throw new QuereusError(`Cannot change the collation of a primary key column of table ${this._tableName}: `
|
|
3237
|
+
+ `rows this transaction has removed still collide under the new collation and must survive a rollback. `
|
|
3238
|
+
+ `Commit/rollback and retry.`, StatusCode.BUSY);
|
|
3239
|
+
}
|
|
2758
3240
|
}
|
|
2759
3241
|
}
|
|
2760
3242
|
/**
|
|
@@ -2792,7 +3274,7 @@ export class MemoryTableManager {
|
|
|
2792
3274
|
* `convertNulls` routes null own-values through `convert` (the SET NOT NULL null → DEFAULT map);
|
|
2793
3275
|
* SET DATA TYPE leaves them untouched. No-op in autocommit.
|
|
2794
3276
|
*
|
|
2795
|
-
* PRIMARY-KEY columns never reach here —
|
|
3277
|
+
* PRIMARY-KEY columns never reach here — any retype of a key column is rejected before
|
|
2796
3278
|
* any mutation, and SET NOT NULL leaves the key bytes unchanged — so the primary tree's keys are
|
|
2797
3279
|
* stable and no re-key is needed.
|
|
2798
3280
|
*/
|
|
@@ -2801,14 +3283,58 @@ export class MemoryTableManager {
|
|
|
2801
3283
|
layer.convertColumn(colIndex, convert, newSchema, convertNulls);
|
|
2802
3284
|
}
|
|
2803
3285
|
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Phase 1 of propagating `add column` / `drop column` into every open transaction layer:
|
|
3288
|
+
* computes each layer's net own-writes rewritten to the new column set, WITHOUT mutating
|
|
3289
|
+
* anything (see {@link TransactionLayer.prepareReshapedColumns}). Runs BEFORE the base is
|
|
3290
|
+
* touched, because `reshapeRow` can throw — ADD COLUMN's per-row backfill evaluates an
|
|
3291
|
+
* engine-supplied `default (new.<col>)` expression against each pending row, and enforces
|
|
3292
|
+
* NOT NULL on the result — and a failure part-way through a mutation pass would leave the
|
|
3293
|
+
* base and the already-visited layers at the new arity with the rest at the old, with no
|
|
3294
|
+
* undo. Failing here instead rejects the ALTER with every structure untouched, exactly like
|
|
3295
|
+
* the pre-mutation validation passes of `alterColumn`.
|
|
3296
|
+
*
|
|
3297
|
+
* The returned plans pin the layer set: {@link installReshapeOnOpenLayers} walks the plans,
|
|
3298
|
+
* not a fresh chain snapshot, so the two phases cannot disagree about which layers exist.
|
|
3299
|
+
* Reading each layer's effective rows here — before the base rebuild — yields the same rows
|
|
3300
|
+
* the single-pass prototype read after it: a layer's pre-reshape tree resolves against the
|
|
3301
|
+
* parent's OLD tree either way (the base rebuild REPLACES the base's tree object; it never
|
|
3302
|
+
* mutates the one the layers inherit from).
|
|
3303
|
+
*/
|
|
3304
|
+
async prepareReshapeOnOpenLayers(reshapeRow, reshapeEventRow) {
|
|
3305
|
+
const plans = [];
|
|
3306
|
+
for (const layer of this.openTransactionLayersOldestFirst()) {
|
|
3307
|
+
plans.push({ layer, prepared: await layer.prepareReshapedColumns(reshapeRow, reshapeEventRow) });
|
|
3308
|
+
}
|
|
3309
|
+
return plans;
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Phase 2: installs the prepared reshape into each open transaction layer, oldest-first
|
|
3313
|
+
* (the plans were gathered in that order), after the base has been rebuilt at the new arity
|
|
3314
|
+
* — each layer's rebuilt tree inherits copy-on-write from its parent's NEW one. Synchronous
|
|
3315
|
+
* and mutation-only; everything fallible ran in {@link prepareReshapeOnOpenLayers}.
|
|
3316
|
+
*
|
|
3317
|
+
* Beyond keeping the transaction's own reads correct, this is what makes the
|
|
3318
|
+
* `commitTransaction` snapshot-wrap identity check (`readLayer.getSchema() ===
|
|
3319
|
+
* this.tableSchema`) pass after a `rollback to savepoint`: without it the eager savepoint
|
|
3320
|
+
* snapshot keeps its frozen pre-ALTER schema, the wrap is skipped, and the snapshot's rows
|
|
3321
|
+
* are silently dropped at commit.
|
|
3322
|
+
*/
|
|
3323
|
+
installReshapeOnOpenLayers(newSchema, plans) {
|
|
3324
|
+
for (const { layer, prepared } of plans) {
|
|
3325
|
+
layer.installReshapedColumns(newSchema, prepared);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
2804
3328
|
/**
|
|
2805
3329
|
* The DDL connection's open {@link TransactionLayer}s, oldest-first, base excluded — its
|
|
2806
3330
|
* pending layer and every savepoint snapshot below it, the layers on the view's parent chain
|
|
2807
3331
|
* above the base. Empty in autocommit (no connection, or the view IS the base).
|
|
2808
3332
|
*
|
|
2809
|
-
*
|
|
2810
|
-
* ({@link convertColumnOnOpenLayers})
|
|
2811
|
-
*
|
|
3333
|
+
* Schema adoption ({@link adoptSchemaOnOpenLayers}), value conversion
|
|
3334
|
+
* ({@link convertColumnOnOpenLayers}), and column-set reshape
|
|
3335
|
+
* ({@link prepareReshapeOnOpenLayers} / {@link installReshapeOnOpenLayers}) MUST all apply
|
|
3336
|
+
* oldest-first: each layer rebuilds its structures over its parent's, so the parent's must
|
|
3337
|
+
* already be the new/converted/reshaped ones.
|
|
2812
3338
|
*
|
|
2813
3339
|
* NOTE: the walk takes every TransactionLayer below the view, which normally means the pending
|
|
2814
3340
|
* layer and its savepoint snapshots. A committed layer already drained into the base by
|
|
@@ -2841,6 +3367,17 @@ export class MemoryTableManager {
|
|
|
2841
3367
|
logger.debugLog(`[Consolidate] Copying data from transaction layer ${transactionLayer.getLayerId()} to base layer for ${this._tableName}`);
|
|
2842
3368
|
// Copy all data from the transaction layer to the base layer
|
|
2843
3369
|
await this.copyTransactionDataToBase(transactionLayer);
|
|
3370
|
+
// The drained chain's pending-change event logs were already delivered when
|
|
3371
|
+
// each layer committed, but the layers can remain in the DDL connection's own
|
|
3372
|
+
// open-transaction parent chain. Once the base becomes the committed head, the
|
|
3373
|
+
// commit-time collection (`collectPendingChanges`) walks that chain down to
|
|
3374
|
+
// the base and would re-deliver them — clear the logs so consolidation is
|
|
3375
|
+
// event-neutral. (Savepoint snapshots of the OPEN transaction sit above the
|
|
3376
|
+
// committed head, not in this chain, so their un-emitted events are kept.)
|
|
3377
|
+
for (let cur = transactionLayer; cur && cur !== this.baseLayer; cur = cur.getParent()) {
|
|
3378
|
+
if (cur instanceof TransactionLayer)
|
|
3379
|
+
cur.clearPendingChanges();
|
|
3380
|
+
}
|
|
2844
3381
|
// Force all connections to read from the base layer
|
|
2845
3382
|
for (const conn of this.connections.values()) {
|
|
2846
3383
|
if (conn.readLayer === transactionLayer) {
|