@quereus/quereus 4.3.2 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- 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-external-changes.d.ts +2 -2
- package/dist/src/core/database-external-changes.d.ts.map +1 -1
- package/dist/src/core/database-external-changes.js +12 -9
- package/dist/src/core/database-external-changes.js.map +1 -1
- package/dist/src/core/database-materialized-views-apply.d.ts +156 -14
- package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-apply.js +370 -65
- package/dist/src/core/database-materialized-views-apply.js.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts +66 -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 +368 -8
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
- package/dist/src/core/database-materialized-views-plans.d.ts +257 -30
- package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +99 -42
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +198 -121
- 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 +41 -18
- 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 +146 -62
- 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/registration.d.ts +19 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +41 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +8 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -11
- 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/comparison-collation.d.ts +2 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.js +1 -1
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
- package/dist/src/planner/analysis/equi-correlation.d.ts +26 -0
- package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
- package/dist/src/planner/analysis/equi-correlation.js +62 -0
- package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
- 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/query-rewrite-matcher.d.ts +60 -18
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +95 -42
- 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 +28 -0
- package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
- package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
- package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
- 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 +87 -6
- 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/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.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +15 -1
- package/dist/src/planner/building/select.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/building/with.d.ts.map +1 -1
- package/dist/src/planner/building/with.js +5 -8
- package/dist/src/planner/building/with.js.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.d.ts +33 -0
- package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.js +164 -28
- package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
- package/dist/src/planner/cache/reference-graph.d.ts +0 -4
- package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
- package/dist/src/planner/cache/reference-graph.js +5 -13
- package/dist/src/planner/cache/reference-graph.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +28 -7
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +26 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/framework/pass.js +3 -3
- package/dist/src/planner/nodes/cache-node.d.ts +18 -2
- package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cache-node.js +42 -3
- package/dist/src/planner/nodes/cache-node.js.map +1 -1
- package/dist/src/planner/nodes/cte-node.d.ts +15 -1
- package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cte-node.js +12 -2
- package/dist/src/planner/nodes/cte-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/recursive-cte-node.d.ts +30 -1
- package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.js +30 -3
- package/dist/src/planner/nodes/recursive-cte-node.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/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +129 -8
- package/dist/src/planner/optimizer.js.map +1 -1
- 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 +43 -18
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.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 +72 -54
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +31 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +68 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.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/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +627 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/runtime/cache/shared-cache.d.ts +7 -0
- package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
- package/dist/src/runtime/cache/shared-cache.js +34 -1
- package/dist/src/runtime/cache/shared-cache.js.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.js +10 -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 +2 -3
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +10 -84
- 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 +348 -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 +6 -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 +32 -3
- 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 +22 -2
- 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 +24 -3
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/cache.d.ts +7 -3
- package/dist/src/runtime/emit/cache.d.ts.map +1 -1
- package/dist/src/runtime/emit/cache.js +23 -6
- package/dist/src/runtime/emit/cache.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 +41 -44
- 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/cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/cte.js +43 -20
- package/dist/src/runtime/emit/cte.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 +241 -115
- package/dist/src/runtime/emit/dml-executor.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.js +5 -0
- package/dist/src/runtime/emit/join.js.map +1 -1
- 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 +38 -7
- 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 +18 -8
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.js +59 -1
- package/dist/src/runtime/emit/recursive-cte.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 +20 -1
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +23 -1
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/types.d.ts +54 -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 +109 -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/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 +89 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +156 -27
- package/dist/src/util/comparison.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 +6 -1
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +14 -5
- 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 +96 -13
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +595 -119
- 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 +69 -36
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +127 -13
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +214 -65
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +9 -2
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +3 -2
- 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 +24 -6
- 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 -19
- 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 -53
- 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';
|
|
@@ -8,19 +9,83 @@ import { MemoryVirtualTableConnection } from '../connection.js';
|
|
|
8
9
|
import { QuereusError } from '../../../common/errors.js';
|
|
9
10
|
import { ConflictResolution } from '../../../common/constants.js';
|
|
10
11
|
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError } from '../../../schema/constraint-builder.js';
|
|
11
|
-
import { indexEnforcesUnique, uniqueEnforcementCollations } from '../../../schema/unique-enforcement.js';
|
|
12
|
-
import {
|
|
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',
|
|
@@ -1738,12 +1995,23 @@ export class MemoryTableManager {
|
|
|
1738
1995
|
// A collation change re-keys any PK / UNIQUE / index that orders by this
|
|
1739
1996
|
// column, so it needs the structure re-sort + uniqueness re-validation below.
|
|
1740
1997
|
let collationChanged = false;
|
|
1998
|
+
// Set when a SET DATA TYPE moves to a type that ORDERS values differently
|
|
1999
|
+
// (comparisonSemanticsDiffer). Same consequence as a collation change — every
|
|
2000
|
+
// structure keyed by the column has to be re-sorted and its uniqueness re-judged —
|
|
2001
|
+
// but a separate flag, because the index-column collation propagation and the
|
|
2002
|
+
// `pkColumnRekeyed` primary-tree path below belong to SET COLLATE alone. Usually
|
|
2003
|
+
// coincides with `valueConvert` (every non-alias retype sets that); the rebuild
|
|
2004
|
+
// chains below test `valueConvert` FIRST, whose full-rebuild path subsumes the
|
|
2005
|
+
// comparator-only re-sort.
|
|
2006
|
+
let comparatorChanged = false;
|
|
1741
2007
|
// Set to the per-value conversion function when a physical value rewrite is needed:
|
|
1742
|
-
// SET DATA TYPE (
|
|
1743
|
-
//
|
|
1744
|
-
//
|
|
1745
|
-
//
|
|
1746
|
-
//
|
|
2008
|
+
// SET DATA TYPE (every non-alias retype — values normalize to the new type's
|
|
2009
|
+
// canonical spelling even when the storage class is unchanged) or SET NOT NULL
|
|
2010
|
+
// backfill (map null → DEFAULT). Both REPLACE the base primary tree with a
|
|
2011
|
+
// freshly-built one (never an in-place mutation, which inheritree forbids while the
|
|
2012
|
+
// open transaction's layers derive from it) and convert the transaction's
|
|
2013
|
+
// own-written rows in every open layer, after `updateSchema` (see the rebuild
|
|
2014
|
+
// section and convertColumnOnOpenLayers).
|
|
1747
2015
|
let valueConvert = null;
|
|
1748
2016
|
// SET NOT NULL backfill also routes NULL values through `valueConvert`; SET DATA TYPE
|
|
1749
2017
|
// leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
|
|
@@ -1821,19 +2089,40 @@ export class MemoryTableManager {
|
|
|
1821
2089
|
}
|
|
1822
2090
|
else if (change.setDataType !== undefined) {
|
|
1823
2091
|
const newLogicalType = inferType(change.setDataType);
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2092
|
+
// Gate on logical-type IDENTITY, not the physical storage class: `inferType`
|
|
2093
|
+
// flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`,
|
|
2094
|
+
// `bigint` IS `INTEGER_TYPE`), so an alias retype is schema-only — no scan, no
|
|
2095
|
+
// rewrite, no re-key. Every OTHER retype validates AND normalizes every value,
|
|
2096
|
+
// whether or not the storage class changes: a same-class move (text → date)
|
|
2097
|
+
// must reject values the new type refuses ('hello') and rewrite the rest to
|
|
2098
|
+
// the new type's canonical spelling ('2024-06-05T00:00:00Z' → '2024-06-05'),
|
|
2099
|
+
// or the column holds values no INSERT could have produced — unfindable by
|
|
2100
|
+
// equality (DATE compares BINARY over the stored text) and invisible to UNIQUE.
|
|
2101
|
+
// NOTE: the rewrite is unconditional once the types differ, even when every
|
|
2102
|
+
// converted value comes back byte-identical (already-canonical ISO durations on a
|
|
2103
|
+
// text → timespan). That costs a full base-tree + secondary-index rebuild here, and
|
|
2104
|
+
// a physical rewrite of every row on the store leg. If ALTER on large tables ever
|
|
2105
|
+
// shows up as slow, have the convert pre-pass record whether any value actually
|
|
2106
|
+
// moved and, when none did, fall through to the comparator-only re-key.
|
|
2107
|
+
if (newLogicalType !== oldCol.logicalType) {
|
|
2108
|
+
// Retyping a PRIMARY KEY column moves the key bytes (a class change), the
|
|
2109
|
+
// tree's ordering (a comparator move), or at minimum the key values'
|
|
2110
|
+
// canonical spelling — and neither the base rewrite below nor the
|
|
2111
|
+
// transaction-layer conversion re-keys the primary tree. The engine already
|
|
2112
|
+
// refuses SET DATA TYPE on any PK column (runtime/emit/alter-table.ts);
|
|
2113
|
+
// this guards direct module calls. Reject rather than corrupt — the
|
|
2114
|
+
// type-change analogue of the SET COLLATE primary-key carve-out
|
|
2115
|
+
// (`alter-collate-pk-in-transaction`).
|
|
1834
2116
|
if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
|
|
1835
2117
|
throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
|
|
1836
2118
|
}
|
|
2119
|
+
// Every MemoryIndex builds its comparator from the column's LOGICAL type
|
|
2120
|
+
// (`createTypedComparator(columnSchema.logicalType, …)` in vtab/memory/index.ts),
|
|
2121
|
+
// so a move to a differently-ordering type (text ↔ timespan: 'PT1H' ≡ 'PT60M';
|
|
2122
|
+
// text ↔ date/time/datetime: binary, ignoring the column's collation) re-sorts
|
|
2123
|
+
// every structure keyed by the column. Tracked separately from the value rewrite
|
|
2124
|
+
// because SET COLLATE shares the re-key machinery without rewriting values.
|
|
2125
|
+
comparatorChanged = comparisonSemanticsDiffer(oldCol.logicalType, newLogicalType);
|
|
1837
2126
|
const convert = (v) => {
|
|
1838
2127
|
try {
|
|
1839
2128
|
return validateAndParse(v, newLogicalType, change.columnName);
|
|
@@ -1868,8 +2157,8 @@ export class MemoryTableManager {
|
|
|
1868
2157
|
// derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
|
|
1869
2158
|
// replacement and the transaction-layer conversion.
|
|
1870
2159
|
valueConvert = convert;
|
|
1871
|
-
newCol = { ...oldCol, logicalType: newLogicalType };
|
|
1872
2160
|
}
|
|
2161
|
+
newCol = { ...oldCol, logicalType: newLogicalType };
|
|
1873
2162
|
}
|
|
1874
2163
|
else if (change.setDefault !== undefined) {
|
|
1875
2164
|
newCol = { ...oldCol, defaultValue: change.setDefault };
|
|
@@ -1878,15 +2167,30 @@ export class MemoryTableManager {
|
|
|
1878
2167
|
throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
|
|
1879
2168
|
}
|
|
1880
2169
|
const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newCol : c);
|
|
2170
|
+
// The changes that move the COMPARATOR the structures are keyed by: SET COLLATE
|
|
2171
|
+
// (values untouched) and a retype into a differently-ordering type (which, unless it
|
|
2172
|
+
// is an alias retype, ALSO rewrites values — `valueConvert` — and its full-rebuild
|
|
2173
|
+
// path below subsumes the comparator-only re-sort). Both need fresh IndexSchema
|
|
2174
|
+
// objects and, when no rewrite runs, the re-validate / re-sort / adopt sequence.
|
|
2175
|
+
const structuresRekeyed = collationChanged || comparatorChanged;
|
|
1881
2176
|
// Propagate a collation change into every PK-definition entry and index
|
|
1882
2177
|
// column that orders by this column, so their comparators re-key under it.
|
|
1883
2178
|
const updatedPkDef = collationChanged
|
|
1884
2179
|
? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
|
|
1885
2180
|
: this.tableSchema.primaryKeyDefinition;
|
|
1886
|
-
|
|
2181
|
+
// Every re-keying change also REBUILDS the IndexSchema objects, because their identity is
|
|
2182
|
+
// the discriminator `TransactionLayer.adoptSchema` uses to decide an open layer's
|
|
2183
|
+
// MemoryIndex must be replaced rather than kept (an index it holds under an object the new
|
|
2184
|
+
// schema still carries is left alone). SET COLLATE has a field to write — the index
|
|
2185
|
+
// column's own collation. A comparator-only retype has none: an index column carries no
|
|
2186
|
+
// logical type, it reads the column's from `newSchema.columns`, so the fresh object IS the
|
|
2187
|
+
// whole signal. Without it the base rebuilt its trees under the new comparator while the
|
|
2188
|
+
// DDL transaction's own layers went on comparing the old way — and shadowed the base's
|
|
2189
|
+
// rebuilt trees once the pending layer became the committed head.
|
|
2190
|
+
const updatedIndexes = (structuresRekeyed && this.tableSchema.indexes)
|
|
1887
2191
|
? this.tableSchema.indexes.map(idx => ({
|
|
1888
2192
|
...idx,
|
|
1889
|
-
columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, collation: newCol.collation } : ic),
|
|
2193
|
+
columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
|
|
1890
2194
|
}))
|
|
1891
2195
|
: this.tableSchema.indexes;
|
|
1892
2196
|
const finalNewTableSchema = Object.freeze({
|
|
@@ -1898,14 +2202,49 @@ export class MemoryTableManager {
|
|
|
1898
2202
|
});
|
|
1899
2203
|
const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
|
|
1900
2204
|
// Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows: a pair
|
|
1901
|
-
// the transaction inserted that collides under the NEW
|
|
2205
|
+
// the transaction inserted that collides under the NEW schema must reject the
|
|
1902
2206
|
// change, and one it has deleted must not block it. A throw here leaves the schema,
|
|
1903
2207
|
// the base layer and the index map exactly as they were.
|
|
1904
2208
|
//
|
|
1905
|
-
//
|
|
1906
|
-
//
|
|
1907
|
-
//
|
|
1908
|
-
|
|
2209
|
+
// A value-REWRITING change (`valueConvert`: SET DATA TYPE, or a SET NOT NULL null →
|
|
2210
|
+
// DEFAULT backfill) probes the CONVERTED rows — the same per-row conversion the base
|
|
2211
|
+
// rewrite below applies — under the NEW schema's comparators, so one call covers both
|
|
2212
|
+
// ways an ALTER can collapse two previously-distinct rows: the VALUES converge
|
|
2213
|
+
// (`'1'`/`'01'` → 1; two NULLs → one DEFAULT; '2024-06-05T00:00:00Z' → '2024-06-05')
|
|
2214
|
+
// and/or the COMPARATOR moves (text → timespan makes 'PT1H'/'PT60M' one value). A
|
|
2215
|
+
// same-storage-class retype sets `valueConvert` AND `structuresRekeyed`, so the
|
|
2216
|
+
// rewrite arm is tested FIRST — its converted-row probe subsumes the comparator-only
|
|
2217
|
+
// one. The rebuild that follows is deliberately non-enforcing, so this is the only
|
|
2218
|
+
// guard.
|
|
2219
|
+
//
|
|
2220
|
+
// The comparator-MOVING arm without a rewrite has one trigger left: SET COLLATE. The
|
|
2221
|
+
// values are untouched — hence no `mapRow` — and only the comparator the structures
|
|
2222
|
+
// are keyed by moves. `newSchema` carries the new collations, so the probe indexes
|
|
2223
|
+
// compare as the rebuilt ones will.
|
|
2224
|
+
//
|
|
2225
|
+
// The primary key: SET COLLATE on a PK member gets a stricter pre-pass
|
|
2226
|
+
// (`validateRekeyedPrimaryKey`): no layer in the chain, base included, may hold a
|
|
2227
|
+
// collision. That is what lets every step below succeed unconditionally, and what
|
|
2228
|
+
// `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass
|
|
2229
|
+
// and cannot shadow one: a retype of a PK column is rejected upstream, the backfill
|
|
2230
|
+
// only fires on a column holding NULLs (which a PK member cannot — the engine
|
|
2231
|
+
// enforces NOT NULL on every PK member regardless of the declared nullability), and
|
|
2232
|
+
// `pkColumnRekeyed` is set only by SET COLLATE, which never sets `valueConvert` — so
|
|
2233
|
+
// whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm still runs.
|
|
2234
|
+
// NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK
|
|
2235
|
+
// collision path (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey`
|
|
2236
|
+
// plus a primary-tree re-key of its own.
|
|
2237
|
+
if (valueConvert) {
|
|
2238
|
+
const convert = valueConvert;
|
|
2239
|
+
const nulls = convertNulls;
|
|
2240
|
+
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows,
|
|
2241
|
+
// Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
|
|
2242
|
+
// the probe sees is visible to the transaction, and the setDataType pre-pass above
|
|
2243
|
+
// already proved each one convertible, so a throw is unreachable; surfacing it as
|
|
2244
|
+
// MISMATCH still beats probing a stale value that could mask a collision.
|
|
2245
|
+
row => convertRowAtIndex(row, colIndex, convert, nulls));
|
|
2246
|
+
}
|
|
2247
|
+
else if (structuresRekeyed) {
|
|
1909
2248
|
await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
|
|
1910
2249
|
// NOTE: `validateRekeyedPrimaryKey` deliberately ignores `rows`. It asserts that no
|
|
1911
2250
|
// LAYER of this manager's own chain holds a PK collision under the new comparator —
|
|
@@ -1919,27 +2258,23 @@ export class MemoryTableManager {
|
|
|
1919
2258
|
this.validateRekeyedPrimaryKey(finalNewTableSchema);
|
|
1920
2259
|
}
|
|
1921
2260
|
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) {
|
|
2261
|
+
// Both arms rebuild the base's structures under the schema `updateSchema` above just
|
|
2262
|
+
// swapped in — each MemoryIndex rebuilds its comparator from the CURRENT schema's
|
|
2263
|
+
// collation and logical type. The secondary rebuilds are NON-enforcing (the pre-pass
|
|
2264
|
+
// above owns uniqueness, and the base's rows are not a subset of the effective rows);
|
|
2265
|
+
// the SET COLLATE PK rebuild is strict, since a PK collision cannot be represented at
|
|
2266
|
+
// all — the pre-pass has already proved the base collision-free, so the strict
|
|
2267
|
+
// rebuild is a live invariant check, not the enforcement path. It runs LAST so its
|
|
2268
|
+
// throw leaves the live tree intact for the catch's rollback.
|
|
2269
|
+
if (valueConvert) {
|
|
1937
2270
|
// SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
|
|
1938
2271
|
// the primary tree with a fresh one holding them (not an in-place upsert — inheritree
|
|
1939
2272
|
// forbids mutating a base while the open transaction's layers derive from it).
|
|
1940
2273
|
// `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
|
|
1941
|
-
// rows, so index-backed lookups see the new values
|
|
1942
|
-
//
|
|
2274
|
+
// rows under the new comparators, so index-backed lookups see the new values — which
|
|
2275
|
+
// is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
|
|
2276
|
+
// same-storage-class retype sets `structuresRekeyed` too. The old tree is saved for
|
|
2277
|
+
// the catch's rollback, exactly as the PK-rekey path does.
|
|
1943
2278
|
// NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
|
|
1944
2279
|
// if a wide-index table ever shows this as slow, filter to indexes whose columns
|
|
1945
2280
|
// include colIndex. Mirrors the collationChanged path's unconditional rebuild.
|
|
@@ -1954,31 +2289,56 @@ export class MemoryTableManager {
|
|
|
1954
2289
|
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
1955
2290
|
this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
|
|
1956
2291
|
}
|
|
2292
|
+
else if (structuresRekeyed) {
|
|
2293
|
+
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2294
|
+
if (pkColumnRekeyed) {
|
|
2295
|
+
basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
|
|
2296
|
+
this.baseLayer.rebuildPrimaryTreeStrict();
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
1957
2299
|
this.tableSchema = finalNewTableSchema;
|
|
1958
2300
|
this.initializePrimaryKeyFunctions();
|
|
1959
2301
|
// The base rebuild handed every secondary index a fresh tree under the new
|
|
1960
|
-
//
|
|
2302
|
+
// comparator; the DDL transaction's own layers still inherit the old ones and froze
|
|
1961
2303
|
// the old schema at construction. Re-key them, or the rest of the transaction — and
|
|
1962
2304
|
// everything after the pending layer becomes the committed head at commit — keeps
|
|
1963
|
-
// comparing under the old collation.
|
|
2305
|
+
// comparing under the old collation / old logical type.
|
|
1964
2306
|
//
|
|
1965
2307
|
// When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
|
|
1966
2308
|
// swapped the base primary tree object out from under those layers' copy-on-write
|
|
1967
2309
|
// bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
|
|
1968
2310
|
// every secondary index (each derives its PK comparator/encoder from the PK
|
|
1969
2311
|
// 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) {
|
|
2312
|
+
if (valueConvert) {
|
|
1974
2313
|
// SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
|
|
1975
2314
|
// transaction's own open layers still froze the old schema and hold their own-written
|
|
1976
2315
|
// rows unconverted. Swap the schema and convert those values so the rest of the
|
|
1977
2316
|
// transaction — and the committed head at commit — read the new value. For SET NOT
|
|
1978
2317
|
// 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).
|
|
2318
|
+
// layer, never reached by the old in-place base upsert). `convertColumn` installs the
|
|
2319
|
+
// new schema and rebuilds every one of the layer's secondary indexes from it, so it
|
|
2320
|
+
// subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
|
|
2321
|
+
// No-op in autocommit.
|
|
1980
2322
|
this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
|
|
1981
2323
|
}
|
|
2324
|
+
else if (structuresRekeyed) {
|
|
2325
|
+
this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
|
|
2326
|
+
}
|
|
2327
|
+
else {
|
|
2328
|
+
// The remaining changes (SET DEFAULT, DROP NOT NULL, a SET NOT NULL that needed no
|
|
2329
|
+
// backfill, a retype between aliases of one logical type) touch nothing a layer
|
|
2330
|
+
// DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
|
|
2331
|
+
// comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
|
|
2332
|
+
// above the module, off the catalog schema. But the frozen schema OBJECT is itself
|
|
2333
|
+
// observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
|
|
2334
|
+
// into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
|
|
2335
|
+
// stale, a `rollback to savepoint` taken across one of these ALTERs restores a
|
|
2336
|
+
// snapshot the wrap then skips, and the transaction's staged rows are silently
|
|
2337
|
+
// dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
|
|
2338
|
+
// over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
|
|
2339
|
+
// the schema reference.
|
|
2340
|
+
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
2341
|
+
}
|
|
1982
2342
|
this.eventEmitter?.emitSchemaChange?.({
|
|
1983
2343
|
type: 'alter',
|
|
1984
2344
|
objectType: 'column',
|
|
@@ -2067,12 +2427,15 @@ export class MemoryTableManager {
|
|
|
2067
2427
|
// The DDL transaction's own layers froze their schema at creation; hand them the
|
|
2068
2428
|
// new one so the rest of the transaction scans and enforces the new index.
|
|
2069
2429
|
this.adoptSchemaOnOpenLayers(finalNewTableSchema);
|
|
2070
|
-
// Emit schema change event
|
|
2430
|
+
// Emit schema change event. The `ddl` is what a sync peer re-executes to
|
|
2431
|
+
// replicate the index; rendered against the POST-create schema so it matches
|
|
2432
|
+
// what a receiving peer regenerates when comparing definitions.
|
|
2071
2433
|
this.eventEmitter?.emitSchemaChange?.({
|
|
2072
2434
|
type: 'create',
|
|
2073
2435
|
objectType: 'index',
|
|
2074
2436
|
schemaName: this.schemaName,
|
|
2075
2437
|
objectName: indexName,
|
|
2438
|
+
ddl: generateIndexDDL(newIndexSchemaEntry, finalNewTableSchema),
|
|
2076
2439
|
});
|
|
2077
2440
|
logger.operation('Create Index', this._tableName, { indexName });
|
|
2078
2441
|
}
|
|
@@ -2135,6 +2498,7 @@ export class MemoryTableManager {
|
|
|
2135
2498
|
objectType: 'index',
|
|
2136
2499
|
schemaName: this.schemaName,
|
|
2137
2500
|
objectName: indexName,
|
|
2501
|
+
ddl: generateDropIndexDDL(this.schemaName, indexName),
|
|
2138
2502
|
});
|
|
2139
2503
|
logger.operation('Drop Index', this._tableName, { indexName });
|
|
2140
2504
|
}
|
|
@@ -2285,6 +2649,14 @@ export class MemoryTableManager {
|
|
|
2285
2649
|
this.baseLayer.rebuildAllSecondaryIndexes();
|
|
2286
2650
|
this.tableSchema = newSchema;
|
|
2287
2651
|
this.initializePrimaryKeyFunctions();
|
|
2652
|
+
// Hand the new schema to the DDL transaction's own frozen layers, as `dropConstraint`
|
|
2653
|
+
// does: a renamed covering index has to move under its new key in each layer's index
|
|
2654
|
+
// map (the old key is dropped, the new one rebuilt over the parent's already-renamed
|
|
2655
|
+
// tree — hence oldest-first), and even the pure schema-only classes (CHECK / FK) must
|
|
2656
|
+
// re-freeze the new schema OBJECT, or `commitTransaction`'s snapshot-wrap identity
|
|
2657
|
+
// check (`readLayer.getSchema() === this.tableSchema`) skips a savepoint snapshot
|
|
2658
|
+
// restored across this ALTER and drops the transaction's staged rows at COMMIT.
|
|
2659
|
+
this.adoptSchemaOnOpenLayers(newSchema);
|
|
2288
2660
|
this.eventEmitter?.emitSchemaChange?.({
|
|
2289
2661
|
type: 'alter',
|
|
2290
2662
|
objectType: 'table',
|
|
@@ -2529,6 +2901,19 @@ export class MemoryTableManager {
|
|
|
2529
2901
|
// bug. A detached connection always has `pendingTransactionLayer === null` (disconnect
|
|
2530
2902
|
// defers while a pending layer is uncommitted), so this never discards in-flight writes.
|
|
2531
2903
|
this.repointRegisteredConnections();
|
|
2904
|
+
// Re-pointing `readLayer` is not enough: a savepoint taken while a connection held no
|
|
2905
|
+
// pending layer stored the layer it was READING as the view to restore. Consolidation
|
|
2906
|
+
// has just drained that layer into the base, and the caller is about to reshape the
|
|
2907
|
+
// base's rows — so the stored reference is a pre-change snapshot of the committed rows.
|
|
2908
|
+
// Left alone, the transaction's `rollback to savepoint` reinstates it and the old column
|
|
2909
|
+
// shape commits over the new one.
|
|
2910
|
+
//
|
|
2911
|
+
// Every connection, not just the re-pointed ones: the DDL issuer is exempt from the
|
|
2912
|
+
// sweeps above (its read view holds its own uncommitted rows) but its own lazy markers
|
|
2913
|
+
// are just as stale.
|
|
2914
|
+
for (const connection of this.knownConnections()) {
|
|
2915
|
+
connection.repointLazySavepointsToCommittedHead(this.baseLayer);
|
|
2916
|
+
}
|
|
2532
2917
|
logger.debugLog(`Schema change safety check passed for ${this._tableName}. Current committed layer is base.`);
|
|
2533
2918
|
}
|
|
2534
2919
|
/**
|
|
@@ -2548,6 +2933,34 @@ export class MemoryTableManager {
|
|
|
2548
2933
|
mc.readLayer = this.baseLayer;
|
|
2549
2934
|
}
|
|
2550
2935
|
}
|
|
2936
|
+
/**
|
|
2937
|
+
* Moves this manager's Database-registered connections from `<schema>.<oldName>` to
|
|
2938
|
+
* `<schema>.<newName>`, for `ALTER TABLE ... RENAME TO`.
|
|
2939
|
+
*
|
|
2940
|
+
* `MemoryVirtualTableConnection.tableName` is set once at registration, and
|
|
2941
|
+
* `Database.getConnectionsForTable` matches on exactly that string — so without this
|
|
2942
|
+
* sweep a rename makes the transaction's own connection invisible to
|
|
2943
|
+
* {@link registeredConnections} and everything built on it: {@link ddlConnection},
|
|
2944
|
+
* {@link knownConnections}, {@link repointRegisteredConnections},
|
|
2945
|
+
* {@link openTransactionLayersOldestFirst}. The visible symptom is a second ALTER in
|
|
2946
|
+
* the same transaction failing with BUSY — {@link ensureSchemaChangeSafety} exempts
|
|
2947
|
+
* `ddlConnection()` from its "nobody else may hold open work" sweep, and with the
|
|
2948
|
+
* registry stale that is `undefined`, so the transaction's own connection is judged a
|
|
2949
|
+
* stranger. `MemoryTable.ensureConnection` would likewise stop reusing it and register
|
|
2950
|
+
* a second connection for the same table.
|
|
2951
|
+
*
|
|
2952
|
+
* Must run BEFORE `_tableName` moves — it looks the connections up under the old name.
|
|
2953
|
+
*/
|
|
2954
|
+
rekeyRegisteredConnections(oldName, newName) {
|
|
2955
|
+
const newQualifiedName = `${this.schemaName}.${newName}`;
|
|
2956
|
+
for (const c of this.db.getConnectionsForTable(`${this.schemaName}.${oldName}`)) {
|
|
2957
|
+
if (!(c instanceof MemoryVirtualTableConnection))
|
|
2958
|
+
continue;
|
|
2959
|
+
if (c.getMemoryConnection().tableManager !== this)
|
|
2960
|
+
continue;
|
|
2961
|
+
c.rename(newQualifiedName);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2551
2964
|
/** Every Database-registered {@link MemoryTableConnection} backed by this manager. */
|
|
2552
2965
|
*registeredConnections() {
|
|
2553
2966
|
const qualifiedName = `${this.schemaName}.${this._tableName}`;
|
|
@@ -2625,20 +3038,12 @@ export class MemoryTableManager {
|
|
|
2625
3038
|
convertBaseRows(colIndex, convert, convertNulls = false) {
|
|
2626
3039
|
const out = [];
|
|
2627
3040
|
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
3041
|
try {
|
|
2635
|
-
|
|
3042
|
+
out.push(convertRowAtIndex(row, colIndex, convert, convertNulls));
|
|
2636
3043
|
}
|
|
2637
3044
|
catch {
|
|
2638
3045
|
out.push(row); // shadowed unconvertible value — keep as-is
|
|
2639
|
-
continue;
|
|
2640
3046
|
}
|
|
2641
|
-
out.push(row.map((v, i) => i === colIndex ? newVal : v));
|
|
2642
3047
|
}
|
|
2643
3048
|
return out;
|
|
2644
3049
|
}
|
|
@@ -2652,31 +3057,47 @@ export class MemoryTableManager {
|
|
|
2652
3057
|
* the transaction's pending rows outside this manager, so only it can name the rows the
|
|
2653
3058
|
* issuing connection actually sees; when it supplies them, they are the judged set and this
|
|
2654
3059
|
* manager's own committed rows are ignored. See `vtab/module.ts` {@link EffectiveRowSource}.
|
|
3060
|
+
*
|
|
3061
|
+
* `mapRow` judges the rows in a form they do not yet have on disk: an `alter column` that
|
|
3062
|
+
* REWRITES values passes the per-row conversion here, so the probe sees the post-ALTER
|
|
3063
|
+
* values. Applied to whichever stream is used; the mapping is a read-side wrap and mutates
|
|
3064
|
+
* nothing.
|
|
2655
3065
|
*/
|
|
2656
|
-
async validateUniqueOverEffectiveRows(indexSchema, schema, rows) {
|
|
3066
|
+
async validateUniqueOverEffectiveRows(indexSchema, schema, rows, mapRow) {
|
|
2657
3067
|
const probe = new MemoryIndex(indexSchema, schema.columns, this.collationResolver, this.primaryKeyFunctions.compare, this.primaryKeyFunctions.encode, schema.name);
|
|
2658
3068
|
if (rows) {
|
|
2659
|
-
|
|
3069
|
+
const stream = mapRow ? mapRowsAsync(rows(), mapRow) : rows();
|
|
3070
|
+
await populateIndexFromRowsAsync(stream, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
|
|
2660
3071
|
return;
|
|
2661
3072
|
}
|
|
2662
|
-
|
|
3073
|
+
const own = this.effectiveDdlRows();
|
|
3074
|
+
populateIndexFromRows(mapRow ? mapRows(own, mapRow) : own, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
|
|
2663
3075
|
}
|
|
2664
3076
|
/**
|
|
2665
|
-
* `ALTER COLUMN
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
3077
|
+
* `ALTER COLUMN` arm of {@link validateUniqueOverEffectiveRows}: rejects the change when it
|
|
3078
|
+
* makes two of the DDL transaction's effective rows collide under any uniqueness-enforcing
|
|
3079
|
+
* structure that covers the altered column. Runs before anything is mutated, so a rejection
|
|
3080
|
+
* leaves the table and schema untouched and the transaction usable.
|
|
3081
|
+
*
|
|
3082
|
+
* Serves both families of change that can collapse two distinct values onto one:
|
|
3083
|
+
*
|
|
3084
|
+
* - `SET COLLATE` re-keys the structures under a new comparator; the stored values are
|
|
3085
|
+
* untouched, so no `mapRow` is passed.
|
|
3086
|
+
* - a value REWRITE (`SET DATA TYPE`, or a `SET NOT NULL` null → DEFAULT backfill) leaves
|
|
3087
|
+
* the comparators alone but changes the values; the caller passes a `mapRow` that applies
|
|
3088
|
+
* the same per-row conversion the base rewrite will, so the probe judges the post-ALTER
|
|
3089
|
+
* values.
|
|
2669
3090
|
*
|
|
2670
|
-
* `newSchema` carries the post-change per-column collations, so each probe
|
|
2671
|
-
* exactly as the rebuilt structure will
|
|
2672
|
-
* keys and need no re-check.
|
|
3091
|
+
* `newSchema` carries the post-change per-column collations AND logical types, so each probe
|
|
3092
|
+
* index compares exactly as the rebuilt structure will (it matters for e.g. `text → real`).
|
|
3093
|
+
* Indexes that do not mention the column keep their keys and need no re-check.
|
|
2673
3094
|
*
|
|
2674
3095
|
* NOTE: walks `schema.indexes`, so a UNIQUE constraint covered by a row-time materialized
|
|
2675
3096
|
* view rather than its auto-index (`findIndexForConstraint` prefers the MV) is not re-checked
|
|
2676
3097
|
* here. The auto-index always exists alongside, so the structure is still validated — but if
|
|
2677
3098
|
* an MV-only covering shape ever becomes reachable, this walk must follow it.
|
|
2678
3099
|
*/
|
|
2679
|
-
async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows) {
|
|
3100
|
+
async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows, mapRow) {
|
|
2680
3101
|
// NOTE: the probe index carries the manager's PRE-change `primaryKeyFunctions` (the new
|
|
2681
3102
|
// ones cannot exist before the schema swaps). Only the probe's per-entry PK bookkeeping
|
|
2682
3103
|
// uses them, and every effective row already has a distinct PK under the old encoder, so
|
|
@@ -2688,7 +3109,7 @@ export class MemoryTableManager {
|
|
|
2688
3109
|
if (!indexEnforcesUnique(newSchema, indexSchema))
|
|
2689
3110
|
continue;
|
|
2690
3111
|
// `rows` is re-callable, so each structure gets a fresh stream.
|
|
2691
|
-
await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows);
|
|
3112
|
+
await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows, mapRow);
|
|
2692
3113
|
}
|
|
2693
3114
|
}
|
|
2694
3115
|
/**
|
|
@@ -2792,7 +3213,7 @@ export class MemoryTableManager {
|
|
|
2792
3213
|
* `convertNulls` routes null own-values through `convert` (the SET NOT NULL null → DEFAULT map);
|
|
2793
3214
|
* SET DATA TYPE leaves them untouched. No-op in autocommit.
|
|
2794
3215
|
*
|
|
2795
|
-
* PRIMARY-KEY columns never reach here —
|
|
3216
|
+
* PRIMARY-KEY columns never reach here — any retype of a key column is rejected before
|
|
2796
3217
|
* any mutation, and SET NOT NULL leaves the key bytes unchanged — so the primary tree's keys are
|
|
2797
3218
|
* stable and no re-key is needed.
|
|
2798
3219
|
*/
|
|
@@ -2801,14 +3222,58 @@ export class MemoryTableManager {
|
|
|
2801
3222
|
layer.convertColumn(colIndex, convert, newSchema, convertNulls);
|
|
2802
3223
|
}
|
|
2803
3224
|
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Phase 1 of propagating `add column` / `drop column` into every open transaction layer:
|
|
3227
|
+
* computes each layer's net own-writes rewritten to the new column set, WITHOUT mutating
|
|
3228
|
+
* anything (see {@link TransactionLayer.prepareReshapedColumns}). Runs BEFORE the base is
|
|
3229
|
+
* touched, because `reshapeRow` can throw — ADD COLUMN's per-row backfill evaluates an
|
|
3230
|
+
* engine-supplied `default (new.<col>)` expression against each pending row, and enforces
|
|
3231
|
+
* NOT NULL on the result — and a failure part-way through a mutation pass would leave the
|
|
3232
|
+
* base and the already-visited layers at the new arity with the rest at the old, with no
|
|
3233
|
+
* undo. Failing here instead rejects the ALTER with every structure untouched, exactly like
|
|
3234
|
+
* the pre-mutation validation passes of `alterColumn`.
|
|
3235
|
+
*
|
|
3236
|
+
* The returned plans pin the layer set: {@link installReshapeOnOpenLayers} walks the plans,
|
|
3237
|
+
* not a fresh chain snapshot, so the two phases cannot disagree about which layers exist.
|
|
3238
|
+
* Reading each layer's effective rows here — before the base rebuild — yields the same rows
|
|
3239
|
+
* the single-pass prototype read after it: a layer's pre-reshape tree resolves against the
|
|
3240
|
+
* parent's OLD tree either way (the base rebuild REPLACES the base's tree object; it never
|
|
3241
|
+
* mutates the one the layers inherit from).
|
|
3242
|
+
*/
|
|
3243
|
+
async prepareReshapeOnOpenLayers(reshapeRow, reshapeEventRow) {
|
|
3244
|
+
const plans = [];
|
|
3245
|
+
for (const layer of this.openTransactionLayersOldestFirst()) {
|
|
3246
|
+
plans.push({ layer, prepared: await layer.prepareReshapedColumns(reshapeRow, reshapeEventRow) });
|
|
3247
|
+
}
|
|
3248
|
+
return plans;
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Phase 2: installs the prepared reshape into each open transaction layer, oldest-first
|
|
3252
|
+
* (the plans were gathered in that order), after the base has been rebuilt at the new arity
|
|
3253
|
+
* — each layer's rebuilt tree inherits copy-on-write from its parent's NEW one. Synchronous
|
|
3254
|
+
* and mutation-only; everything fallible ran in {@link prepareReshapeOnOpenLayers}.
|
|
3255
|
+
*
|
|
3256
|
+
* Beyond keeping the transaction's own reads correct, this is what makes the
|
|
3257
|
+
* `commitTransaction` snapshot-wrap identity check (`readLayer.getSchema() ===
|
|
3258
|
+
* this.tableSchema`) pass after a `rollback to savepoint`: without it the eager savepoint
|
|
3259
|
+
* snapshot keeps its frozen pre-ALTER schema, the wrap is skipped, and the snapshot's rows
|
|
3260
|
+
* are silently dropped at commit.
|
|
3261
|
+
*/
|
|
3262
|
+
installReshapeOnOpenLayers(newSchema, plans) {
|
|
3263
|
+
for (const { layer, prepared } of plans) {
|
|
3264
|
+
layer.installReshapedColumns(newSchema, prepared);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
2804
3267
|
/**
|
|
2805
3268
|
* The DDL connection's open {@link TransactionLayer}s, oldest-first, base excluded — its
|
|
2806
3269
|
* pending layer and every savepoint snapshot below it, the layers on the view's parent chain
|
|
2807
3270
|
* above the base. Empty in autocommit (no connection, or the view IS the base).
|
|
2808
3271
|
*
|
|
2809
|
-
*
|
|
2810
|
-
* ({@link convertColumnOnOpenLayers})
|
|
2811
|
-
*
|
|
3272
|
+
* Schema adoption ({@link adoptSchemaOnOpenLayers}), value conversion
|
|
3273
|
+
* ({@link convertColumnOnOpenLayers}), and column-set reshape
|
|
3274
|
+
* ({@link prepareReshapeOnOpenLayers} / {@link installReshapeOnOpenLayers}) MUST all apply
|
|
3275
|
+
* oldest-first: each layer rebuilds its structures over its parent's, so the parent's must
|
|
3276
|
+
* already be the new/converted/reshaped ones.
|
|
2812
3277
|
*
|
|
2813
3278
|
* NOTE: the walk takes every TransactionLayer below the view, which normally means the pending
|
|
2814
3279
|
* layer and its savepoint snapshots. A committed layer already drained into the base by
|
|
@@ -2841,6 +3306,17 @@ export class MemoryTableManager {
|
|
|
2841
3306
|
logger.debugLog(`[Consolidate] Copying data from transaction layer ${transactionLayer.getLayerId()} to base layer for ${this._tableName}`);
|
|
2842
3307
|
// Copy all data from the transaction layer to the base layer
|
|
2843
3308
|
await this.copyTransactionDataToBase(transactionLayer);
|
|
3309
|
+
// The drained chain's pending-change event logs were already delivered when
|
|
3310
|
+
// each layer committed, but the layers can remain in the DDL connection's own
|
|
3311
|
+
// open-transaction parent chain. Once the base becomes the committed head, the
|
|
3312
|
+
// commit-time collection (`collectPendingChanges`) walks that chain down to
|
|
3313
|
+
// the base and would re-deliver them — clear the logs so consolidation is
|
|
3314
|
+
// event-neutral. (Savepoint snapshots of the OPEN transaction sit above the
|
|
3315
|
+
// committed head, not in this chain, so their un-emitted events are kept.)
|
|
3316
|
+
for (let cur = transactionLayer; cur && cur !== this.baseLayer; cur = cur.getParent()) {
|
|
3317
|
+
if (cur instanceof TransactionLayer)
|
|
3318
|
+
cur.clearPendingChanges();
|
|
3319
|
+
}
|
|
2844
3320
|
// Force all connections to read from the base layer
|
|
2845
3321
|
for (const conn of this.connections.values()) {
|
|
2846
3322
|
if (conn.readLayer === transactionLayer) {
|