@quereus/quereus 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/src/common/datatype.d.ts +12 -0
- package/dist/src/common/datatype.d.ts.map +1 -1
- package/dist/src/common/datatype.js.map +1 -1
- package/dist/src/common/types.d.ts +24 -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-assertions.d.ts +37 -9
- package/dist/src/core/database-assertions.d.ts.map +1 -1
- package/dist/src/core/database-assertions.js +62 -110
- package/dist/src/core/database-assertions.js.map +1 -1
- package/dist/src/core/database-events.d.ts +163 -0
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +235 -21
- package/dist/src/core/database-events.js.map +1 -1
- package/dist/src/core/database-external-changes.d.ts +28 -0
- package/dist/src/core/database-external-changes.d.ts.map +1 -0
- package/dist/src/core/database-external-changes.js +242 -0
- package/dist/src/core/database-external-changes.js.map +1 -0
- package/dist/src/core/database-internal.d.ts +50 -1
- package/dist/src/core/database-internal.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +1253 -0
- package/dist/src/core/database-materialized-views.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views.js +3064 -0
- package/dist/src/core/database-materialized-views.js.map +1 -0
- package/dist/src/core/database-options.d.ts +4 -0
- package/dist/src/core/database-options.d.ts.map +1 -1
- package/dist/src/core/database-options.js +10 -0
- package/dist/src/core/database-options.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts +19 -3
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +30 -3
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database-watchers.d.ts +19 -0
- package/dist/src/core/database-watchers.d.ts.map +1 -1
- package/dist/src/core/database-watchers.js +63 -3
- package/dist/src/core/database-watchers.js.map +1 -1
- package/dist/src/core/database.d.ts +203 -11
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +493 -29
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/derived-row-validator.d.ts +137 -0
- package/dist/src/core/derived-row-validator.d.ts.map +1 -0
- package/dist/src/core/derived-row-validator.js +314 -0
- package/dist/src/core/derived-row-validator.js.map +1 -0
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +30 -9
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/emit/ast-stringify.d.ts +135 -1
- package/dist/src/emit/ast-stringify.d.ts.map +1 -1
- package/dist/src/emit/ast-stringify.js +793 -118
- package/dist/src/emit/ast-stringify.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +11 -10
- 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 +32 -0
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts +3 -0
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +229 -0
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +10 -2
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/func/builtins/json.d.ts.map +1 -1
- package/dist/src/func/builtins/json.js +3 -2
- package/dist/src/func/builtins/json.js.map +1 -1
- package/dist/src/func/builtins/mutation.d.ts +2 -0
- package/dist/src/func/builtins/mutation.d.ts.map +1 -0
- package/dist/src/func/builtins/mutation.js +53 -0
- package/dist/src/func/builtins/mutation.js.map +1 -0
- package/dist/src/func/builtins/schema.d.ts +2 -0
- package/dist/src/func/builtins/schema.d.ts.map +1 -1
- package/dist/src/func/builtins/schema.js +713 -26
- package/dist/src/func/builtins/schema.js.map +1 -1
- package/dist/src/func/builtins/string.js +1 -1
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/registration.d.ts +9 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +4 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +25 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +27 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/ast.d.ts +353 -21
- package/dist/src/parser/ast.d.ts.map +1 -1
- package/dist/src/parser/index.d.ts +14 -1
- package/dist/src/parser/index.d.ts.map +1 -1
- package/dist/src/parser/index.js +19 -0
- package/dist/src/parser/index.js.map +1 -1
- package/dist/src/parser/lexer.d.ts +9 -0
- package/dist/src/parser/lexer.d.ts.map +1 -1
- package/dist/src/parser/lexer.js +9 -0
- package/dist/src/parser/lexer.js.map +1 -1
- package/dist/src/parser/parser.d.ts +276 -7
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +1387 -469
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/parser/visitor.d.ts.map +1 -1
- package/dist/src/parser/visitor.js +12 -8
- package/dist/src/parser/visitor.js.map +1 -1
- package/dist/src/planner/analysis/assertion-classifier.d.ts.map +1 -1
- package/dist/src/planner/analysis/assertion-classifier.js +4 -0
- package/dist/src/planner/analysis/assertion-classifier.js.map +1 -1
- package/dist/src/planner/analysis/assertion-hoist-cache.d.ts.map +1 -1
- package/dist/src/planner/analysis/assertion-hoist-cache.js +8 -4
- package/dist/src/planner/analysis/assertion-hoist-cache.js.map +1 -1
- package/dist/src/planner/analysis/authored-inverse.d.ts +22 -0
- package/dist/src/planner/analysis/authored-inverse.d.ts.map +1 -0
- package/dist/src/planner/analysis/authored-inverse.js +267 -0
- package/dist/src/planner/analysis/authored-inverse.js.map +1 -0
- package/dist/src/planner/analysis/change-scope.d.ts +34 -4
- package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
- package/dist/src/planner/analysis/change-scope.js +108 -7
- package/dist/src/planner/analysis/change-scope.js.map +1 -1
- package/dist/src/planner/analysis/check-extraction.d.ts +36 -2
- package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/check-extraction.js +174 -46
- package/dist/src/planner/analysis/check-extraction.js.map +1 -1
- package/dist/src/planner/analysis/coarsened-key.d.ts +109 -0
- package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -0
- package/dist/src/planner/analysis/coarsened-key.js +228 -0
- package/dist/src/planner/analysis/coarsened-key.js.map +1 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts +216 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -0
- package/dist/src/planner/analysis/comparison-collation.js +341 -0
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -0
- package/dist/src/planner/analysis/constraint-extractor.d.ts +3 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +192 -9
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/coverage-prover.d.ts +321 -0
- package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -0
- package/dist/src/planner/analysis/coverage-prover.js +1038 -0
- package/dist/src/planner/analysis/coverage-prover.js.map +1 -0
- package/dist/src/planner/analysis/key-filter.d.ts +22 -0
- package/dist/src/planner/analysis/key-filter.d.ts.map +1 -0
- package/dist/src/planner/analysis/key-filter.js +105 -0
- package/dist/src/planner/analysis/key-filter.js.map +1 -0
- package/dist/src/planner/analysis/partial-unique-extraction.d.ts +36 -1
- package/dist/src/planner/analysis/partial-unique-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/partial-unique-extraction.js +148 -22
- package/dist/src/planner/analysis/partial-unique-extraction.js.map +1 -1
- package/dist/src/planner/analysis/predicate-normalizer.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-normalizer.js +30 -1
- package/dist/src/planner/analysis/predicate-normalizer.js.map +1 -1
- package/dist/src/planner/analysis/predicate-shape.d.ts +36 -1
- package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-shape.js +51 -13
- package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +314 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.js +1081 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -0
- package/dist/src/planner/analysis/scalar-invertibility.d.ts +92 -0
- package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -0
- package/dist/src/planner/analysis/scalar-invertibility.js +129 -0
- package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -0
- package/dist/src/planner/analysis/update-lineage.d.ts +196 -0
- package/dist/src/planner/analysis/update-lineage.d.ts.map +1 -0
- package/dist/src/planner/analysis/update-lineage.js +322 -0
- package/dist/src/planner/analysis/update-lineage.js.map +1 -0
- package/dist/src/planner/analysis/view-complement.d.ts +42 -0
- package/dist/src/planner/analysis/view-complement.d.ts.map +1 -0
- package/dist/src/planner/analysis/view-complement.js +54 -0
- package/dist/src/planner/analysis/view-complement.js.map +1 -0
- package/dist/src/planner/building/alter-table.d.ts +1 -1
- package/dist/src/planner/building/alter-table.d.ts.map +1 -1
- package/dist/src/planner/building/alter-table.js +211 -2
- package/dist/src/planner/building/alter-table.js.map +1 -1
- package/dist/src/planner/building/block.d.ts.map +1 -1
- package/dist/src/planner/building/block.js +18 -1
- package/dist/src/planner/building/block.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.d.ts +33 -5
- package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +63 -28
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/create-view.d.ts +9 -0
- package/dist/src/planner/building/create-view.d.ts.map +1 -1
- package/dist/src/planner/building/create-view.js +41 -12
- package/dist/src/planner/building/create-view.js.map +1 -1
- package/dist/src/planner/building/ddl.d.ts.map +1 -1
- package/dist/src/planner/building/ddl.js +94 -0
- package/dist/src/planner/building/ddl.js.map +1 -1
- package/dist/src/planner/building/declare-schema.d.ts +1 -0
- package/dist/src/planner/building/declare-schema.d.ts.map +1 -1
- package/dist/src/planner/building/declare-schema.js +4 -1
- package/dist/src/planner/building/declare-schema.js.map +1 -1
- package/dist/src/planner/building/default-scope.d.ts +26 -0
- package/dist/src/planner/building/default-scope.d.ts.map +1 -0
- package/dist/src/planner/building/default-scope.js +41 -0
- package/dist/src/planner/building/default-scope.js.map +1 -0
- package/dist/src/planner/building/delete.d.ts +19 -1
- package/dist/src/planner/building/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +109 -30
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/dml-target.d.ts +118 -0
- package/dist/src/planner/building/dml-target.d.ts.map +1 -0
- package/dist/src/planner/building/dml-target.js +282 -0
- package/dist/src/planner/building/dml-target.js.map +1 -0
- package/dist/src/planner/building/drop-index.d.ts.map +1 -1
- package/dist/src/planner/building/drop-index.js +4 -1
- package/dist/src/planner/building/drop-index.js.map +1 -1
- package/dist/src/planner/building/drop-view.d.ts.map +1 -1
- package/dist/src/planner/building/drop-view.js +4 -2
- package/dist/src/planner/building/drop-view.js.map +1 -1
- package/dist/src/planner/building/expression.d.ts.map +1 -1
- package/dist/src/planner/building/expression.js +60 -21
- package/dist/src/planner/building/expression.js.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts +30 -0
- package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.js +160 -129
- package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts +45 -2
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +257 -88
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/lens-auxiliary-access.d.ts +22 -0
- package/dist/src/planner/building/lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/planner/building/lens-auxiliary-access.js +132 -0
- package/dist/src/planner/building/lens-auxiliary-access.js.map +1 -0
- package/dist/src/planner/building/materialized-view.d.ts +16 -0
- package/dist/src/planner/building/materialized-view.d.ts.map +1 -0
- package/dist/src/planner/building/materialized-view.js +57 -0
- package/dist/src/planner/building/materialized-view.js.map +1 -0
- package/dist/src/planner/building/returning-star.d.ts +32 -0
- package/dist/src/planner/building/returning-star.d.ts.map +1 -0
- package/dist/src/planner/building/returning-star.js +45 -0
- package/dist/src/planner/building/returning-star.js.map +1 -0
- package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
- package/dist/src/planner/building/select-aggregates.js +47 -0
- package/dist/src/planner/building/select-aggregates.js.map +1 -1
- package/dist/src/planner/building/select-compound.d.ts.map +1 -1
- package/dist/src/planner/building/select-compound.js +84 -11
- package/dist/src/planner/building/select-compound.js.map +1 -1
- package/dist/src/planner/building/select-context.d.ts +10 -2
- package/dist/src/planner/building/select-context.d.ts.map +1 -1
- package/dist/src/planner/building/select-context.js +7 -1
- package/dist/src/planner/building/select-context.js.map +1 -1
- package/dist/src/planner/building/select-modifiers.js +6 -0
- package/dist/src/planner/building/select-modifiers.js.map +1 -1
- package/dist/src/planner/building/select-ordinal.d.ts +18 -0
- package/dist/src/planner/building/select-ordinal.d.ts.map +1 -1
- package/dist/src/planner/building/select-ordinal.js +30 -0
- package/dist/src/planner/building/select-ordinal.js.map +1 -1
- package/dist/src/planner/building/select-projections.d.ts +8 -2
- package/dist/src/planner/building/select-projections.d.ts.map +1 -1
- package/dist/src/planner/building/select-projections.js +26 -4
- package/dist/src/planner/building/select-projections.js.map +1 -1
- package/dist/src/planner/building/select-window.d.ts.map +1 -1
- package/dist/src/planner/building/select-window.js +8 -5
- package/dist/src/planner/building/select-window.js.map +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +164 -59
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/set-object-tags.d.ts +7 -0
- package/dist/src/planner/building/set-object-tags.d.ts.map +1 -0
- package/dist/src/planner/building/set-object-tags.js +38 -0
- package/dist/src/planner/building/set-object-tags.js.map +1 -0
- package/dist/src/planner/building/tag-diagnostics.d.ts +27 -0
- package/dist/src/planner/building/tag-diagnostics.d.ts.map +1 -0
- package/dist/src/planner/building/tag-diagnostics.js +37 -0
- package/dist/src/planner/building/tag-diagnostics.js.map +1 -0
- package/dist/src/planner/building/update.d.ts +18 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +134 -58
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/building/view-mutation-builder.d.ts +15 -0
- package/dist/src/planner/building/view-mutation-builder.d.ts.map +1 -0
- package/dist/src/planner/building/view-mutation-builder.js +1158 -0
- package/dist/src/planner/building/view-mutation-builder.js.map +1 -0
- package/dist/src/planner/building/with.d.ts +11 -0
- package/dist/src/planner/building/with.d.ts.map +1 -1
- package/dist/src/planner/building/with.js +48 -10
- package/dist/src/planner/building/with.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +83 -0
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +114 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/framework/characteristics.d.ts +38 -4
- package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
- package/dist/src/planner/framework/characteristics.js +50 -6
- package/dist/src/planner/framework/characteristics.js.map +1 -1
- package/dist/src/planner/framework/pass.d.ts.map +1 -1
- package/dist/src/planner/framework/pass.js +2 -1
- package/dist/src/planner/framework/pass.js.map +1 -1
- package/dist/src/planner/framework/registry.d.ts +39 -1
- package/dist/src/planner/framework/registry.d.ts.map +1 -1
- package/dist/src/planner/framework/registry.js +18 -2
- package/dist/src/planner/framework/registry.js.map +1 -1
- package/dist/src/planner/mutation/backward-body.d.ts +131 -0
- package/dist/src/planner/mutation/backward-body.d.ts.map +1 -0
- package/dist/src/planner/mutation/backward-body.js +135 -0
- package/dist/src/planner/mutation/backward-body.js.map +1 -0
- package/dist/src/planner/mutation/cte-flatten.d.ts +17 -0
- package/dist/src/planner/mutation/cte-flatten.d.ts.map +1 -0
- package/dist/src/planner/mutation/cte-flatten.js +364 -0
- package/dist/src/planner/mutation/cte-flatten.js.map +1 -0
- package/dist/src/planner/mutation/decomposition.d.ts +273 -0
- package/dist/src/planner/mutation/decomposition.d.ts.map +1 -0
- package/dist/src/planner/mutation/decomposition.js +1719 -0
- package/dist/src/planner/mutation/decomposition.js.map +1 -0
- package/dist/src/planner/mutation/lens-enforcement.d.ts +165 -0
- package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -0
- package/dist/src/planner/mutation/lens-enforcement.js +745 -0
- package/dist/src/planner/mutation/lens-enforcement.js.map +1 -0
- package/dist/src/planner/mutation/multi-source.d.ts +568 -0
- package/dist/src/planner/mutation/multi-source.d.ts.map +1 -0
- package/dist/src/planner/mutation/multi-source.js +2915 -0
- package/dist/src/planner/mutation/multi-source.js.map +1 -0
- package/dist/src/planner/mutation/mutation-diagnostic.d.ts +37 -0
- package/dist/src/planner/mutation/mutation-diagnostic.d.ts.map +1 -0
- package/dist/src/planner/mutation/mutation-diagnostic.js +24 -0
- package/dist/src/planner/mutation/mutation-diagnostic.js.map +1 -0
- package/dist/src/planner/mutation/mutation-tags.d.ts +33 -0
- package/dist/src/planner/mutation/mutation-tags.d.ts.map +1 -0
- package/dist/src/planner/mutation/mutation-tags.js +31 -0
- package/dist/src/planner/mutation/mutation-tags.js.map +1 -0
- package/dist/src/planner/mutation/propagate.d.ts +97 -0
- package/dist/src/planner/mutation/propagate.d.ts.map +1 -0
- package/dist/src/planner/mutation/propagate.js +220 -0
- package/dist/src/planner/mutation/propagate.js.map +1 -0
- package/dist/src/planner/mutation/scope-transform.d.ts +181 -0
- package/dist/src/planner/mutation/scope-transform.d.ts.map +1 -0
- package/dist/src/planner/mutation/scope-transform.js +574 -0
- package/dist/src/planner/mutation/scope-transform.js.map +1 -0
- package/dist/src/planner/mutation/set-op.d.ts +242 -0
- package/dist/src/planner/mutation/set-op.d.ts.map +1 -0
- package/dist/src/planner/mutation/set-op.js +1687 -0
- package/dist/src/planner/mutation/set-op.js.map +1 -0
- package/dist/src/planner/mutation/single-source.d.ts +261 -0
- package/dist/src/planner/mutation/single-source.d.ts.map +1 -0
- package/dist/src/planner/mutation/single-source.js +1096 -0
- package/dist/src/planner/mutation/single-source.js.map +1 -0
- package/dist/src/planner/nodes/aggregate-node.js +3 -3
- package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
- package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alias-node.js +5 -1
- package/dist/src/planner/nodes/alias-node.js.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.d.ts +124 -1
- package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alter-table-node.js +27 -0
- package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
- package/dist/src/planner/nodes/analyze-node.d.ts +2 -1
- package/dist/src/planner/nodes/analyze-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/analyze-node.js +18 -1
- package/dist/src/planner/nodes/analyze-node.js.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.d.ts +43 -0
- package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/asserted-keys-node.js +99 -0
- package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -0
- package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.js +33 -8
- package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.js +2 -1
- package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
- package/dist/src/planner/nodes/create-view-node.d.ts +7 -2
- package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/create-view-node.js +4 -1
- package/dist/src/planner/nodes/create-view-node.js.map +1 -1
- package/dist/src/planner/nodes/declarative-schema.d.ts +13 -1
- package/dist/src/planner/nodes/declarative-schema.d.ts.map +1 -1
- package/dist/src/planner/nodes/declarative-schema.js +32 -0
- package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
- package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/distinct-node.js +2 -0
- package/dist/src/planner/nodes/distinct-node.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +29 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js +27 -3
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/eager-prefetch-node.js +2 -0
- package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
- package/dist/src/planner/nodes/envelope-scan-node.d.ts +42 -0
- package/dist/src/planner/nodes/envelope-scan-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/envelope-scan-node.js +62 -0
- package/dist/src/planner/nodes/envelope-scan-node.js.map +1 -0
- package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js +11 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +63 -13
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts +41 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +78 -8
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/join-utils.d.ts +33 -6
- package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-utils.js +124 -9
- package/dist/src/planner/nodes/join-utils.js.map +1 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts +104 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js +91 -0
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -0
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +4 -5
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/materialized-view-nodes.d.ts +69 -0
- package/dist/src/planner/nodes/materialized-view-nodes.d.ts.map +1 -0
- package/dist/src/planner/nodes/materialized-view-nodes.js +111 -0
- package/dist/src/planner/nodes/materialized-view-nodes.js.map +1 -0
- package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.js +2 -1
- package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.js +2 -0
- package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.d.ts +9 -0
- package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.js +9 -0
- package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +265 -5
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/pragma.d.ts +2 -1
- package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
- package/dist/src/planner/nodes/pragma.js +12 -0
- package/dist/src/planner/nodes/pragma.js.map +1 -1
- package/dist/src/planner/nodes/project-node.d.ts +14 -1
- package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/project-node.js +85 -11
- package/dist/src/planner/nodes/project-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +62 -27
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.js +7 -0
- package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
- package/dist/src/planner/nodes/returning-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/returning-node.js +10 -3
- package/dist/src/planner/nodes/returning-node.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts +20 -0
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +71 -14
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/set-object-tags-node.d.ts +39 -0
- package/dist/src/planner/nodes/set-object-tags-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/set-object-tags-node.js +41 -0
- package/dist/src/planner/nodes/set-object-tags-node.js.map +1 -0
- package/dist/src/planner/nodes/set-operation-node.d.ts +123 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +291 -18
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/planner/nodes/single-row.d.ts.map +1 -1
- package/dist/src/planner/nodes/single-row.js +3 -0
- package/dist/src/planner/nodes/single-row.js.map +1 -1
- package/dist/src/planner/nodes/sort.d.ts.map +1 -1
- package/dist/src/planner/nodes/sort.js +7 -6
- package/dist/src/planner/nodes/sort.js.map +1 -1
- package/dist/src/planner/nodes/subquery.d.ts +2 -0
- package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
- package/dist/src/planner/nodes/subquery.js +18 -2
- package/dist/src/planner/nodes/subquery.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +23 -3
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/nodes/table-function-call.js +6 -0
- package/dist/src/planner/nodes/table-function-call.js.map +1 -1
- package/dist/src/planner/nodes/values-node.d.ts +1 -0
- package/dist/src/planner/nodes/values-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/values-node.js +16 -6
- package/dist/src/planner/nodes/values-node.js.map +1 -1
- package/dist/src/planner/nodes/view-mutation-node.d.ts +259 -0
- package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/view-mutation-node.js +273 -0
- package/dist/src/planner/nodes/view-mutation-node.js.map +1 -0
- package/dist/src/planner/nodes/window-function.d.ts +17 -1
- package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-function.js +15 -1
- package/dist/src/planner/nodes/window-function.js.map +1 -1
- package/dist/src/planner/nodes/window-node.js +2 -2
- package/dist/src/planner/nodes/window-node.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +372 -39
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/planning-context.d.ts +1 -1
- package/dist/src/planner/planning-context.d.ts.map +1 -1
- package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts +70 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts.map +1 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.js +156 -0
- package/dist/src/planner/rules/access/lens-access-form-matcher.js.map +1 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts +31 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js +176 -0
- package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js.map +1 -0
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +435 -37
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +9 -0
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +39 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +616 -0
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js +8 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +36 -0
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.js +38 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +10 -0
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
- 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 +19 -1
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +130 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +206 -0
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js.map +1 -0
- package/dist/src/planner/rules/join/rule-join-elimination.d.ts +67 -14
- package/dist/src/planner/rules/join/rule-join-elimination.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-elimination.js +81 -25
- package/dist/src/planner/rules/join/rule-join-elimination.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts +84 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.js +138 -0
- package/dist/src/planner/rules/join/rule-join-existence-pruning.js.map +1 -0
- package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js +9 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js +12 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +4 -0
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +4 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +10 -0
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts +286 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js +548 -0
- package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js.map +1 -0
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js +9 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +7 -0
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.d.ts.map +1 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js +10 -1
- package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js +9 -0
- package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +18 -0
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +7 -0
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +9 -0
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -3
- package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.js +14 -0
- package/dist/src/planner/rules/retrieve/rule-projection-pruning.js.map +1 -1
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +1 -1
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +4 -4
- package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +8 -0
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +7 -0
- package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +12 -0
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/planner/type-utils.d.ts +14 -0
- package/dist/src/planner/type-utils.d.ts.map +1 -1
- package/dist/src/planner/type-utils.js +66 -21
- package/dist/src/planner/type-utils.js.map +1 -1
- package/dist/src/planner/util/fd-utils.d.ts +177 -43
- package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
- package/dist/src/planner/util/fd-utils.js +396 -101
- package/dist/src/planner/util/fd-utils.js.map +1 -1
- package/dist/src/planner/util/ind-utils.d.ts +27 -1
- package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
- package/dist/src/planner/util/ind-utils.js +80 -6
- package/dist/src/planner/util/ind-utils.js.map +1 -1
- package/dist/src/planner/util/key-utils.d.ts.map +1 -1
- package/dist/src/planner/util/key-utils.js +81 -12
- package/dist/src/planner/util/key-utils.js.map +1 -1
- package/dist/src/planner/util/set-op-wrapper.d.ts +37 -0
- package/dist/src/planner/util/set-op-wrapper.d.ts.map +1 -0
- package/dist/src/planner/util/set-op-wrapper.js +82 -0
- package/dist/src/planner/util/set-op-wrapper.js.map +1 -0
- package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
- package/dist/src/planner/validation/plan-validator.js +1 -0
- package/dist/src/planner/validation/plan-validator.js.map +1 -1
- package/dist/src/runtime/context-helpers.d.ts +13 -1
- package/dist/src/runtime/context-helpers.d.ts.map +1 -1
- package/dist/src/runtime/context-helpers.js +7 -1
- package/dist/src/runtime/context-helpers.js.map +1 -1
- package/dist/src/runtime/delta-executor.d.ts +30 -1
- package/dist/src/runtime/delta-executor.d.ts.map +1 -1
- package/dist/src/runtime/delta-executor.js +29 -4
- package/dist/src/runtime/delta-executor.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +38 -5
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +10 -8
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +664 -108
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
- package/dist/src/runtime/emit/analyze.js +2 -1
- package/dist/src/runtime/emit/analyze.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +18 -5
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/asserted-keys.d.ts +13 -0
- package/dist/src/runtime/emit/asserted-keys.d.ts.map +1 -0
- package/dist/src/runtime/emit/asserted-keys.js +13 -0
- package/dist/src/runtime/emit/asserted-keys.js.map +1 -0
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +24 -19
- 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 +5 -9
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/block.d.ts.map +1 -1
- package/dist/src/runtime/emit/block.js +11 -2
- package/dist/src/runtime/emit/block.js.map +1 -1
- package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/bloom-join.js +8 -2
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +15 -0
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-table.js +8 -0
- package/dist/src/runtime/emit/create-table.js.map +1 -1
- package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-view.js +16 -1
- package/dist/src/runtime/emit/create-view.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts +27 -0
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +413 -193
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-table.js +10 -0
- package/dist/src/runtime/emit/drop-table.js.map +1 -1
- package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-view.js +17 -0
- package/dist/src/runtime/emit/drop-view.js.map +1 -1
- package/dist/src/runtime/emit/envelope-scan.d.ts +13 -0
- package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -0
- package/dist/src/runtime/emit/envelope-scan.js +22 -0
- package/dist/src/runtime/emit/envelope-scan.js.map +1 -0
- package/dist/src/runtime/emit/join.d.ts +10 -2
- package/dist/src/runtime/emit/join.d.ts.map +1 -1
- package/dist/src/runtime/emit/join.js +128 -38
- package/dist/src/runtime/emit/join.js.map +1 -1
- package/dist/src/runtime/emit/lens-auxiliary-access.d.ts +16 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.d.ts.map +1 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.js +16 -0
- package/dist/src/runtime/emit/lens-auxiliary-access.js.map +1 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +640 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -0
- package/dist/src/runtime/emit/materialized-view-helpers.js +2576 -0
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -0
- package/dist/src/runtime/emit/materialized-view.d.ts +31 -0
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -0
- package/dist/src/runtime/emit/materialized-view.js +187 -0
- package/dist/src/runtime/emit/materialized-view.js.map +1 -0
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +15 -3
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/project.d.ts.map +1 -1
- package/dist/src/runtime/emit/project.js +10 -5
- package/dist/src/runtime/emit/project.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts +1 -0
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +101 -5
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.d.ts +16 -0
- package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -0
- package/dist/src/runtime/emit/set-object-tags.js +57 -0
- package/dist/src/runtime/emit/set-object-tags.js.map +1 -0
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +140 -24
- package/dist/src/runtime/emit/set-operation.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +110 -5
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +34 -6
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/runtime/emit/view-mutation.d.ts +70 -0
- package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -0
- package/dist/src/runtime/emit/view-mutation.js +299 -0
- package/dist/src/runtime/emit/view-mutation.js.map +1 -0
- package/dist/src/runtime/emit/window.js +29 -5
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.d.ts +66 -3
- package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +580 -172
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +4 -1
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +5 -1
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/register.d.ts.map +1 -1
- package/dist/src/runtime/register.js +17 -1
- package/dist/src/runtime/register.js.map +1 -1
- package/dist/src/runtime/types.d.ts +10 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/basis-backfill.d.ts +63 -0
- package/dist/src/schema/basis-backfill.d.ts.map +1 -0
- package/dist/src/schema/basis-backfill.js +161 -0
- package/dist/src/schema/basis-backfill.js.map +1 -0
- package/dist/src/schema/catalog.d.ts +115 -1
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +249 -22
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/change-events.d.ts +42 -1
- package/dist/src/schema/change-events.d.ts.map +1 -1
- package/dist/src/schema/change-events.js.map +1 -1
- package/dist/src/schema/column.d.ts +16 -0
- package/dist/src/schema/column.d.ts.map +1 -1
- package/dist/src/schema/column.js.map +1 -1
- package/dist/src/schema/constraint-builder.d.ts +182 -0
- package/dist/src/schema/constraint-builder.d.ts.map +1 -0
- package/dist/src/schema/constraint-builder.js +424 -0
- package/dist/src/schema/constraint-builder.js.map +1 -0
- package/dist/src/schema/ddl-generator.d.ts +86 -1
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +316 -20
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/declared-schema-manager.d.ts +51 -0
- package/dist/src/schema/declared-schema-manager.d.ts.map +1 -1
- package/dist/src/schema/declared-schema-manager.js +61 -0
- package/dist/src/schema/declared-schema-manager.js.map +1 -1
- package/dist/src/schema/derivation.d.ts +106 -0
- package/dist/src/schema/derivation.d.ts.map +1 -0
- package/dist/src/schema/derivation.js +25 -0
- package/dist/src/schema/derivation.js.map +1 -0
- package/dist/src/schema/function.d.ts +13 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/lens-ack.d.ts +90 -0
- package/dist/src/schema/lens-ack.d.ts.map +1 -0
- package/dist/src/schema/lens-ack.js +361 -0
- package/dist/src/schema/lens-ack.js.map +1 -0
- package/dist/src/schema/lens-compiler.d.ts +62 -0
- package/dist/src/schema/lens-compiler.d.ts.map +1 -0
- package/dist/src/schema/lens-compiler.js +1594 -0
- package/dist/src/schema/lens-compiler.js.map +1 -0
- package/dist/src/schema/lens-fk-discovery.d.ts +175 -0
- package/dist/src/schema/lens-fk-discovery.d.ts.map +1 -0
- package/dist/src/schema/lens-fk-discovery.js +336 -0
- package/dist/src/schema/lens-fk-discovery.js.map +1 -0
- package/dist/src/schema/lens-prover.d.ts +336 -0
- package/dist/src/schema/lens-prover.d.ts.map +1 -0
- package/dist/src/schema/lens-prover.js +1988 -0
- package/dist/src/schema/lens-prover.js.map +1 -0
- package/dist/src/schema/lens.d.ts +254 -0
- package/dist/src/schema/lens.d.ts.map +1 -0
- package/dist/src/schema/lens.js +21 -0
- package/dist/src/schema/lens.js.map +1 -0
- package/dist/src/schema/manager.d.ts +676 -18
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +1573 -238
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/mapping-advertisement-tags.d.ts +39 -0
- package/dist/src/schema/mapping-advertisement-tags.d.ts.map +1 -0
- package/dist/src/schema/mapping-advertisement-tags.js +216 -0
- package/dist/src/schema/mapping-advertisement-tags.js.map +1 -0
- package/dist/src/schema/rename-rewriter.d.ts +45 -4
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +412 -19
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/reserved-tags-policy.d.ts +32 -0
- package/dist/src/schema/reserved-tags-policy.d.ts.map +1 -0
- package/dist/src/schema/reserved-tags-policy.js +34 -0
- package/dist/src/schema/reserved-tags-policy.js.map +1 -0
- package/dist/src/schema/reserved-tags.d.ts +170 -0
- package/dist/src/schema/reserved-tags.d.ts.map +1 -0
- package/dist/src/schema/reserved-tags.js +507 -0
- package/dist/src/schema/reserved-tags.js.map +1 -0
- package/dist/src/schema/schema-differ.d.ts +158 -2
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +1460 -78
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/schema-hasher.d.ts +8 -3
- package/dist/src/schema/schema-hasher.d.ts.map +1 -1
- package/dist/src/schema/schema-hasher.js +22 -2
- package/dist/src/schema/schema-hasher.js.map +1 -1
- package/dist/src/schema/schema.d.ts +25 -1
- package/dist/src/schema/schema.d.ts.map +1 -1
- package/dist/src/schema/schema.js +36 -2
- package/dist/src/schema/schema.js.map +1 -1
- package/dist/src/schema/table.d.ts +259 -10
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +309 -26
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +78 -0
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -0
- package/dist/src/schema/unique-enforcement.js +93 -0
- package/dist/src/schema/unique-enforcement.js.map +1 -0
- package/dist/src/schema/view.d.ts +83 -2
- package/dist/src/schema/view.d.ts.map +1 -1
- package/dist/src/schema/view.js +67 -1
- package/dist/src/schema/view.js.map +1 -1
- package/dist/src/schema/window-function.d.ts +9 -1
- package/dist/src/schema/window-function.d.ts.map +1 -1
- package/dist/src/schema/window-function.js.map +1 -1
- package/dist/src/util/comparison.d.ts +24 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +34 -0
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/mutation-statement.d.ts.map +1 -1
- package/dist/src/util/mutation-statement.js +4 -1
- package/dist/src/util/mutation-statement.js.map +1 -1
- package/dist/src/util/serialization.d.ts +9 -0
- package/dist/src/util/serialization.d.ts.map +1 -1
- package/dist/src/util/serialization.js +26 -0
- package/dist/src/util/serialization.js.map +1 -1
- package/dist/src/vtab/backing-host.d.ts +286 -0
- package/dist/src/vtab/backing-host.d.ts.map +1 -0
- package/dist/src/vtab/backing-host.js +118 -0
- package/dist/src/vtab/backing-host.js.map +1 -0
- package/dist/src/vtab/best-access-plan.d.ts +21 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/capabilities.d.ts +5 -5
- package/dist/src/vtab/capabilities.d.ts.map +1 -1
- package/dist/src/vtab/mapping-advertisement.d.ts +163 -0
- package/dist/src/vtab/mapping-advertisement.d.ts.map +1 -0
- package/dist/src/vtab/mapping-advertisement.js +2 -0
- package/dist/src/vtab/mapping-advertisement.js.map +1 -0
- package/dist/src/vtab/memory/index.d.ts +64 -4
- package/dist/src/vtab/memory/index.d.ts.map +1 -1
- package/dist/src/vtab/memory/index.js +119 -12
- package/dist/src/vtab/memory/index.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +38 -1
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +112 -24
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +291 -4
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +1050 -91
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.js +35 -6
- package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +66 -14
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.d.ts +14 -0
- package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.js +27 -4
- package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +5 -1
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +17 -0
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +82 -3
- 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 +15 -5
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/memory/types.d.ts +20 -2
- package/dist/src/vtab/memory/types.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.js +46 -24
- package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key-encode.d.ts +31 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.js +101 -0
- package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts +8 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.js +12 -5
- package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
- package/dist/src/vtab/module.d.ts +203 -4
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +9 -0
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +6 -5
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Schema } from './schema.js';
|
|
2
|
+
import { normalizeCollationName } from '../util/comparison.js';
|
|
2
3
|
import { quereusError, QuereusError } from '../common/errors.js';
|
|
3
4
|
import { StatusCode } from '../common/types.js';
|
|
4
|
-
import { buildColumnIndexMap, columnDefToSchema, findPKDefinition, opsToMask, mutationContextVarToSchema, extractGeneratedColumnDependencies, topoSortGeneratedColumns } from './table.js';
|
|
5
|
+
import { buildColumnIndexMap, columnDefToSchema, findPKDefinition, opsToMask, mutationContextVarToSchema, extractGeneratedColumnDependencies, topoSortGeneratedColumns, requireVtabModule, resolveNamedConstraintClass, appendIndexToTableSchema } from './table.js';
|
|
6
|
+
import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, validateForeignKeyCollations } from './constraint-builder.js';
|
|
7
|
+
import { normalizeBackingModule } from './view.js';
|
|
8
|
+
import { isMaintainedTable } from './derivation.js';
|
|
9
|
+
import { isHiddenImplicitIndex, findExposedImplicitConstraintIndex } from './catalog.js';
|
|
10
|
+
import { buildLensBasisFkGate } from './lens-fk-discovery.js';
|
|
5
11
|
import { createLogger } from '../common/logger.js';
|
|
6
12
|
import { Parser } from '../parser/parser.js';
|
|
7
13
|
import { traverseAst } from '../parser/visitor.js';
|
|
@@ -13,10 +19,49 @@ import { BuildTimeDependencyTracker } from '../planner/planning-context.js';
|
|
|
13
19
|
import { GlobalScope } from '../planner/scopes/global.js';
|
|
14
20
|
import { ParameterScope } from '../planner/scopes/param.js';
|
|
15
21
|
import { hasNativeEventSupport } from '../util/event-support.js';
|
|
16
|
-
import { quoteIdentifier } from '../emit/ast-stringify.js';
|
|
22
|
+
import { quoteIdentifier, createViewToString, astToString } from '../emit/ast-stringify.js';
|
|
23
|
+
import { materializeView, adoptMaterializedView, deriveBackingShape, backingShapeMatches, assertDeclaredColumnArity } from '../runtime/emit/materialized-view-helpers.js';
|
|
17
24
|
const log = createLogger('schema:manager');
|
|
18
25
|
const warnLog = log.extend('warn');
|
|
19
26
|
const errorLog = log.extend('error');
|
|
27
|
+
/**
|
|
28
|
+
* Shared frozen empty result for the reverse-FK lookup miss (the O(1)
|
|
29
|
+
* unreferenced-table gate), so the hot path allocates nothing per call.
|
|
30
|
+
*/
|
|
31
|
+
const EMPTY_REFERENCING_FKS = Object.freeze([]);
|
|
32
|
+
/** Build the import spec from the `create materialized view` sugar form. */
|
|
33
|
+
function maintainedImportFromMvStmt(stmt) {
|
|
34
|
+
return {
|
|
35
|
+
schemaName: stmt.view.schema,
|
|
36
|
+
name: stmt.view.name,
|
|
37
|
+
select: stmt.select,
|
|
38
|
+
columns: stmt.columns,
|
|
39
|
+
moduleName: stmt.moduleName,
|
|
40
|
+
moduleArgs: stmt.moduleArgs,
|
|
41
|
+
tags: stmt.tags,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the import spec from the canonical `create table … maintained as …` form
|
|
46
|
+
* (the unified-model persistence / export form). The rename list is read from the
|
|
47
|
+
* `maintained [(columns)]` clause, NOT the declared column list: its presence is
|
|
48
|
+
* the lossless flag that distinguishes an explicit MV-sugar rename (arity-locks —
|
|
49
|
+
* a widened source is a sited error) from an implicit `select *` body (reshapes to
|
|
50
|
+
* follow its source on reopen). `generateMaintainedTableDDL` emits the clause iff
|
|
51
|
+
* the derivation carries a rename, so this restores `derivation.columns` faithfully.
|
|
52
|
+
*/
|
|
53
|
+
function maintainedImportFromTableStmt(stmt) {
|
|
54
|
+
const maintained = stmt.maintained;
|
|
55
|
+
return {
|
|
56
|
+
schemaName: stmt.table.schema,
|
|
57
|
+
name: stmt.table.name,
|
|
58
|
+
select: maintained.select,
|
|
59
|
+
columns: maintained.columns,
|
|
60
|
+
moduleName: stmt.moduleName,
|
|
61
|
+
moduleArgs: stmt.moduleArgs,
|
|
62
|
+
tags: stmt.tags,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
20
65
|
/**
|
|
21
66
|
* Manages all schemas associated with a database connection (main, temp, attached).
|
|
22
67
|
* Handles lookup resolution according to SQLite's rules.
|
|
@@ -38,6 +83,44 @@ export class SchemaManager {
|
|
|
38
83
|
* See `assertion-hoist-cache.ts` and `core/database-assertions.ts`.
|
|
39
84
|
*/
|
|
40
85
|
assertionHoistSuppressed = 0;
|
|
86
|
+
/**
|
|
87
|
+
* Re-entrancy guard: when truthy, the read-side materialized-view query-rewrite
|
|
88
|
+
* rule (`rule-materialized-view-rewrite.ts`) is suppressed. Set while planning a
|
|
89
|
+
* materialized view's own body for the purpose of (re)computing or maintaining
|
|
90
|
+
* its backing table (create / refresh / row-time-maintenance compile). Without
|
|
91
|
+
* it, the rewrite rule would recognize the MV's body as "answered from" the MV
|
|
92
|
+
* itself and rewrite it to scan the backing table being populated — reading a
|
|
93
|
+
* stale/empty snapshot instead of recomputing from the source.
|
|
94
|
+
*/
|
|
95
|
+
mvRewriteSuppressed = 0;
|
|
96
|
+
/**
|
|
97
|
+
* Catalog-level reverse foreign-key index: referenced `schema.table`
|
|
98
|
+
* (lowercased) → the FKs that reference it. `null` ⇒ needs a (re)build from
|
|
99
|
+
* the live catalog on next access — a pure derived cache, nulled on every
|
|
100
|
+
* mutation that can add/drop/retarget an FK or add/remove a schema (see
|
|
101
|
+
* {@link invalidateReverseFkIndex}). See `getReferencingForeignKeys`.
|
|
102
|
+
*/
|
|
103
|
+
reverseFkIndex = null;
|
|
104
|
+
/**
|
|
105
|
+
* Lens basis-FK gate: the set of basis `schema.table` keys (lowercased) that
|
|
106
|
+
* back ≥1 logical parent slot referenced by ≥1 logical FK — the logical-FK
|
|
107
|
+
* analogue of {@link reverseFkIndex}. `null` ⇒ rebuild from the live catalog on
|
|
108
|
+
* next access. A pure derived cache (built by {@link buildLensBasisFkGate}),
|
|
109
|
+
* nulled on every event that can change the underlying slot scan: lens deploy
|
|
110
|
+
* (no `SchemaChangeEvent` fires — `lens-compiler` calls
|
|
111
|
+
* {@link invalidateLensFkGate} directly), any `table_added`/`_modified`/`_removed`
|
|
112
|
+
* (basis-table catalog change, via the constructor listener), and schema
|
|
113
|
+
* attach/detach/reset (no event — invalidated directly).
|
|
114
|
+
*
|
|
115
|
+
* Soundness invariant (load-bearing): a stale gate that **under-reports** would
|
|
116
|
+
* silently drop logical FK enforcement (cascade not propagated / RESTRICT not
|
|
117
|
+
* enforced / divergent basis action not suppressed) — the fatal direction — so
|
|
118
|
+
* invalidation must be exhaustive. Built from, and reset alongside, the same
|
|
119
|
+
* catalog state the three lens FK paths scan, it never under-reports for the
|
|
120
|
+
* current catalog; over-reporting (a stray key ⇒ an on-hit scan that finds
|
|
121
|
+
* nothing) is harmless. See {@link basisTableBacksLogicalParentFk}.
|
|
122
|
+
*/
|
|
123
|
+
lensFkGate = null;
|
|
41
124
|
/**
|
|
42
125
|
* Creates a new schema manager
|
|
43
126
|
*
|
|
@@ -48,6 +131,27 @@ export class SchemaManager {
|
|
|
48
131
|
// Ensure 'main' and 'temp' schemas always exist
|
|
49
132
|
this.schemas.set('main', new Schema('main'));
|
|
50
133
|
this.schemas.set('temp', new Schema('temp'));
|
|
134
|
+
// Self-subscribe so any table FK-lifecycle event invalidates the reverse FK
|
|
135
|
+
// index. An FK is declared on a table, and a table enters/leaves/changes the
|
|
136
|
+
// catalog ONLY through one of these events: `create table … references`
|
|
137
|
+
// (table_added), `alter table add/drop constraint` and FK retargets from a
|
|
138
|
+
// parent/column rename (table_modified), and `drop table` (table_removed) —
|
|
139
|
+
// so this is exhaustive. The body just nulls the cache (rebuild happens on
|
|
140
|
+
// next access, never inside the listener), so subscribing to our own notifier
|
|
141
|
+
// is order-independent and safe. Schema ATTACH/DETACH fire no event, so those
|
|
142
|
+
// invalidate directly in addSchema/getOrCreateSchema/removeSchema. The
|
|
143
|
+
// listener lifetime is the SchemaManager's (no disposal path to unsubscribe).
|
|
144
|
+
// The same table events also change basis-table resolution for the lens
|
|
145
|
+
// basis-FK gate (a basis table created after the gate was built is the
|
|
146
|
+
// under-report vector; a drop / column-rename can change which slot resolves
|
|
147
|
+
// to which basis), so reset it on the same events. Lens-slot lifecycle fires
|
|
148
|
+
// no event — `lens-compiler.deployLogicalSchema` invalidates the gate directly.
|
|
149
|
+
this.changeNotifier.addListener(event => {
|
|
150
|
+
if (event.type === 'table_added' || event.type === 'table_modified' || event.type === 'table_removed') {
|
|
151
|
+
this.invalidateReverseFkIndex();
|
|
152
|
+
this.invalidateLensFkGate();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
51
155
|
}
|
|
52
156
|
/**
|
|
53
157
|
* Sets the current default schema for unqualified names
|
|
@@ -224,7 +328,7 @@ export class SchemaManager {
|
|
|
224
328
|
if (existing) {
|
|
225
329
|
this.changeNotifier.notifyChange({
|
|
226
330
|
type: 'assertion_modified',
|
|
227
|
-
schemaName:
|
|
331
|
+
schemaName: schema.name,
|
|
228
332
|
objectName: assertion.name,
|
|
229
333
|
oldObject: existing,
|
|
230
334
|
newObject: assertion,
|
|
@@ -233,7 +337,7 @@ export class SchemaManager {
|
|
|
233
337
|
else {
|
|
234
338
|
this.changeNotifier.notifyChange({
|
|
235
339
|
type: 'assertion_added',
|
|
236
|
-
schemaName:
|
|
340
|
+
schemaName: schema.name,
|
|
237
341
|
objectName: assertion.name,
|
|
238
342
|
newObject: assertion,
|
|
239
343
|
});
|
|
@@ -254,7 +358,7 @@ export class SchemaManager {
|
|
|
254
358
|
if (removed) {
|
|
255
359
|
this.changeNotifier.notifyChange({
|
|
256
360
|
type: 'assertion_removed',
|
|
257
|
-
schemaName:
|
|
361
|
+
schemaName: schema.name,
|
|
258
362
|
objectName: name,
|
|
259
363
|
oldObject: existing,
|
|
260
364
|
});
|
|
@@ -289,21 +393,80 @@ export class SchemaManager {
|
|
|
289
393
|
this.assertionHoistSuppressed--;
|
|
290
394
|
}
|
|
291
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* True when the read-side materialized-view query-rewrite rule must be
|
|
398
|
+
* suppressed (the caller is currently planning an MV's own body to recompute or
|
|
399
|
+
* maintain its backing). Read by `rule-materialized-view-rewrite.ts`.
|
|
400
|
+
*/
|
|
401
|
+
isMaterializedViewRewriteSuppressed() {
|
|
402
|
+
return this.mvRewriteSuppressed > 0;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Run a synchronous `fn` with the materialized-view query-rewrite rule
|
|
406
|
+
* suppressed. Re-entrant via a depth counter; always restores state, even on
|
|
407
|
+
* throw. Wrap every place that plans an MV body to (re)compute its backing.
|
|
408
|
+
*/
|
|
409
|
+
withSuppressedMaterializedViewRewrite(fn) {
|
|
410
|
+
this.mvRewriteSuppressed++;
|
|
411
|
+
try {
|
|
412
|
+
return fn();
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
this.mvRewriteSuppressed--;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/** Async counterpart of {@link withSuppressedMaterializedViewRewrite}. */
|
|
419
|
+
async withSuppressedMaterializedViewRewriteAsync(fn) {
|
|
420
|
+
this.mvRewriteSuppressed++;
|
|
421
|
+
try {
|
|
422
|
+
return await fn();
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
this.mvRewriteSuppressed--;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
292
428
|
/**
|
|
293
429
|
* Adds a new schema (e.g., for ATTACH)
|
|
294
430
|
*
|
|
295
431
|
* @param name Name of the schema to add
|
|
432
|
+
* @param kind Whether the schema is module-backed (`physical`, default) or
|
|
433
|
+
* design-only (`logical`). See `docs/lens.md` § Schema Kinds.
|
|
296
434
|
* @returns The newly created schema
|
|
297
435
|
* @throws QuereusError if the name conflicts with an existing schema
|
|
298
436
|
*/
|
|
299
|
-
addSchema(name) {
|
|
437
|
+
addSchema(name, kind = 'physical') {
|
|
300
438
|
const lowerName = name.toLowerCase();
|
|
301
439
|
if (this.schemas.has(lowerName)) {
|
|
302
440
|
throw new QuereusError(`Schema '${name}' already exists`, StatusCode.ERROR);
|
|
303
441
|
}
|
|
304
|
-
const schema = new Schema(lowerName);
|
|
442
|
+
const schema = new Schema(lowerName, kind);
|
|
305
443
|
this.schemas.set(lowerName, schema);
|
|
306
|
-
|
|
444
|
+
// ATTACH can bring (or, via later import, an attached schema can hold) a
|
|
445
|
+
// cross-schema FK target; this method fires no change event, so reset directly.
|
|
446
|
+
// A logical-schema ATTACH likewise brings lens slots, so reset the lens gate too.
|
|
447
|
+
this.invalidateReverseFkIndex();
|
|
448
|
+
this.invalidateLensFkGate();
|
|
449
|
+
log(`Added schema '%s' (kind=%s)`, lowerName, kind);
|
|
450
|
+
return schema;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Returns the named schema, lazily creating an empty (physical) one if absent.
|
|
454
|
+
* Used by the catalog-import paths ({@link importTable}/{@link importView}) so an
|
|
455
|
+
* object can rehydrate into a schema that holds no tables yet — making import
|
|
456
|
+
* order-independent. Unlike {@link addSchema} this never throws on an existing
|
|
457
|
+
* schema.
|
|
458
|
+
*/
|
|
459
|
+
getOrCreateSchema(name) {
|
|
460
|
+
const lowerName = name.toLowerCase();
|
|
461
|
+
let schema = this.schemas.get(lowerName);
|
|
462
|
+
if (!schema) {
|
|
463
|
+
schema = new Schema(lowerName);
|
|
464
|
+
this.schemas.set(lowerName, schema);
|
|
465
|
+
// Fires no change event; a cross-schema FK can land in/under this schema, and
|
|
466
|
+
// a lens slot can later resolve a basis under it — reset both derived caches.
|
|
467
|
+
this.invalidateReverseFkIndex();
|
|
468
|
+
this.invalidateLensFkGate();
|
|
469
|
+
}
|
|
307
470
|
return schema;
|
|
308
471
|
}
|
|
309
472
|
/**
|
|
@@ -324,7 +487,14 @@ export class SchemaManager {
|
|
|
324
487
|
schema.clearTables();
|
|
325
488
|
schema.clearViews();
|
|
326
489
|
schema.clearAssertions();
|
|
490
|
+
schema.clearLensSlots();
|
|
327
491
|
this.schemas.delete(lowerName);
|
|
492
|
+
// DETACH fires no change event, yet a cross-schema FK may have keyed
|
|
493
|
+
// under (or referenced from) this schema; reset directly. The schema's lens
|
|
494
|
+
// slots (cleared above) also leave the gate stale — a basis re-attach after a
|
|
495
|
+
// stale-but-non-null gate could under-report — so reset it too.
|
|
496
|
+
this.invalidateReverseFkIndex();
|
|
497
|
+
this.invalidateLensFkGate();
|
|
328
498
|
log(`Removed schema '%s'`, name);
|
|
329
499
|
return true;
|
|
330
500
|
}
|
|
@@ -416,6 +586,49 @@ export class SchemaManager {
|
|
|
416
586
|
const schema = this.schemas.get(targetSchemaName);
|
|
417
587
|
return schema?.getView(viewName);
|
|
418
588
|
}
|
|
589
|
+
/**
|
|
590
|
+
* Retrieves a maintained table (a table carrying a derivation — what
|
|
591
|
+
* `create materialized view` produces) by name.
|
|
592
|
+
*
|
|
593
|
+
* @param schemaName The schema name ('main', etc.). Defaults to current schema
|
|
594
|
+
* @param name The maintained table's name
|
|
595
|
+
*/
|
|
596
|
+
getMaintainedTable(schemaName, name) {
|
|
597
|
+
const table = this.getTable(schemaName ?? undefined, name);
|
|
598
|
+
return isMaintainedTable(table) ? table : undefined;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Returns all maintained tables (derivation-bearing tables) across all schemas.
|
|
602
|
+
*/
|
|
603
|
+
getAllMaintainedTables() {
|
|
604
|
+
const result = [];
|
|
605
|
+
for (const schema of this.schemas.values()) {
|
|
606
|
+
for (const table of schema.getAllTables()) {
|
|
607
|
+
if (isMaintainedTable(table))
|
|
608
|
+
result.push(table);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Attaches (or replaces) a derivation on an already-registered table,
|
|
615
|
+
* swapping the registered record for `{...table, derivation}`. Fires no
|
|
616
|
+
* event — callers own the event discipline (create fires
|
|
617
|
+
* `materialized_view_added`; import stays silent). Returns the swapped
|
|
618
|
+
* maintained table.
|
|
619
|
+
*
|
|
620
|
+
* @throws QuereusError if the table is not registered.
|
|
621
|
+
*/
|
|
622
|
+
attachDerivation(schemaName, tableName, derivation) {
|
|
623
|
+
const schema = this.getSchemaOrFail(schemaName);
|
|
624
|
+
const table = schema.getTable(tableName);
|
|
625
|
+
if (!table) {
|
|
626
|
+
throw new QuereusError(`Cannot attach derivation: table '${schemaName}.${tableName}' not found`, StatusCode.INTERNAL);
|
|
627
|
+
}
|
|
628
|
+
const maintained = { ...table, derivation };
|
|
629
|
+
schema.addTable(maintained);
|
|
630
|
+
return maintained;
|
|
631
|
+
}
|
|
419
632
|
/**
|
|
420
633
|
* Retrieves any schema item (table or view) by name. Checks views first
|
|
421
634
|
*
|
|
@@ -446,6 +659,79 @@ export class SchemaManager {
|
|
|
446
659
|
const tableSchema = this.getTable(targetSchemaName, tableName);
|
|
447
660
|
return tableSchema?.tags;
|
|
448
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Freezes a whole-set tag replacement: an empty record stores `undefined`
|
|
664
|
+
* (so `tags IS NULL` and the differ's "no tags" both hold), a non-empty one a
|
|
665
|
+
* frozen copy. Shared by the three catalog-only tag setters.
|
|
666
|
+
*/
|
|
667
|
+
freezeTags(tags) {
|
|
668
|
+
return Object.keys(tags).length > 0 ? Object.freeze({ ...tags }) : undefined;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Computes the next frozen tag record from the current one plus a per-key
|
|
672
|
+
* mutation, reusing {@link freezeTags} for the empty→`undefined` collapse.
|
|
673
|
+
*
|
|
674
|
+
* - `merge`: shallow-overlay the new keys onto the current set (overwrite on
|
|
675
|
+
* collision), keeping the rest. A merge of a non-empty payload can never empty
|
|
676
|
+
* the set; an empty merge of an empty set collapses to `undefined`.
|
|
677
|
+
* - `drop`: every listed key must currently be present (atomic). Any missing key
|
|
678
|
+
* raises `NOTFOUND` naming the offenders and mutates nothing; otherwise the keys
|
|
679
|
+
* are deleted and dropping the last key collapses to `undefined`. Key matching
|
|
680
|
+
* is verbatim (case-sensitive), matching how `parseTags` stores keys.
|
|
681
|
+
*/
|
|
682
|
+
mutateTagRecord(current, mutation) {
|
|
683
|
+
if (mutation.op === 'merge') {
|
|
684
|
+
return this.freezeTags({ ...(current ?? {}), ...mutation.tags });
|
|
685
|
+
}
|
|
686
|
+
const next = { ...(current ?? {}) };
|
|
687
|
+
const missing = mutation.keys.filter(k => !(k in next));
|
|
688
|
+
if (missing.length > 0) {
|
|
689
|
+
throw new QuereusError(`Tag key(s) not found: ${missing.join(', ')}`, StatusCode.NOTFOUND);
|
|
690
|
+
}
|
|
691
|
+
for (const k of mutation.keys)
|
|
692
|
+
delete next[k];
|
|
693
|
+
return this.freezeTags(next);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Re-registers a tag-only schema swap and fires `table_modified` so optimizer
|
|
697
|
+
* caches invalidate. Tags are excluded from the schema hash, so a tag-only swap
|
|
698
|
+
* is a structural no-op except for the metadata itself.
|
|
699
|
+
*/
|
|
700
|
+
commitTagUpdate(targetSchemaName, oldSchema, newSchema) {
|
|
701
|
+
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
702
|
+
schema.addTable(newSchema);
|
|
703
|
+
this.changeNotifier.notifyChange({
|
|
704
|
+
type: 'table_modified',
|
|
705
|
+
// Stored names of the swapped object, not the raw ALTER args — see
|
|
706
|
+
// canonicalSchemaName for the emitter/stored-name invariant. A raw
|
|
707
|
+
// `targetSchemaName` here (e.g. `alter index MAIN.idx set tags`) would
|
|
708
|
+
// miss the table dep's stored `'main'`.
|
|
709
|
+
schemaName: newSchema.schemaName,
|
|
710
|
+
objectName: newSchema.name,
|
|
711
|
+
oldObject: oldSchema,
|
|
712
|
+
newObject: newSchema,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Shared table-tag read-modify-write: fetches the live table (NOTFOUND if
|
|
717
|
+
* absent), computes the next tag record from its current `tags` via `compute`,
|
|
718
|
+
* and commits the swap (firing `table_modified`). `compute` decides
|
|
719
|
+
* replace / merge / drop; it may throw before any mutation (e.g. drop-of-absent
|
|
720
|
+
* NOTFOUND), leaving the catalog untouched. Reads the *live* schema each call so
|
|
721
|
+
* back-to-back ALTERs and prepared-statement reuse see the prior result.
|
|
722
|
+
*/
|
|
723
|
+
updateTableTags(tableName, compute, schemaName) {
|
|
724
|
+
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
725
|
+
const tableSchema = this.getTable(targetSchemaName, tableName);
|
|
726
|
+
if (!tableSchema) {
|
|
727
|
+
throw new QuereusError(`Table '${tableName}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
728
|
+
}
|
|
729
|
+
const updatedSchema = {
|
|
730
|
+
...tableSchema,
|
|
731
|
+
tags: compute(tableSchema.tags),
|
|
732
|
+
};
|
|
733
|
+
this.commitTagUpdate(targetSchemaName, tableSchema, updatedSchema);
|
|
734
|
+
}
|
|
449
735
|
/**
|
|
450
736
|
* Sets metadata tags on an existing table, replacing any existing tags.
|
|
451
737
|
*
|
|
@@ -454,18 +740,456 @@ export class SchemaManager {
|
|
|
454
740
|
* @param schemaName Optional schema name (defaults to current schema)
|
|
455
741
|
*/
|
|
456
742
|
setTableTags(tableName, tags, schemaName) {
|
|
743
|
+
this.updateTableTags(tableName, () => this.freezeTags(tags), schemaName);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Merges `tags` into an existing table's tags — set/overwrite the listed keys,
|
|
747
|
+
* keep the rest (the `ALTER TABLE … ADD TAGS` primitive). An empty `tags` is a
|
|
748
|
+
* no-op (it does NOT clear). Reads the table's live tags at call time.
|
|
749
|
+
*/
|
|
750
|
+
mergeTableTags(tableName, tags, schemaName) {
|
|
751
|
+
this.updateTableTags(tableName, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Drops the listed keys from an existing table's tags (the `ALTER TABLE …
|
|
755
|
+
* DROP TAGS` primitive). Atomic: every key must be present, else `NOTFOUND`
|
|
756
|
+
* names the missing key(s) and nothing is dropped. Dropping the last key(s)
|
|
757
|
+
* leaves `tags` undefined. An empty `keys` is a no-op.
|
|
758
|
+
*/
|
|
759
|
+
dropTableTags(tableName, keys, schemaName) {
|
|
760
|
+
this.updateTableTags(tableName, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Shared column-tag read-modify-write: resolves the table and column (NOTFOUND
|
|
764
|
+
* on either miss), computes the column's next tag record from its current `tags`
|
|
765
|
+
* via `compute`, and commits the swap. Only the column's `tags` field changes;
|
|
766
|
+
* nullability / type / default / PK membership are untouched. `compute` may throw
|
|
767
|
+
* before any mutation (drop-of-absent NOTFOUND), leaving the catalog untouched.
|
|
768
|
+
*/
|
|
769
|
+
updateColumnTags(tableName, columnName, compute, schemaName) {
|
|
457
770
|
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
458
771
|
const tableSchema = this.getTable(targetSchemaName, tableName);
|
|
459
772
|
if (!tableSchema) {
|
|
460
773
|
throw new QuereusError(`Table '${tableName}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
461
774
|
}
|
|
462
|
-
const
|
|
775
|
+
const colIndex = tableSchema.columnIndexMap.get(columnName.toLowerCase());
|
|
776
|
+
if (colIndex === undefined) {
|
|
777
|
+
throw new QuereusError(`Column '${columnName}' not found in table '${tableName}'`, StatusCode.NOTFOUND);
|
|
778
|
+
}
|
|
779
|
+
// Compute before building the new column array so a drop-of-absent NOTFOUND
|
|
780
|
+
// aborts before any swap.
|
|
781
|
+
const nextTags = compute(tableSchema.columns[colIndex].tags);
|
|
782
|
+
const newColumns = tableSchema.columns.map((c, i) => (i === colIndex ? { ...c, tags: nextTags } : c));
|
|
463
783
|
const updatedSchema = {
|
|
464
784
|
...tableSchema,
|
|
465
|
-
|
|
785
|
+
columns: Object.freeze(newColumns),
|
|
466
786
|
};
|
|
787
|
+
this.commitTagUpdate(targetSchemaName, tableSchema, updatedSchema);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Sets metadata tags on a column of an existing table, replacing any existing
|
|
791
|
+
* tags on that column (empty record clears). Catalog-only — only the column's
|
|
792
|
+
* `tags` field changes; nullability / type / default / PK membership are
|
|
793
|
+
* untouched.
|
|
794
|
+
*
|
|
795
|
+
* @throws QuereusError(NOTFOUND) if the table or column does not exist.
|
|
796
|
+
*/
|
|
797
|
+
setColumnTags(tableName, columnName, tags, schemaName) {
|
|
798
|
+
this.updateColumnTags(tableName, columnName, () => this.freezeTags(tags), schemaName);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Merges `tags` into a column's existing tags — set/overwrite the listed keys,
|
|
802
|
+
* keep the rest (`ALTER TABLE … ALTER COLUMN … ADD TAGS`). Empty `tags` is a
|
|
803
|
+
* no-op (does NOT clear).
|
|
804
|
+
*
|
|
805
|
+
* @throws QuereusError(NOTFOUND) if the table or column does not exist.
|
|
806
|
+
*/
|
|
807
|
+
mergeColumnTags(tableName, columnName, tags, schemaName) {
|
|
808
|
+
this.updateColumnTags(tableName, columnName, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Drops the listed keys from a column's tags (`ALTER TABLE … ALTER COLUMN …
|
|
812
|
+
* DROP TAGS`). Atomic: every key must be present, else `NOTFOUND` names the
|
|
813
|
+
* missing key(s) and nothing is dropped. Empty `keys` is a no-op.
|
|
814
|
+
*
|
|
815
|
+
* @throws QuereusError(NOTFOUND) if the table or column does not exist, or any
|
|
816
|
+
* listed key is absent.
|
|
817
|
+
*/
|
|
818
|
+
dropColumnTags(tableName, columnName, keys, schemaName) {
|
|
819
|
+
this.updateColumnTags(tableName, columnName, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Shared named-constraint-tag read-modify-write: resolves the table (NOTFOUND if
|
|
823
|
+
* absent) and the single matching constraint class (check → unique → fk;
|
|
824
|
+
* NOTFOUND / ambiguous via {@link resolveNamedConstraintClass}), computes the
|
|
825
|
+
* matching constraint's next tag record from its current `tags` via `compute`,
|
|
826
|
+
* and commits. `compute` may throw before any mutation (drop-of-absent NOTFOUND);
|
|
827
|
+
* since it runs inside the array rebuild prior to `commitTagUpdate`, a throw
|
|
828
|
+
* leaves the catalog untouched.
|
|
829
|
+
*/
|
|
830
|
+
updateConstraintTags(tableName, constraintName, compute, schemaName) {
|
|
831
|
+
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
832
|
+
const tableSchema = this.getTable(targetSchemaName, tableName);
|
|
833
|
+
if (!tableSchema) {
|
|
834
|
+
throw new QuereusError(`Table '${tableName}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
835
|
+
}
|
|
836
|
+
const lower = constraintName.toLowerCase();
|
|
837
|
+
// Resolve to exactly one class (check → unique → fk), or throw NOTFOUND/ambiguous.
|
|
838
|
+
const constraintClass = resolveNamedConstraintClass(tableSchema, constraintName);
|
|
839
|
+
const updatedSchema = { ...tableSchema };
|
|
840
|
+
if (constraintClass === 'check') {
|
|
841
|
+
updatedSchema.checkConstraints = Object.freeze(tableSchema.checkConstraints.map(c => (c.name?.toLowerCase() === lower ? { ...c, tags: compute(c.tags) } : c)));
|
|
842
|
+
}
|
|
843
|
+
else if (constraintClass === 'unique') {
|
|
844
|
+
updatedSchema.uniqueConstraints = Object.freeze(tableSchema.uniqueConstraints.map(c => (c.name?.toLowerCase() === lower ? { ...c, tags: compute(c.tags) } : c)));
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
updatedSchema.foreignKeys = Object.freeze(tableSchema.foreignKeys.map(c => (c.name?.toLowerCase() === lower ? { ...c, tags: compute(c.tags) } : c)));
|
|
848
|
+
}
|
|
849
|
+
this.commitTagUpdate(targetSchemaName, tableSchema, updatedSchema);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Sets metadata tags on a NAMED table-level constraint (CHECK / UNIQUE /
|
|
853
|
+
* FOREIGN KEY), replacing any existing tags (empty record clears). Lookup order
|
|
854
|
+
* is checks → unique → foreign keys; a name present in more than one class is
|
|
855
|
+
* rejected as ambiguous. Unnamed constraints are not addressable.
|
|
856
|
+
*
|
|
857
|
+
* @throws QuereusError(NOTFOUND) if no named constraint matches.
|
|
858
|
+
* @throws QuereusError(ERROR) if the name is ambiguous across constraint classes.
|
|
859
|
+
*/
|
|
860
|
+
setConstraintTags(tableName, constraintName, tags, schemaName) {
|
|
861
|
+
this.updateConstraintTags(tableName, constraintName, () => this.freezeTags(tags), schemaName);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Merges `tags` into a named constraint's existing tags — set/overwrite the
|
|
865
|
+
* listed keys, keep the rest (`ALTER TABLE … ALTER CONSTRAINT … ADD TAGS`).
|
|
866
|
+
* Empty `tags` is a no-op (does NOT clear).
|
|
867
|
+
*
|
|
868
|
+
* @throws QuereusError(NOTFOUND) if no named constraint matches.
|
|
869
|
+
* @throws QuereusError(ERROR) if the name is ambiguous across constraint classes.
|
|
870
|
+
*/
|
|
871
|
+
mergeConstraintTags(tableName, constraintName, tags, schemaName) {
|
|
872
|
+
this.updateConstraintTags(tableName, constraintName, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Drops the listed keys from a named constraint's tags (`ALTER TABLE … ALTER
|
|
876
|
+
* CONSTRAINT … DROP TAGS`). Atomic: every key must be present, else `NOTFOUND`
|
|
877
|
+
* names the missing key(s) and nothing is dropped. Empty `keys` is a no-op.
|
|
878
|
+
*
|
|
879
|
+
* @throws QuereusError(NOTFOUND) if no named constraint matches, or any listed
|
|
880
|
+
* key is absent.
|
|
881
|
+
* @throws QuereusError(ERROR) if the name is ambiguous across constraint classes.
|
|
882
|
+
*/
|
|
883
|
+
dropConstraintTags(tableName, constraintName, keys, schemaName) {
|
|
884
|
+
this.updateConstraintTags(tableName, constraintName, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Shared view-tag read-modify-write: fetches the live view (NOTFOUND if
|
|
888
|
+
* absent), computes its next tag record from its current `tags` via `compute`,
|
|
889
|
+
* re-registers the swapped {@link ViewSchema}, and fires `view_modified` so a
|
|
890
|
+
* cached write-through plan that recorded a `view` dependency (every
|
|
891
|
+
* view-/MV-mediated write does — see `buildViewMutation`) is invalidated when
|
|
892
|
+
* the view's tags change (tag validation re-runs at plan time, so a
|
|
893
|
+
* newly-invalid tag must surface on the next run). This event is distinct
|
|
894
|
+
* from the (non-existent) plain-view create event, so it triggers no maintenance
|
|
895
|
+
* re-registration. `compute` decides replace / merge / drop and may throw before
|
|
896
|
+
* any mutation (drop-of-absent NOTFOUND), leaving the catalog untouched.
|
|
897
|
+
*/
|
|
898
|
+
updateViewTags(viewName, compute, schemaName) {
|
|
899
|
+
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
467
900
|
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
468
|
-
schema.
|
|
901
|
+
const view = schema.getView(viewName);
|
|
902
|
+
if (!view) {
|
|
903
|
+
throw new QuereusError(`View '${viewName}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
904
|
+
}
|
|
905
|
+
const updated = { ...view, tags: compute(view.tags) };
|
|
906
|
+
schema.addView(updated);
|
|
907
|
+
this.changeNotifier.notifyChange({
|
|
908
|
+
type: 'view_modified',
|
|
909
|
+
// Stored names of the swapped object, not the raw ALTER args — see
|
|
910
|
+
// canonicalSchemaName for the emitter/stored-name invariant. A
|
|
911
|
+
// case-differing ALTER (e.g. `alter view MAIN.MYVIEW` on
|
|
912
|
+
// `create view MyView` in `main`) would otherwise miss on either field.
|
|
913
|
+
schemaName: schema.name,
|
|
914
|
+
objectName: updated.name,
|
|
915
|
+
oldObject: view,
|
|
916
|
+
newObject: updated,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Sets metadata tags on an existing view, replacing any existing tags (empty
|
|
921
|
+
* record clears).
|
|
922
|
+
*
|
|
923
|
+
* @throws QuereusError(NOTFOUND) if the view does not exist.
|
|
924
|
+
*/
|
|
925
|
+
setViewTags(viewName, tags, schemaName) {
|
|
926
|
+
this.updateViewTags(viewName, () => this.freezeTags(tags), schemaName);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Merges `tags` into an existing view's tags — set/overwrite the listed keys,
|
|
930
|
+
* keep the rest (`ALTER VIEW … ADD TAGS`). Empty `tags` is a no-op (does NOT
|
|
931
|
+
* clear). Reads the view's live tags at call time.
|
|
932
|
+
*
|
|
933
|
+
* @throws QuereusError(NOTFOUND) if the view does not exist.
|
|
934
|
+
*/
|
|
935
|
+
mergeViewTags(viewName, tags, schemaName) {
|
|
936
|
+
this.updateViewTags(viewName, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Drops the listed keys from an existing view's tags (`ALTER VIEW … DROP TAGS`).
|
|
940
|
+
* Atomic: every key must be present, else `NOTFOUND` names the missing key(s)
|
|
941
|
+
* and nothing is dropped. Dropping the last key(s) leaves `tags` undefined. An
|
|
942
|
+
* empty `keys` is a no-op.
|
|
943
|
+
*
|
|
944
|
+
* @throws QuereusError(NOTFOUND) if the view does not exist, or any listed key
|
|
945
|
+
* is absent.
|
|
946
|
+
*/
|
|
947
|
+
dropViewTags(viewName, keys, schemaName) {
|
|
948
|
+
this.updateViewTags(viewName, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Shared materialized-view-tag read-modify-write: fetches the live maintained
|
|
952
|
+
* table (NOTFOUND if absent or derivation-less), computes its next tag record
|
|
953
|
+
* via `compute`, re-registers the swapped table (the shared `derivation`
|
|
954
|
+
* object rides the spread), and fires `materialized_view_modified`. The
|
|
955
|
+
* table's contents and the row-time maintenance plan are untouched (tags do
|
|
956
|
+
* not affect maintenance), so this never re-materializes — `_modified` is
|
|
957
|
+
* deliberately distinct from `materialized_view_added` (what create emits):
|
|
958
|
+
* the MV maintenance manager re-registers on `_added` but ignores
|
|
959
|
+
* `_modified`. The event invalidates a cached write-through plan that
|
|
960
|
+
* recorded a `view` dependency when the MV's tags change (tag validation
|
|
961
|
+
* re-runs at plan time). `compute` may throw before any mutation
|
|
962
|
+
* (drop-of-absent NOTFOUND), leaving the catalog untouched.
|
|
963
|
+
*/
|
|
964
|
+
updateMaterializedViewTags(name, compute, schemaName) {
|
|
965
|
+
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
966
|
+
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
967
|
+
const table = schema.getTable(name);
|
|
968
|
+
if (!isMaintainedTable(table)) {
|
|
969
|
+
throw new QuereusError(`Materialized view '${name}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
970
|
+
}
|
|
971
|
+
const updated = { ...table, tags: compute(table.tags) };
|
|
972
|
+
schema.addTable(updated);
|
|
973
|
+
this.changeNotifier.notifyChange({
|
|
974
|
+
type: 'materialized_view_modified',
|
|
975
|
+
// Stored names of the swapped object, not the raw ALTER args — see
|
|
976
|
+
// canonicalSchemaName for the emitter/stored-name invariant.
|
|
977
|
+
schemaName: schema.name,
|
|
978
|
+
objectName: updated.name,
|
|
979
|
+
oldObject: table,
|
|
980
|
+
newObject: updated,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Sets metadata tags on an existing materialized view, replacing any existing
|
|
985
|
+
* tags (empty record clears). Catalog-only — never re-materializes.
|
|
986
|
+
*
|
|
987
|
+
* @throws QuereusError(NOTFOUND) if the materialized view does not exist.
|
|
988
|
+
*/
|
|
989
|
+
setMaterializedViewTags(name, tags, schemaName) {
|
|
990
|
+
this.updateMaterializedViewTags(name, () => this.freezeTags(tags), schemaName);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Merges `tags` into an existing materialized view's tags — set/overwrite the
|
|
994
|
+
* listed keys, keep the rest (`ALTER MATERIALIZED VIEW … ADD TAGS`). Empty
|
|
995
|
+
* `tags` is a no-op (does NOT clear). Catalog-only — never re-materializes.
|
|
996
|
+
*
|
|
997
|
+
* @throws QuereusError(NOTFOUND) if the materialized view does not exist.
|
|
998
|
+
*/
|
|
999
|
+
mergeMaterializedViewTags(name, tags, schemaName) {
|
|
1000
|
+
this.updateMaterializedViewTags(name, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Drops the listed keys from an existing materialized view's tags (`ALTER
|
|
1004
|
+
* MATERIALIZED VIEW … DROP TAGS`). Atomic: every key must be present, else
|
|
1005
|
+
* `NOTFOUND` names the missing key(s) and nothing is dropped. Empty `keys` is a
|
|
1006
|
+
* no-op. Catalog-only — never re-materializes.
|
|
1007
|
+
*
|
|
1008
|
+
* @throws QuereusError(NOTFOUND) if the materialized view does not exist, or any
|
|
1009
|
+
* listed key is absent.
|
|
1010
|
+
*/
|
|
1011
|
+
dropMaterializedViewTags(name, keys, schemaName) {
|
|
1012
|
+
this.updateMaterializedViewTags(name, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Shared index-tag read-modify-write. Indexes live on their owning
|
|
1016
|
+
* {@link TableSchema}, so this resolves the owner by index name, computes the
|
|
1017
|
+
* matching {@link IndexSchema}'s next tag record from its current `tags` via
|
|
1018
|
+
* `compute`, swaps it, re-registers the table, and fires `table_modified`
|
|
1019
|
+
* (mirroring create/drop index) so optimizer caches invalidate.
|
|
1020
|
+
*
|
|
1021
|
+
* Hidden implicit covering structures (the auto-built BTree backing a UNIQUE
|
|
1022
|
+
* constraint, not opted into catalog visibility) are not user-addressable and
|
|
1023
|
+
* surface as NOTFOUND — their tags live on the originating constraint. `compute`
|
|
1024
|
+
* runs before the index array is rebuilt, so a drop-of-absent NOTFOUND aborts
|
|
1025
|
+
* before any swap.
|
|
1026
|
+
*/
|
|
1027
|
+
updateIndexTags(indexName, compute, schemaName) {
|
|
1028
|
+
const targetSchemaName = schemaName ?? this.getCurrentSchemaName();
|
|
1029
|
+
const { oldSchema, newSchema } = this.resolveIndexTagSwap(targetSchemaName, indexName, compute);
|
|
1030
|
+
this.commitTagUpdate(targetSchemaName, oldSchema, newSchema);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Resolve-and-swap core shared by {@link updateIndexTags} (live ALTER — commits
|
|
1034
|
+
* via {@link commitTagUpdate}, firing `table_modified`) and the catalog-import
|
|
1035
|
+
* path ({@link applyImportedIndexTags} — commits silently). Resolves `indexName`
|
|
1036
|
+
* within `targetSchemaName` and returns the owning table plus its swapped
|
|
1037
|
+
* replacement with the computed tags applied; mutates nothing itself.
|
|
1038
|
+
*/
|
|
1039
|
+
resolveIndexTagSwap(targetSchemaName, indexName, compute) {
|
|
1040
|
+
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
1041
|
+
const lower = indexName.toLowerCase();
|
|
1042
|
+
// Primary path: a materialized IndexSchema — every real index, plus the
|
|
1043
|
+
// memory backend's materialized implicit covering index. Tags live on the
|
|
1044
|
+
// matched IndexSchema. A *hidden* implicit index is not user-addressable and
|
|
1045
|
+
// is skipped here; it then fails the exposed-constraint fallback below
|
|
1046
|
+
// (its name is materialized, so it is not "exposed and unmaterialized") and
|
|
1047
|
+
// surfaces as NOTFOUND — preserving Phase 22/37 behavior.
|
|
1048
|
+
for (const table of schema.getAllTables()) {
|
|
1049
|
+
const matched = table.indexes?.find(idx => idx.name.toLowerCase() === lower);
|
|
1050
|
+
if (!matched || isHiddenImplicitIndex(table, matched.name))
|
|
1051
|
+
continue;
|
|
1052
|
+
// Compute before rebuilding the index array so a drop-of-absent NOTFOUND
|
|
1053
|
+
// aborts before any swap.
|
|
1054
|
+
const nextTags = compute(matched.tags);
|
|
1055
|
+
const updatedIndexes = table.indexes.map(idx => (idx.name.toLowerCase() === lower ? { ...idx, tags: nextTags } : idx));
|
|
1056
|
+
return { oldSchema: table, newSchema: { ...table, indexes: Object.freeze(updatedIndexes) } };
|
|
1057
|
+
}
|
|
1058
|
+
// Fallback (store mode): the exposed implicit covering index is not
|
|
1059
|
+
// materialized as an IndexSchema. Route its tags onto the originating UNIQUE
|
|
1060
|
+
// constraint's `exposedIndexTags` — kept separate from `uc.tags`, which holds
|
|
1061
|
+
// the exposure flag, so the flag never leaks into the surfaced index tags.
|
|
1062
|
+
// `findExposedImplicitConstraintIndex` returns -1 for hidden/materialized
|
|
1063
|
+
// implicit indexes, so they fall through to NOTFOUND.
|
|
1064
|
+
for (const table of schema.getAllTables()) {
|
|
1065
|
+
const ucIndex = findExposedImplicitConstraintIndex(table, indexName);
|
|
1066
|
+
if (ucIndex < 0)
|
|
1067
|
+
continue;
|
|
1068
|
+
const constraints = table.uniqueConstraints;
|
|
1069
|
+
// Compute before swapping so a drop-of-absent NOTFOUND aborts untouched.
|
|
1070
|
+
const nextTags = compute(constraints[ucIndex].exposedIndexTags);
|
|
1071
|
+
const updatedConstraints = constraints.map((uc, i) => {
|
|
1072
|
+
if (i !== ucIndex)
|
|
1073
|
+
return uc;
|
|
1074
|
+
const next = { ...uc };
|
|
1075
|
+
if (nextTags)
|
|
1076
|
+
next.exposedIndexTags = nextTags;
|
|
1077
|
+
else
|
|
1078
|
+
delete next.exposedIndexTags;
|
|
1079
|
+
return next;
|
|
1080
|
+
});
|
|
1081
|
+
return { oldSchema: table, newSchema: { ...table, uniqueConstraints: Object.freeze(updatedConstraints) } };
|
|
1082
|
+
}
|
|
1083
|
+
throw new QuereusError(`Index '${indexName}' not found in schema '${targetSchemaName}'`, StatusCode.NOTFOUND);
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Sets metadata tags on an existing index, replacing any existing tags (empty
|
|
1087
|
+
* record clears).
|
|
1088
|
+
*
|
|
1089
|
+
* @throws QuereusError(NOTFOUND) if no user-visible index matches.
|
|
1090
|
+
*/
|
|
1091
|
+
setIndexTags(indexName, tags, schemaName) {
|
|
1092
|
+
this.updateIndexTags(indexName, () => this.freezeTags(tags), schemaName);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Merges `tags` into an existing index's tags — set/overwrite the listed keys,
|
|
1096
|
+
* keep the rest (`ALTER INDEX … ADD TAGS`). Empty `tags` is a no-op (does NOT
|
|
1097
|
+
* clear).
|
|
1098
|
+
*
|
|
1099
|
+
* @throws QuereusError(NOTFOUND) if no user-visible index matches.
|
|
1100
|
+
*/
|
|
1101
|
+
mergeIndexTags(indexName, tags, schemaName) {
|
|
1102
|
+
this.updateIndexTags(indexName, current => this.mutateTagRecord(current, { op: 'merge', tags }), schemaName);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Drops the listed keys from an existing index's tags (`ALTER INDEX … DROP
|
|
1106
|
+
* TAGS`). Atomic: every key must be present, else `NOTFOUND` names the missing
|
|
1107
|
+
* key(s) and nothing is dropped. Empty `keys` is a no-op.
|
|
1108
|
+
*
|
|
1109
|
+
* @throws QuereusError(NOTFOUND) if no user-visible index matches, or any listed
|
|
1110
|
+
* key is absent.
|
|
1111
|
+
*/
|
|
1112
|
+
dropIndexTags(indexName, keys, schemaName) {
|
|
1113
|
+
this.updateIndexTags(indexName, current => this.mutateTagRecord(current, { op: 'drop', keys }), schemaName);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Nulls the reverse FK index so it rebuilds from the live catalog on next
|
|
1117
|
+
* access. Pure derived-cache reset — order-independent, called both from the
|
|
1118
|
+
* self-subscribed change listener and from the schema attach/detach methods.
|
|
1119
|
+
*/
|
|
1120
|
+
invalidateReverseFkIndex() {
|
|
1121
|
+
this.reverseFkIndex = null;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Builds the reverse FK index from the live catalog, bucketing every FK under
|
|
1125
|
+
* its resolved referenced `schema.table` key (cross-schema FKs key under their
|
|
1126
|
+
* `referencedSchema`). Preserves schema-insertion → table → FK-declaration
|
|
1127
|
+
* order within each bucket so the first-surviving-child RESTRICT pre-check and
|
|
1128
|
+
* any error-message golden tests keep naming the same child.
|
|
1129
|
+
*/
|
|
1130
|
+
buildReverseFkIndex() {
|
|
1131
|
+
const index = new Map();
|
|
1132
|
+
for (const schema of this._getAllSchemas()) {
|
|
1133
|
+
for (const childTable of schema.getAllTables()) {
|
|
1134
|
+
if (!childTable.foreignKeys)
|
|
1135
|
+
continue;
|
|
1136
|
+
for (const fk of childTable.foreignKeys) {
|
|
1137
|
+
const refSchema = (fk.referencedSchema ?? childTable.schemaName).toLowerCase();
|
|
1138
|
+
const key = `${refSchema}.${fk.referencedTable.toLowerCase()}`;
|
|
1139
|
+
let bucket = index.get(key);
|
|
1140
|
+
if (!bucket) {
|
|
1141
|
+
bucket = [];
|
|
1142
|
+
index.set(key, bucket);
|
|
1143
|
+
}
|
|
1144
|
+
bucket.push({ childTable, fk });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
return index;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Returns the FKs that reference `parentSchemaName.parentTableName`
|
|
1152
|
+
* (case-insensitive), the shared primitive every parent-side referential scan
|
|
1153
|
+
* uses to short-circuit. Returns a shared frozen empty array — the O(1) gate —
|
|
1154
|
+
* when nothing references the table; otherwise exactly its referencing FKs.
|
|
1155
|
+
* Lazily (re)builds the whole index from the live catalog on the first access
|
|
1156
|
+
* after any schema mutation; a pure derived cache (over-reporting a since-dropped
|
|
1157
|
+
* FK is harmless — each consumer re-checks arity/target in its per-FK body).
|
|
1158
|
+
*
|
|
1159
|
+
* The returned `fk` objects are the same references held in
|
|
1160
|
+
* `childTable.foreignKeys` (identity preserved).
|
|
1161
|
+
*/
|
|
1162
|
+
getReferencingForeignKeys(parentSchemaName, parentTableName) {
|
|
1163
|
+
if (this.reverseFkIndex === null) {
|
|
1164
|
+
this.reverseFkIndex = this.buildReverseFkIndex();
|
|
1165
|
+
}
|
|
1166
|
+
const key = `${parentSchemaName.toLowerCase()}.${parentTableName.toLowerCase()}`;
|
|
1167
|
+
return this.reverseFkIndex.get(key) ?? EMPTY_REFERENCING_FKS;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Nulls the lens basis-FK gate so it rebuilds from the live catalog on next
|
|
1171
|
+
* access. Pure derived-cache reset — order-independent (rebuild happens on next
|
|
1172
|
+
* access, never inside a listener). Public because `lens-compiler` calls it after
|
|
1173
|
+
* a lens (re)deploy, which mutates the slot set without firing a `SchemaChangeEvent`.
|
|
1174
|
+
*/
|
|
1175
|
+
invalidateLensFkGate() {
|
|
1176
|
+
this.lensFkGate = null;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* O(1) gate for the three basis-keyed lens FK paths: does `schemaName.tableName`
|
|
1180
|
+
* (case-insensitive) back ≥1 logical parent slot referenced by ≥1 logical FK?
|
|
1181
|
+
* When `false`, `executeLensForeignKeyActions`,
|
|
1182
|
+
* `assertLensRestrictsForParentMutation`, and `basisFksOverriddenByDivergentLensFk`
|
|
1183
|
+
* early-return — the reverse-map slot scan they would run finds nothing. Lazily
|
|
1184
|
+
* (re)builds {@link lensFkGate} on the first access after any invalidation, then
|
|
1185
|
+
* does a single `Set.has`. See {@link buildLensBasisFkGate} for the build, and the
|
|
1186
|
+
* {@link lensFkGate} doc-comment for the never-under-report soundness invariant.
|
|
1187
|
+
*/
|
|
1188
|
+
basisTableBacksLogicalParentFk(schemaName, tableName) {
|
|
1189
|
+
if (this.lensFkGate === null) {
|
|
1190
|
+
this.lensFkGate = buildLensBasisFkGate(this);
|
|
1191
|
+
}
|
|
1192
|
+
return this.lensFkGate.has(`${schemaName.toLowerCase()}.${tableName.toLowerCase()}`);
|
|
469
1193
|
}
|
|
470
1194
|
/**
|
|
471
1195
|
* Asserts that no other table has FK rows referencing the table being dropped.
|
|
@@ -477,42 +1201,34 @@ export class SchemaManager {
|
|
|
477
1201
|
return;
|
|
478
1202
|
const parentSchemaLower = parentSchemaName.toLowerCase();
|
|
479
1203
|
const parentTableLower = parentTableName.toLowerCase();
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const stmt = this.db.prepare(sql);
|
|
502
|
-
try {
|
|
503
|
-
let referenced = false;
|
|
504
|
-
for await (const _row of stmt._iterateRowsRaw()) {
|
|
505
|
-
referenced = true;
|
|
506
|
-
break;
|
|
507
|
-
}
|
|
508
|
-
if (referenced) {
|
|
509
|
-
throw new QuereusError(`FOREIGN KEY constraint failed: cannot drop table '${parentTableName}' because table '${childTable.name}' still has rows referencing it`, StatusCode.CONSTRAINT);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
finally {
|
|
513
|
-
await stmt.finalize();
|
|
514
|
-
}
|
|
1204
|
+
// The reverse FK index already keyed on the referenced schema.table, so the
|
|
1205
|
+
// two discovery filters (referencedTable / targetSchema match) are satisfied
|
|
1206
|
+
// by the lookup and drop out; every other line below is unchanged.
|
|
1207
|
+
for (const { childTable, fk } of this.getReferencingForeignKeys(parentSchemaName, parentTableName)) {
|
|
1208
|
+
// Skip the table being dropped itself — self-FK rows are going away with it.
|
|
1209
|
+
if (childTable.schemaName.toLowerCase() === parentSchemaLower &&
|
|
1210
|
+
childTable.name.toLowerCase() === parentTableLower)
|
|
1211
|
+
continue;
|
|
1212
|
+
// MATCH SIMPLE: row is referencing iff every FK column is non-NULL.
|
|
1213
|
+
const childColNames = fk.columns.map(idx => quoteIdentifier(childTable.columns[idx].name));
|
|
1214
|
+
const whereClause = childColNames.map(c => `${c} IS NOT NULL`).join(' AND ');
|
|
1215
|
+
const schemaPrefix = childTable.schemaName.toLowerCase() !== 'main'
|
|
1216
|
+
? `${quoteIdentifier(childTable.schemaName)}.`
|
|
1217
|
+
: '';
|
|
1218
|
+
const sql = `select 1 from ${schemaPrefix}${quoteIdentifier(childTable.name)} where ${whereClause} limit 1`;
|
|
1219
|
+
const stmt = this.db.prepare(sql);
|
|
1220
|
+
try {
|
|
1221
|
+
let referenced = false;
|
|
1222
|
+
for await (const _row of stmt._iterateRowsRaw()) {
|
|
1223
|
+
referenced = true;
|
|
1224
|
+
break;
|
|
515
1225
|
}
|
|
1226
|
+
if (referenced) {
|
|
1227
|
+
throw new QuereusError(`FOREIGN KEY constraint failed: cannot drop table '${parentTableName}' because table '${childTable.name}' still has rows referencing it`, StatusCode.CONSTRAINT);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
finally {
|
|
1231
|
+
await stmt.finalize();
|
|
516
1232
|
}
|
|
517
1233
|
}
|
|
518
1234
|
}
|
|
@@ -544,56 +1260,64 @@ export class SchemaManager {
|
|
|
544
1260
|
// with the table). MATCH SIMPLE: a row is "referencing" iff every FK column
|
|
545
1261
|
// is non-NULL.
|
|
546
1262
|
await this.assertNoReferencingChildrenForDrop(schemaName, tableName);
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
1263
|
+
// Call destroy on the module FIRST, awaiting it and PROPAGATING any rejection,
|
|
1264
|
+
// BEFORE any engine-side teardown. A module may veto the drop (e.g. a
|
|
1265
|
+
// schema-level inbound-FK guard that an emptied child cannot satisfy); by
|
|
1266
|
+
// awaiting destroy before mutating connection/schema state we make the veto
|
|
1267
|
+
// abort the statement atomically — on rejection the table stays in our schema
|
|
1268
|
+
// map AND in the module's own catalogue, since neither has been touched yet.
|
|
1269
|
+
// Awaiting here (rather than after removeTable, as before) also preserves the
|
|
1270
|
+
// original "subsequent DDL/DML sees a clean slate" intent: destroy still
|
|
1271
|
+
// completes before dropTable returns, just without swallowing its error.
|
|
553
1272
|
if (tableSchema.vtabModuleName) { // tableSchema is guaranteed to be defined here
|
|
554
1273
|
const moduleRegistration = this.getModule(tableSchema.vtabModuleName);
|
|
555
1274
|
if (moduleRegistration && moduleRegistration.module && moduleRegistration.module.destroy) {
|
|
556
1275
|
log(`Calling destroy for VTab %s.%s via module %s`, schemaName, tableName, tableSchema.vtabModuleName);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1276
|
+
// Module-facing stored-name contract (see canonicalSchemaName): hand the
|
|
1277
|
+
// resolved table's canonical schemaName and stored display casing, never
|
|
1278
|
+
// the raw `drop table T` spelling — so a module keying by the arg finds the
|
|
1279
|
+
// create-time key, and the store's own `drop` schema-change event (which
|
|
1280
|
+
// emits objectName: tableName) fires the stored name.
|
|
1281
|
+
await moduleRegistration.module.destroy(this.db, moduleRegistration.auxData, tableSchema.vtabModuleName, tableSchema.schemaName, tableSchema.name);
|
|
1282
|
+
log(`destroy completed for VTab %s.%s`, schemaName, tableName);
|
|
561
1283
|
}
|
|
562
1284
|
else {
|
|
563
1285
|
warnLog(`VTab module %s (for table %s.%s) or its destroy method not found during dropTable.`, tableSchema.vtabModuleName, schemaName, tableName);
|
|
564
1286
|
}
|
|
565
1287
|
}
|
|
566
|
-
//
|
|
1288
|
+
// destroy succeeded (or the module had none) — now tear down engine-side state.
|
|
1289
|
+
// Remove any active connections for this table before removing it from the
|
|
1290
|
+
// schema map. Connections become stale once the table is dropped and must not
|
|
1291
|
+
// be reused if the table is later recreated with the same name.
|
|
1292
|
+
this.db.removeConnectionsForTable(schemaName, tableName);
|
|
1293
|
+
// Remove from schema map
|
|
567
1294
|
const removed = schema.removeTable(tableName);
|
|
568
1295
|
if (!removed && !ifExists) {
|
|
569
1296
|
// This should ideally not be reached if tableSchema was found above.
|
|
570
1297
|
// But as a safeguard if removeTable could fail for other reasons.
|
|
571
1298
|
throw new QuereusError(`Failed to remove table ${tableName} from schema ${schemaName}, though it was initially found.`, StatusCode.INTERNAL);
|
|
572
1299
|
}
|
|
573
|
-
// Notify schema change listeners if table was removed
|
|
1300
|
+
// Notify schema change listeners if table was removed. Stored names of the
|
|
1301
|
+
// dropped object, not the raw drop args — see canonicalSchemaName for the
|
|
1302
|
+
// emitter/stored-name invariant.
|
|
574
1303
|
if (removed) {
|
|
575
1304
|
this.changeNotifier.notifyChange({
|
|
576
1305
|
type: 'table_removed',
|
|
577
|
-
schemaName: schemaName,
|
|
578
|
-
objectName:
|
|
1306
|
+
schemaName: tableSchema.schemaName,
|
|
1307
|
+
objectName: tableSchema.name,
|
|
579
1308
|
oldObject: tableSchema
|
|
580
1309
|
});
|
|
581
1310
|
// Emit auto schema event for modules without native event support
|
|
582
1311
|
const moduleReg = tableSchema.vtabModuleName ? this.getModule(tableSchema.vtabModuleName) : undefined;
|
|
583
|
-
if (this.db.
|
|
1312
|
+
if (this.db._needsSchemaEvents() && !hasNativeEventSupport(moduleReg?.module)) {
|
|
584
1313
|
this.db._getEventEmitter().emitAutoSchemaEvent(tableSchema.vtabModuleName ?? 'memory', {
|
|
585
1314
|
type: 'drop',
|
|
586
1315
|
objectType: 'table',
|
|
587
|
-
schemaName: schemaName,
|
|
588
|
-
objectName:
|
|
1316
|
+
schemaName: tableSchema.schemaName,
|
|
1317
|
+
objectName: tableSchema.name,
|
|
589
1318
|
});
|
|
590
1319
|
}
|
|
591
1320
|
}
|
|
592
|
-
// Await destruction so subsequent DDL/DML sees a clean slate
|
|
593
|
-
if (destroyPromise) {
|
|
594
|
-
await destroyPromise;
|
|
595
|
-
log(`destroy completed for VTab %s.%s`, schemaName, tableName);
|
|
596
|
-
}
|
|
597
1321
|
return removed; // True if removed from schema, false if not found and ifExists was true.
|
|
598
1322
|
}
|
|
599
1323
|
/**
|
|
@@ -618,7 +1342,12 @@ export class SchemaManager {
|
|
|
618
1342
|
schema.clearFunctions();
|
|
619
1343
|
schema.clearViews();
|
|
620
1344
|
schema.clearAssertions();
|
|
1345
|
+
schema.clearLensSlots();
|
|
621
1346
|
});
|
|
1347
|
+
// Wiping every table + lens slot leaves both derived FK caches stale (this
|
|
1348
|
+
// path fires no change event); reset so the next access rebuilds them empty.
|
|
1349
|
+
this.invalidateReverseFkIndex();
|
|
1350
|
+
this.invalidateLensFkGate();
|
|
622
1351
|
log("Cleared all schemas.");
|
|
623
1352
|
}
|
|
624
1353
|
/**
|
|
@@ -635,6 +1364,25 @@ export class SchemaManager {
|
|
|
635
1364
|
}
|
|
636
1365
|
return schema;
|
|
637
1366
|
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Canonical form of a raw (statement-supplied) schema qualifier.
|
|
1369
|
+
*
|
|
1370
|
+
* The invalidation contract this anchors: stored `schemaName` on
|
|
1371
|
+
* tables/views/MVs is canonical, and every schema-change emitter fires the
|
|
1372
|
+
* *stored* names of the object it swapped. `Statement.compile()` compares
|
|
1373
|
+
* recorded dependencies against events exactly, so a raw-cased name on
|
|
1374
|
+
* either side silently misses cached-plan invalidation.
|
|
1375
|
+
*
|
|
1376
|
+
* `Schema.name` is invariantly lowercase (every construction site
|
|
1377
|
+
* lowercases), so an existing schema canonicalizes through its Schema
|
|
1378
|
+
* object; an absent one (a plan-time reference that may resolve by run
|
|
1379
|
+
* time, a catalog import into a not-yet-created schema) folds the way the
|
|
1380
|
+
* Schema constructor would. Existence is NOT validated here — lookup sites
|
|
1381
|
+
* keep their own missing-schema handling.
|
|
1382
|
+
*/
|
|
1383
|
+
canonicalSchemaName(raw) {
|
|
1384
|
+
return this.schemas.get(raw.toLowerCase())?.name ?? raw.toLowerCase();
|
|
1385
|
+
}
|
|
638
1386
|
/**
|
|
639
1387
|
* Retrieves a table from the specified schema
|
|
640
1388
|
*
|
|
@@ -673,19 +1421,22 @@ export class SchemaManager {
|
|
|
673
1421
|
* Builds column schemas from AST column/constraint definitions,
|
|
674
1422
|
* resolving PK membership and nullability.
|
|
675
1423
|
*/
|
|
676
|
-
buildColumnSchemas(astColumns, astConstraints, defaultNotNull) {
|
|
677
|
-
const preliminaryColumnSchemas = astColumns.map(colDef => columnDefToSchema(colDef, defaultNotNull));
|
|
678
|
-
const { pkDef: pkDefinition, defaultConflict: pkDefaultConflict } = findPKDefinition(preliminaryColumnSchemas, astConstraints);
|
|
1424
|
+
buildColumnSchemas(astColumns, astConstraints, defaultNotNull, defaultCollation = 'BINARY') {
|
|
1425
|
+
const preliminaryColumnSchemas = astColumns.map(colDef => columnDefToSchema(colDef, defaultNotNull, defaultCollation));
|
|
1426
|
+
const { pkDef: pkDefinition, defaultConflict: pkDefaultConflict, synthesized } = findPKDefinition(preliminaryColumnSchemas, astConstraints);
|
|
679
1427
|
const columns = preliminaryColumnSchemas.map((col, idx) => {
|
|
680
1428
|
const isPkColumn = pkDefinition.some(pkCol => pkCol.index === idx);
|
|
681
1429
|
const pkOrder = isPkColumn
|
|
682
1430
|
? pkDefinition.findIndex(pkC => pkC.index === idx) + 1
|
|
683
1431
|
: 0;
|
|
1432
|
+
// Only an explicitly-declared PK forces NOT NULL. A synthesized
|
|
1433
|
+
// all-columns key (the no-PK fallback) leaves each column's declared
|
|
1434
|
+
// nullability intact — see findPKDefinition.
|
|
684
1435
|
return {
|
|
685
1436
|
...col,
|
|
686
1437
|
primaryKey: isPkColumn,
|
|
687
1438
|
pkOrder,
|
|
688
|
-
notNull: isPkColumn ? true : col.notNull,
|
|
1439
|
+
notNull: (isPkColumn && !synthesized) ? true : col.notNull,
|
|
689
1440
|
};
|
|
690
1441
|
});
|
|
691
1442
|
return { columns, pkDefinition, pkDefaultConflict };
|
|
@@ -746,7 +1497,7 @@ export class SchemaManager {
|
|
|
746
1497
|
name: con.name ?? `_fk_${tableName}_${colDef.name}`,
|
|
747
1498
|
columns: Object.freeze([childColIndex]),
|
|
748
1499
|
referencedTable: fk.table,
|
|
749
|
-
referencedSchema: schemaName,
|
|
1500
|
+
referencedSchema: fk.schema ?? schemaName,
|
|
750
1501
|
referencedColumns: Object.freeze([]), // resolved at enforcement time
|
|
751
1502
|
referencedColumnNames: fk.columns, // deferred resolution via resolveReferencedColumns
|
|
752
1503
|
onDelete: fk.onDelete ?? 'restrict',
|
|
@@ -757,32 +1508,11 @@ export class SchemaManager {
|
|
|
757
1508
|
}
|
|
758
1509
|
}
|
|
759
1510
|
}
|
|
760
|
-
// Table-level foreign keys
|
|
1511
|
+
// Table-level foreign keys — delegate to the shared builder so the module
|
|
1512
|
+
// `ADD CONSTRAINT` path and CREATE TABLE produce byte-identical schemas.
|
|
761
1513
|
for (const con of astConstraints ?? []) {
|
|
762
1514
|
if (con.type === 'foreignKey' && con.foreignKey && con.columns) {
|
|
763
|
-
|
|
764
|
-
const childColIndices = con.columns.map(col => {
|
|
765
|
-
const idx = columnIndexMap.get(col.name.toLowerCase());
|
|
766
|
-
if (idx === undefined) {
|
|
767
|
-
throw new QuereusError(`FK column '${col.name}' not found in table '${tableName}'`, StatusCode.ERROR);
|
|
768
|
-
}
|
|
769
|
-
return idx;
|
|
770
|
-
});
|
|
771
|
-
if (fk.columns && fk.columns.length !== childColIndices.length) {
|
|
772
|
-
throw new QuereusError(`FK constraint '${con.name ?? `_fk_${tableName}_${con.columns.map(c => c.name).join('_')}`}' on table '${tableName}': child column count (${childColIndices.length}) does not match parent column count (${fk.columns.length})`, StatusCode.ERROR);
|
|
773
|
-
}
|
|
774
|
-
result.push({
|
|
775
|
-
name: con.name ?? `_fk_${tableName}_${con.columns.map(c => c.name).join('_')}`,
|
|
776
|
-
columns: Object.freeze(childColIndices),
|
|
777
|
-
referencedTable: fk.table,
|
|
778
|
-
referencedSchema: schemaName,
|
|
779
|
-
referencedColumns: Object.freeze([]), // resolved at enforcement time
|
|
780
|
-
referencedColumnNames: fk.columns, // deferred resolution via resolveReferencedColumns
|
|
781
|
-
onDelete: fk.onDelete ?? 'restrict',
|
|
782
|
-
onUpdate: fk.onUpdate ?? 'restrict',
|
|
783
|
-
deferred: fk.initiallyDeferred ?? false,
|
|
784
|
-
tags: con.tags && Object.keys(con.tags).length > 0 ? Object.freeze({ ...con.tags }) : undefined,
|
|
785
|
-
});
|
|
1515
|
+
result.push(buildForeignKeyConstraintSchema(con, columnIndexMap, tableName, schemaName));
|
|
786
1516
|
}
|
|
787
1517
|
}
|
|
788
1518
|
return result;
|
|
@@ -809,22 +1539,11 @@ export class SchemaManager {
|
|
|
809
1539
|
}
|
|
810
1540
|
}
|
|
811
1541
|
}
|
|
812
|
-
// Table-level unique constraints
|
|
1542
|
+
// Table-level unique constraints — delegate to the shared builder (DRY with
|
|
1543
|
+
// the module `ADD CONSTRAINT` path).
|
|
813
1544
|
for (const con of astConstraints ?? []) {
|
|
814
1545
|
if (con.type === 'unique' && con.columns && con.columns.length > 0) {
|
|
815
|
-
|
|
816
|
-
const idx = columnIndexMap.get(col.name.toLowerCase());
|
|
817
|
-
if (idx === undefined) {
|
|
818
|
-
throw new QuereusError(`UNIQUE constraint column '${col.name}' not found`, StatusCode.ERROR);
|
|
819
|
-
}
|
|
820
|
-
return idx;
|
|
821
|
-
});
|
|
822
|
-
result.push({
|
|
823
|
-
name: con.name,
|
|
824
|
-
columns: Object.freeze(colIndices),
|
|
825
|
-
defaultConflict: con.onConflict,
|
|
826
|
-
tags: con.tags && Object.keys(con.tags).length > 0 ? Object.freeze({ ...con.tags }) : undefined,
|
|
827
|
-
});
|
|
1546
|
+
result.push(buildUniqueConstraintSchema(con, columnIndexMap));
|
|
828
1547
|
}
|
|
829
1548
|
}
|
|
830
1549
|
return result;
|
|
@@ -833,13 +1552,24 @@ export class SchemaManager {
|
|
|
833
1552
|
* Builds a base TableSchema from an AST CREATE TABLE statement.
|
|
834
1553
|
* Shared by both createTable (new storage) and importTable (existing storage).
|
|
835
1554
|
*/
|
|
836
|
-
buildTableSchemaFromAST(stmt, moduleName, effectiveModuleArgs, moduleInfo
|
|
837
|
-
|
|
1555
|
+
buildTableSchemaFromAST(stmt, moduleName, effectiveModuleArgs, moduleInfo,
|
|
1556
|
+
/**
|
|
1557
|
+
* Session `default_collation` for columns with no explicit COLLATE. The
|
|
1558
|
+
* caller decides: `createTable` passes the live session option (user-authored
|
|
1559
|
+
* CREATE), `importTable` passes `'BINARY'` (persisted DDL already made any
|
|
1560
|
+
* non-BINARY collation explicit, so an omitted COLLATE is canonical BINARY).
|
|
1561
|
+
*/
|
|
1562
|
+
defaultCollation = 'BINARY') {
|
|
1563
|
+
// Stored schemaName is canonical (see canonicalSchemaName); the table name
|
|
1564
|
+
// keeps its declared display casing.
|
|
1565
|
+
const targetSchemaName = stmt.table.schema
|
|
1566
|
+
? this.canonicalSchemaName(stmt.table.schema)
|
|
1567
|
+
: this.getCurrentSchemaName();
|
|
838
1568
|
const tableName = stmt.table.name;
|
|
839
1569
|
const defaultNullability = this.db.options.getStringOption('default_column_nullability');
|
|
840
1570
|
const defaultNotNull = defaultNullability === 'not_null';
|
|
841
1571
|
const astColumns = stmt.columns || [];
|
|
842
|
-
const { columns, pkDefinition, pkDefaultConflict } = this.buildColumnSchemas(astColumns, stmt.constraints, defaultNotNull);
|
|
1572
|
+
const { columns, pkDefinition, pkDefaultConflict } = this.buildColumnSchemas(astColumns, stmt.constraints, defaultNotNull, defaultCollation);
|
|
843
1573
|
const checkConstraints = this.extractCheckConstraints(astColumns, stmt.constraints);
|
|
844
1574
|
const columnIndexMap = buildColumnIndexMap(columns);
|
|
845
1575
|
const foreignKeys = this.extractForeignKeys(astColumns, stmt.constraints, columnIndexMap, tableName, targetSchemaName);
|
|
@@ -867,7 +1597,6 @@ export class SchemaManager {
|
|
|
867
1597
|
checkConstraints: Object.freeze(checkConstraints),
|
|
868
1598
|
foreignKeys: foreignKeys.length > 0 ? Object.freeze(foreignKeys) : undefined,
|
|
869
1599
|
uniqueConstraints: uniqueConstraints.length > 0 ? Object.freeze(uniqueConstraints) : undefined,
|
|
870
|
-
isTemporary: !!stmt.isTemporary,
|
|
871
1600
|
isView: false,
|
|
872
1601
|
vtabModuleName: moduleName,
|
|
873
1602
|
vtabArgs: effectiveModuleArgs,
|
|
@@ -880,6 +1609,48 @@ export class SchemaManager {
|
|
|
880
1609
|
tags: stmt.tags && Object.keys(stmt.tags).length > 0 ? Object.freeze({ ...stmt.tags }) : undefined,
|
|
881
1610
|
};
|
|
882
1611
|
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Builds a **logical** TableSchema spec from a declared CREATE TABLE AST,
|
|
1614
|
+
* for use as the `logicalTable` of a lens slot (see `schema/lens.ts`).
|
|
1615
|
+
*
|
|
1616
|
+
* Reuses the same column / PK / constraint extraction as a physical table
|
|
1617
|
+
* (so the spec is a faithful design), but carries **no** `vtabModule`
|
|
1618
|
+
* (`vtabModuleName: ''`, `isLogical: true`) — a logical table is never
|
|
1619
|
+
* registered or executed; its compiled effective body is registered as a
|
|
1620
|
+
* `ViewSchema`. Module association / indexes / storage are rejected upstream
|
|
1621
|
+
* by the lens compiler before this is called.
|
|
1622
|
+
*/
|
|
1623
|
+
buildLogicalTableSchema(stmt, schemaName) {
|
|
1624
|
+
const tableName = stmt.table.name;
|
|
1625
|
+
const defaultNullability = this.db.options.getStringOption('default_column_nullability');
|
|
1626
|
+
const defaultNotNull = defaultNullability === 'not_null';
|
|
1627
|
+
// User-authored declaration shares the CREATE surface, so honor the session default.
|
|
1628
|
+
const defaultCollation = normalizeCollationName(this.db.options.getStringOption('default_collation'));
|
|
1629
|
+
const astColumns = stmt.columns || [];
|
|
1630
|
+
const { columns, pkDefinition, pkDefaultConflict } = this.buildColumnSchemas(astColumns, stmt.constraints, defaultNotNull, defaultCollation);
|
|
1631
|
+
const checkConstraints = this.extractCheckConstraints(astColumns, stmt.constraints);
|
|
1632
|
+
const columnIndexMap = buildColumnIndexMap(columns);
|
|
1633
|
+
const foreignKeys = this.extractForeignKeys(astColumns, stmt.constraints, columnIndexMap, tableName, schemaName);
|
|
1634
|
+
const uniqueConstraints = this.extractUniqueConstraints(astColumns, stmt.constraints, columnIndexMap);
|
|
1635
|
+
return {
|
|
1636
|
+
name: tableName,
|
|
1637
|
+
schemaName,
|
|
1638
|
+
columns: Object.freeze(columns),
|
|
1639
|
+
columnIndexMap,
|
|
1640
|
+
primaryKeyDefinition: pkDefinition,
|
|
1641
|
+
primaryKeyDefaultConflict: pkDefaultConflict,
|
|
1642
|
+
checkConstraints: Object.freeze(checkConstraints),
|
|
1643
|
+
foreignKeys: foreignKeys.length > 0 ? Object.freeze(foreignKeys) : undefined,
|
|
1644
|
+
uniqueConstraints: uniqueConstraints.length > 0 ? Object.freeze(uniqueConstraints) : undefined,
|
|
1645
|
+
isView: false,
|
|
1646
|
+
isLogical: true,
|
|
1647
|
+
// Logical tables carry no module — they are a design, not storage.
|
|
1648
|
+
vtabModule: undefined,
|
|
1649
|
+
vtabModuleName: '',
|
|
1650
|
+
estimatedRows: 0,
|
|
1651
|
+
tags: stmt.tags && Object.keys(stmt.tags).length > 0 ? Object.freeze({ ...stmt.tags }) : undefined,
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
883
1654
|
/**
|
|
884
1655
|
* Walks an expression AST and rejects bind-parameter and (optionally)
|
|
885
1656
|
* column-reference nodes. Used by DDL-time DEFAULT/CHECK validators where
|
|
@@ -890,6 +1661,15 @@ export class SchemaManager {
|
|
|
890
1661
|
*/
|
|
891
1662
|
rejectIllegalReferences(expr, options) {
|
|
892
1663
|
let offendingType;
|
|
1664
|
+
// A column reference nested inside a subquery is scoped to that subquery's own
|
|
1665
|
+
// FROM, not the row being inserted, so it is not an illegal sibling-row
|
|
1666
|
+
// reference — only top-level (depth-0) columns are. This is what lets a DEFAULT
|
|
1667
|
+
// author a self-referencing allocator like
|
|
1668
|
+
// `coalesce((select max(rid) from t), 0) + mutation_ordinal()` (the
|
|
1669
|
+
// shared-key-via-default surrogate recipe — docs/view-updateability.md
|
|
1670
|
+
// § Mutation Context). Parameters stay rejected at any depth.
|
|
1671
|
+
let subqueryDepth = 0;
|
|
1672
|
+
const isQueryBoundary = (node) => node.type === 'select' || node.type === 'subquery' || node.type === 'exists';
|
|
893
1673
|
traverseAst(expr, {
|
|
894
1674
|
enterNode: (node) => {
|
|
895
1675
|
if (offendingType)
|
|
@@ -898,10 +1678,22 @@ export class SchemaManager {
|
|
|
898
1678
|
offendingType = 'parameter';
|
|
899
1679
|
return false;
|
|
900
1680
|
}
|
|
901
|
-
if (options.rejectColumns && node.type === 'column') {
|
|
902
|
-
|
|
903
|
-
|
|
1681
|
+
if (options.rejectColumns && node.type === 'column' && subqueryDepth === 0) {
|
|
1682
|
+
// `new.<column>` is an explicit, legal read of a value the INSERT
|
|
1683
|
+
// supplies for a sibling column (resolved against the row scope at
|
|
1684
|
+
// INSERT time); only a bare (unqualified) column is the illegal
|
|
1685
|
+
// sibling reference rejected here.
|
|
1686
|
+
if (node.table?.toLowerCase() !== 'new') {
|
|
1687
|
+
offendingType = 'column';
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
904
1690
|
}
|
|
1691
|
+
if (isQueryBoundary(node))
|
|
1692
|
+
subqueryDepth += 1;
|
|
1693
|
+
},
|
|
1694
|
+
exitNode: (node) => {
|
|
1695
|
+
if (isQueryBoundary(node))
|
|
1696
|
+
subqueryDepth -= 1;
|
|
905
1697
|
},
|
|
906
1698
|
});
|
|
907
1699
|
if (offendingType === 'parameter') {
|
|
@@ -925,10 +1717,45 @@ export class SchemaManager {
|
|
|
925
1717
|
* context variable, and the build attempt is permitted to fail —
|
|
926
1718
|
* scope resolution is deferred to row-time).
|
|
927
1719
|
*/
|
|
928
|
-
|
|
1720
|
+
/** True when a DEFAULT expression embeds a subquery (scalar subquery / EXISTS / SELECT). */
|
|
1721
|
+
defaultEmbedsSubquery(expr) {
|
|
1722
|
+
let found = false;
|
|
1723
|
+
traverseAst(expr, {
|
|
1724
|
+
enterNode: (node) => {
|
|
1725
|
+
if (node.type === 'select' || node.type === 'subquery' || node.type === 'exists') {
|
|
1726
|
+
found = true;
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
});
|
|
1731
|
+
return found;
|
|
1732
|
+
}
|
|
1733
|
+
/** True when a DEFAULT expression reads the row being written via `new.<column>`. */
|
|
1734
|
+
defaultReferencesNewRow(expr) {
|
|
1735
|
+
let found = false;
|
|
1736
|
+
traverseAst(expr, {
|
|
1737
|
+
enterNode: (node) => {
|
|
1738
|
+
if (found)
|
|
1739
|
+
return false;
|
|
1740
|
+
if (node.type === 'column' && node.table?.toLowerCase() === 'new') {
|
|
1741
|
+
found = true;
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
},
|
|
1745
|
+
});
|
|
1746
|
+
return found;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Build the throwaway planning context (global + parameter scope, no table/row
|
|
1750
|
+
* scope) used to compile a DEFAULT expression for DDL-time validation. The table's
|
|
1751
|
+
* columns are intentionally absent so a bare-column reference fails to build —
|
|
1752
|
+
* which the bare-column pre-walk has already rejected for the strict case, and
|
|
1753
|
+
* which the deferral path tolerates for `new.`/subquery/mutation-context defaults.
|
|
1754
|
+
*/
|
|
1755
|
+
makeDdlValidationContext() {
|
|
929
1756
|
const globalScope = new GlobalScope(this.db.schemaManager);
|
|
930
1757
|
const parameterScope = new ParameterScope(globalScope);
|
|
931
|
-
|
|
1758
|
+
return {
|
|
932
1759
|
db: this.db,
|
|
933
1760
|
schemaManager: this.db.schemaManager,
|
|
934
1761
|
parameters: {},
|
|
@@ -939,43 +1766,104 @@ export class SchemaManager {
|
|
|
939
1766
|
cteReferenceCache: new Map(),
|
|
940
1767
|
outputScopes: new Map()
|
|
941
1768
|
};
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Validate a single DEFAULT expression — the per-default core of
|
|
1772
|
+
* {@link validateDefaultDeterminism}, factored out so the ALTER COLUMN SET DEFAULT
|
|
1773
|
+
* path ({@link validateAlterColumnDefault}) routes through the identical checks:
|
|
1774
|
+
* bind parameters and (absent a mutation context) bare columns rejected up front,
|
|
1775
|
+
* non-determinism rejected unless `allowNonDeterministic`, and a `new.<column>` /
|
|
1776
|
+
* subquery / mutation-context default deferred to INSERT time (it cannot build
|
|
1777
|
+
* here without the row/table scope; determinism is re-checked when the row scope
|
|
1778
|
+
* is established).
|
|
1779
|
+
*/
|
|
1780
|
+
validateOneDefault(planningCtx, defaultValue, columnName, tableName, hasMutationContext, allowNonDeterministic, ddlPhase) {
|
|
1781
|
+
this.rejectIllegalReferences(defaultValue, {
|
|
1782
|
+
rejectColumns: !hasMutationContext,
|
|
1783
|
+
formatParamError: () => `DEFAULT for column '${columnName}' in table '${tableName}' may not reference bind parameters.`,
|
|
1784
|
+
formatColumnError: () => `DEFAULT for column '${columnName}' in table '${tableName}' may not reference a bare column; use 'new.<column>' to read a value supplied by the INSERT, or a generated column instead.`,
|
|
1785
|
+
});
|
|
1786
|
+
let defaultExpr;
|
|
1787
|
+
// A DEFAULT that embeds a subquery may forward-reference the table being
|
|
1788
|
+
// created (a self-referencing allocator — `select max(rid) from t` on `t`):
|
|
1789
|
+
// the table is not yet registered here, so the build legitimately fails.
|
|
1790
|
+
// A DEFAULT that reads `new.<column>` resolves only against the row scope
|
|
1791
|
+
// established at INSERT time, so it likewise cannot build here. Either way
|
|
1792
|
+
// determinism is re-checked at INSERT time (both the single-source insert
|
|
1793
|
+
// expansion and the shared-key envelope re-validate the compiled default),
|
|
1794
|
+
// so defer rather than reject. Other build failures (a typo'd function /
|
|
1795
|
+
// bare column) stay strict.
|
|
1796
|
+
const defaultEmbedsSubquery = this.defaultEmbedsSubquery(defaultValue);
|
|
1797
|
+
const defaultReferencesNewRow = this.defaultReferencesNewRow(defaultValue);
|
|
1798
|
+
try {
|
|
1799
|
+
defaultExpr = buildExpression(planningCtx, defaultValue);
|
|
1800
|
+
}
|
|
1801
|
+
catch (e) {
|
|
1802
|
+
if (hasMutationContext || defaultEmbedsSubquery || defaultReferencesNewRow) {
|
|
1803
|
+
// Column-style identifiers in DEFAULT may resolve to mutation
|
|
1804
|
+
// context variables at INSERT time; a subquery may forward-reference
|
|
1805
|
+
// the table being created; `new.<column>` resolves against the INSERT
|
|
1806
|
+
// row scope. The row/table scope isn't available here, so a build
|
|
1807
|
+
// failure isn't necessarily a bug. Determinism is re-checked at
|
|
1808
|
+
// INSERT time.
|
|
1809
|
+
log('Skipping determinism validation for default on column %s.%s at %s time (deferred to INSERT%s): %s', tableName, columnName, ddlPhase, hasMutationContext ? ', mutation context present' : defaultEmbedsSubquery ? ', embeds subquery' : ', references new row', e.message);
|
|
954
1810
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
// available here, so a build failure isn't necessarily a bug.
|
|
960
|
-
// Determinism is re-checked at INSERT time.
|
|
961
|
-
log('Skipping determinism validation for default on column %s.%s at CREATE TABLE time (deferred to INSERT, mutation context present): %s', tableName, col.name, e.message);
|
|
962
|
-
}
|
|
963
|
-
else {
|
|
964
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
965
|
-
const code = e instanceof QuereusError ? e.code : StatusCode.ERROR;
|
|
966
|
-
throw new QuereusError(`DEFAULT for column '${col.name}' in table '${tableName}' is invalid: ${message}`, code, e instanceof Error ? e : undefined);
|
|
967
|
-
}
|
|
1811
|
+
else {
|
|
1812
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1813
|
+
const code = e instanceof QuereusError ? e.code : StatusCode.ERROR;
|
|
1814
|
+
throw new QuereusError(`DEFAULT for column '${columnName}' in table '${tableName}' is invalid: ${message}`, code, e instanceof Error ? e : undefined);
|
|
968
1815
|
}
|
|
969
|
-
|
|
1816
|
+
}
|
|
1817
|
+
if (!defaultExpr)
|
|
1818
|
+
return;
|
|
1819
|
+
if (allowNonDeterministic)
|
|
1820
|
+
return;
|
|
1821
|
+
const result = checkDeterministic(defaultExpr);
|
|
1822
|
+
if (!result.valid) {
|
|
1823
|
+
throw new QuereusError(`Non-deterministic expression not allowed in DEFAULT for column '${columnName}' in table '${tableName}'. ` +
|
|
1824
|
+
`Expression: ${result.expression}. ` +
|
|
1825
|
+
`Use mutation context to pass non-deterministic values (e.g., WITH CONTEXT (timestamp = datetime('now'))).`, StatusCode.ERROR);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
validateDefaultDeterminism(columns, tableName, hasMutationContext, allowNonDeterministic = false) {
|
|
1829
|
+
const planningCtx = this.makeDdlValidationContext();
|
|
1830
|
+
for (const col of columns) {
|
|
1831
|
+
if (!col.defaultValue || typeof col.defaultValue !== 'object' || col.defaultValue === null || !('type' in col.defaultValue)) {
|
|
970
1832
|
continue;
|
|
971
|
-
const result = checkDeterministic(defaultExpr);
|
|
972
|
-
if (!result.valid) {
|
|
973
|
-
throw new QuereusError(`Non-deterministic expression not allowed in DEFAULT for column '${col.name}' in table '${tableName}'. ` +
|
|
974
|
-
`Expression: ${result.expression}. ` +
|
|
975
|
-
`Use mutation context to pass non-deterministic values (e.g., WITH CONTEXT (timestamp = datetime('now'))).`, StatusCode.ERROR);
|
|
976
1833
|
}
|
|
1834
|
+
this.validateOneDefault(planningCtx, col.defaultValue, col.name, tableName, hasMutationContext, allowNonDeterministic, 'CREATE TABLE');
|
|
977
1835
|
}
|
|
978
1836
|
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Validate a DEFAULT expression supplied by an `ALTER COLUMN … SET DEFAULT`,
|
|
1839
|
+
* routing it through the same checks CREATE TABLE applies so the stored default is
|
|
1840
|
+
* consistent with what INSERT will accept: bind parameters and (absent a mutation
|
|
1841
|
+
* context) bare columns are rejected, non-determinism is rejected unless the
|
|
1842
|
+
* `nondeterministic_schema` option is set, and a `new.<column>` default is accepted
|
|
1843
|
+
* with the build/determinism check deferred to INSERT time. DROP DEFAULT (a null
|
|
1844
|
+
* expression) never reaches here. Called from the ALTER TABLE runtime emitter.
|
|
1845
|
+
*/
|
|
1846
|
+
validateAlterColumnDefault(defaultExpr, columnName, tableName, hasMutationContext) {
|
|
1847
|
+
this.validateDdlDefault(defaultExpr, columnName, tableName, hasMutationContext, 'ALTER COLUMN SET DEFAULT');
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Validate a DEFAULT supplied by `ALTER TABLE ADD COLUMN`, routing it through the
|
|
1851
|
+
* same checks CREATE TABLE / ALTER COLUMN apply so the stored default is consistent
|
|
1852
|
+
* with what INSERT (and the per-row backfill) will accept: bind parameters and
|
|
1853
|
+
* (absent a mutation context) bare columns are rejected, non-determinism is rejected
|
|
1854
|
+
* unless `nondeterministic_schema` is set, and a `new.<column>` default is accepted
|
|
1855
|
+
* with its build deferred — it reads the existing row's sibling during backfill and
|
|
1856
|
+
* the INSERT-supplied sibling for future inserts. Called from the ALTER TABLE
|
|
1857
|
+
* statement builder (`buildAlterTableStmt`) at plan-build time.
|
|
1858
|
+
*/
|
|
1859
|
+
validateAddColumnDefault(defaultExpr, columnName, tableName, hasMutationContext) {
|
|
1860
|
+
this.validateDdlDefault(defaultExpr, columnName, tableName, hasMutationContext, 'ALTER TABLE ADD COLUMN');
|
|
1861
|
+
}
|
|
1862
|
+
/** Shared body for the ALTER-time DEFAULT validators (ALTER COLUMN SET DEFAULT / ADD COLUMN). */
|
|
1863
|
+
validateDdlDefault(defaultExpr, columnName, tableName, hasMutationContext, ddlPhase) {
|
|
1864
|
+
const allowNonDet = this.db.options.getBooleanOption('nondeterministic_schema');
|
|
1865
|
+
this.validateOneDefault(this.makeDdlValidationContext(), defaultExpr, columnName, tableName, hasMutationContext, allowNonDet, ddlPhase);
|
|
1866
|
+
}
|
|
979
1867
|
/**
|
|
980
1868
|
* Validates that CHECK constraint expressions don't call non-deterministic
|
|
981
1869
|
* functions and don't reference bind parameters. Walks the AST and looks
|
|
@@ -984,13 +1872,15 @@ export class SchemaManager {
|
|
|
984
1872
|
* CHECK expressions reference table columns whose scope is not yet
|
|
985
1873
|
* established at CREATE TABLE time.
|
|
986
1874
|
*/
|
|
987
|
-
validateCheckConstraintDeterminism(checkConstraints, tableName) {
|
|
1875
|
+
validateCheckConstraintDeterminism(checkConstraints, tableName, allowNonDeterministic = false) {
|
|
988
1876
|
for (const cc of checkConstraints) {
|
|
989
1877
|
const constraintName = cc.name ?? `_check_${tableName}`;
|
|
990
1878
|
this.rejectIllegalReferences(cc.expr, {
|
|
991
1879
|
rejectColumns: false,
|
|
992
1880
|
formatParamError: () => `CHECK constraint '${constraintName}' on table '${tableName}' may not reference bind parameters.`,
|
|
993
1881
|
});
|
|
1882
|
+
if (allowNonDeterministic)
|
|
1883
|
+
continue;
|
|
994
1884
|
let offendingExpr;
|
|
995
1885
|
traverseAst(cc.expr, {
|
|
996
1886
|
enterNode: (node) => {
|
|
@@ -1057,7 +1947,8 @@ export class SchemaManager {
|
|
|
1057
1947
|
if (!tableSchema) {
|
|
1058
1948
|
throw new QuereusError(`no such table: ${tableName}`, StatusCode.ERROR, undefined, stmt.table.loc?.start.line, stmt.table.loc?.start.column);
|
|
1059
1949
|
}
|
|
1060
|
-
|
|
1950
|
+
const vtabModule = requireVtabModule(tableSchema);
|
|
1951
|
+
if (!vtabModule.createIndex) {
|
|
1061
1952
|
throw new QuereusError(`Virtual table module '${tableSchema.vtabModuleName}' for table '${tableName}' does not support CREATE INDEX.`, StatusCode.ERROR, undefined, stmt.table.loc?.start.line, stmt.table.loc?.start.column);
|
|
1062
1953
|
}
|
|
1063
1954
|
const existingIndex = tableSchema.indexes?.find(idx => idx.name.toLowerCase() === indexName.toLowerCase());
|
|
@@ -1070,27 +1961,36 @@ export class SchemaManager {
|
|
|
1070
1961
|
}
|
|
1071
1962
|
const indexSchema = this.buildIndexSchema(stmt, tableSchema, tableName, indexName);
|
|
1072
1963
|
try {
|
|
1073
|
-
|
|
1964
|
+
// Module-facing stored-name contract (see canonicalSchemaName): hand the
|
|
1965
|
+
// module the resolved table's canonical schemaName and stored display
|
|
1966
|
+
// casing, never the raw `create index … on T` spelling — a module may key
|
|
1967
|
+
// storage/registries by these args verbatim. `indexSchema.name` is the
|
|
1968
|
+
// *new* index's own name (the future stored name), left as-spelled.
|
|
1969
|
+
await vtabModule.createIndex(this.db, tableSchema.schemaName, tableSchema.name, indexSchema);
|
|
1074
1970
|
}
|
|
1075
1971
|
catch (e) {
|
|
1076
1972
|
const message = e instanceof Error ? e.message : String(e);
|
|
1077
1973
|
const code = e instanceof QuereusError ? e.code : StatusCode.ERROR;
|
|
1078
1974
|
throw new QuereusError(`createIndex failed for index '${indexName}' on table '${tableName}': ${message}`, code, e instanceof Error ? e : undefined, stmt.loc?.start.line, stmt.loc?.start.column);
|
|
1079
1975
|
}
|
|
1080
|
-
const updatedTableSchema =
|
|
1976
|
+
const updatedTableSchema = appendIndexToTableSchema(tableSchema, indexSchema);
|
|
1081
1977
|
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
1082
1978
|
schema.addTable(updatedTableSchema);
|
|
1083
1979
|
this.changeNotifier.notifyChange({
|
|
1084
1980
|
type: 'table_modified',
|
|
1085
|
-
|
|
1086
|
-
|
|
1981
|
+
// Stored names of the swapped table, not the raw statement spelling — see
|
|
1982
|
+
// canonicalSchemaName for the emitter/stored-name invariant. A raw
|
|
1983
|
+
// `create index … on T` (stored `t`) or an unqualified CREATE INDEX against
|
|
1984
|
+
// a `MAIN.`-created table would otherwise miss the cached plan's table dep.
|
|
1985
|
+
schemaName: updatedTableSchema.schemaName,
|
|
1986
|
+
objectName: updatedTableSchema.name,
|
|
1087
1987
|
oldObject: tableSchema,
|
|
1088
1988
|
newObject: updatedTableSchema
|
|
1089
1989
|
});
|
|
1090
1990
|
this.emitAutoSchemaEventIfNeeded(tableSchema.vtabModuleName, {
|
|
1091
1991
|
type: 'create',
|
|
1092
1992
|
objectType: 'index',
|
|
1093
|
-
schemaName:
|
|
1993
|
+
schemaName: updatedTableSchema.schemaName,
|
|
1094
1994
|
objectName: indexName,
|
|
1095
1995
|
});
|
|
1096
1996
|
log(`Successfully created index %s on table %s.%s`, indexName, targetSchemaName, tableName);
|
|
@@ -1100,12 +2000,13 @@ export class SchemaManager {
|
|
|
1100
2000
|
*/
|
|
1101
2001
|
buildIndexSchema(stmt, tableSchema, tableName, indexName) {
|
|
1102
2002
|
const indexColumns = stmt.columns.map((indexedCol) => {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
1106
|
-
|
|
2003
|
+
// The parser folds `col COLLATE x` into a collate expression over a bare
|
|
2004
|
+
// column reference; resolveImportedIndexColumn unwraps that form to a
|
|
2005
|
+
// { name, collation } pair, mirroring importIndex. A genuine expression
|
|
2006
|
+
// index (non-column operand) resolves to an unset name and is rejected.
|
|
2007
|
+
const { name: colName, collation } = resolveImportedIndexColumn(indexedCol);
|
|
1107
2008
|
if (!colName) {
|
|
1108
|
-
throw new QuereusError(`
|
|
2009
|
+
throw new QuereusError(`Indices on expressions are not supported yet.`, StatusCode.ERROR, undefined, indexedCol.expr?.loc?.start.line, indexedCol.expr?.loc?.start.column);
|
|
1109
2010
|
}
|
|
1110
2011
|
const tableColIndex = tableSchema.columnIndexMap.get(colName.toLowerCase());
|
|
1111
2012
|
if (tableColIndex === undefined) {
|
|
@@ -1115,7 +2016,7 @@ export class SchemaManager {
|
|
|
1115
2016
|
return {
|
|
1116
2017
|
index: tableColIndex,
|
|
1117
2018
|
desc: indexedCol.direction === 'desc',
|
|
1118
|
-
collation:
|
|
2019
|
+
collation: normalizeCollationName(collation || tableColSchema.collation || 'BINARY')
|
|
1119
2020
|
};
|
|
1120
2021
|
});
|
|
1121
2022
|
return {
|
|
@@ -1126,29 +2027,6 @@ export class SchemaManager {
|
|
|
1126
2027
|
tags: stmt.tags && Object.keys(stmt.tags).length > 0 ? Object.freeze({ ...stmt.tags }) : undefined,
|
|
1127
2028
|
};
|
|
1128
2029
|
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Returns a new TableSchema with the given index appended. If the index is
|
|
1131
|
-
* unique, also adds a matching uniqueConstraint so the mutation manager
|
|
1132
|
-
* enforces uniqueness on insert/update through its existing checks.
|
|
1133
|
-
*/
|
|
1134
|
-
addIndexToTableSchema(tableSchema, indexSchema) {
|
|
1135
|
-
const updatedIndexes = [...(tableSchema.indexes || []), indexSchema];
|
|
1136
|
-
const result = {
|
|
1137
|
-
...tableSchema,
|
|
1138
|
-
indexes: Object.freeze(updatedIndexes),
|
|
1139
|
-
};
|
|
1140
|
-
if (indexSchema.unique) {
|
|
1141
|
-
const newConstraint = {
|
|
1142
|
-
name: indexSchema.name,
|
|
1143
|
-
columns: Object.freeze(indexSchema.columns.map(c => c.index)),
|
|
1144
|
-
predicate: indexSchema.predicate,
|
|
1145
|
-
derivedFromIndex: indexSchema.name,
|
|
1146
|
-
};
|
|
1147
|
-
const updatedConstraints = [...(tableSchema.uniqueConstraints ?? []), newConstraint];
|
|
1148
|
-
result.uniqueConstraints = Object.freeze(updatedConstraints);
|
|
1149
|
-
}
|
|
1150
|
-
return result;
|
|
1151
|
-
}
|
|
1152
2030
|
/**
|
|
1153
2031
|
* Drops a secondary index from the table that owns it.
|
|
1154
2032
|
* Searches all tables in the target schema to find the owning table.
|
|
@@ -1180,11 +2058,18 @@ export class SchemaManager {
|
|
|
1180
2058
|
}
|
|
1181
2059
|
throw new QuereusError(`no such index: ${indexName}`, StatusCode.ERROR);
|
|
1182
2060
|
}
|
|
2061
|
+
// Stored display casing of the dropped index — the raw `indexName` arg may
|
|
2062
|
+
// differ in case. Computed *before* the module call so module.dropIndex
|
|
2063
|
+
// receives the stored name (the module-facing stored-name contract; see
|
|
2064
|
+
// canonicalSchemaName), keeping a case-divergent `DROP INDEX iDx` from
|
|
2065
|
+
// missing a module handle/registry keyed by the stored `idx` (e.g. the
|
|
2066
|
+
// store's StoreTable.indexStores cache). Also reused by the events below.
|
|
2067
|
+
const storedIndexName = ownerTable.indexes.find(idx => idx.name.toLowerCase() === lowerIndexName).name;
|
|
1183
2068
|
// Call module.dropIndex if the module supports it
|
|
1184
2069
|
const moduleReg = ownerTable.vtabModuleName ? this.getModule(ownerTable.vtabModuleName) : undefined;
|
|
1185
2070
|
if (moduleReg?.module?.dropIndex) {
|
|
1186
2071
|
try {
|
|
1187
|
-
await moduleReg.module.dropIndex(this.db, schemaName, ownerTable.name,
|
|
2072
|
+
await moduleReg.module.dropIndex(this.db, ownerTable.schemaName, ownerTable.name, storedIndexName);
|
|
1188
2073
|
}
|
|
1189
2074
|
catch (e) {
|
|
1190
2075
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -1193,7 +2078,7 @@ export class SchemaManager {
|
|
|
1193
2078
|
}
|
|
1194
2079
|
}
|
|
1195
2080
|
// Remove the index from the table schema, along with any uniqueConstraint
|
|
1196
|
-
// that was synthesized from this index (see
|
|
2081
|
+
// that was synthesized from this index (see appendIndexToTableSchema).
|
|
1197
2082
|
const updatedIndexes = (ownerTable.indexes || []).filter(idx => idx.name.toLowerCase() !== lowerIndexName);
|
|
1198
2083
|
const updatedUniqueConstraints = (ownerTable.uniqueConstraints ?? []).filter(uc => uc.derivedFromIndex?.toLowerCase() !== lowerIndexName);
|
|
1199
2084
|
const updatedTableSchema = {
|
|
@@ -1206,7 +2091,9 @@ export class SchemaManager {
|
|
|
1206
2091
|
schema.addTable(updatedTableSchema);
|
|
1207
2092
|
this.changeNotifier.notifyChange({
|
|
1208
2093
|
type: 'table_modified',
|
|
1209
|
-
|
|
2094
|
+
// Stored names of the swapped table, not the raw drop args — see
|
|
2095
|
+
// canonicalSchemaName for the emitter/stored-name invariant.
|
|
2096
|
+
schemaName: ownerTable.schemaName,
|
|
1210
2097
|
objectName: ownerTable.name,
|
|
1211
2098
|
oldObject: ownerTable,
|
|
1212
2099
|
newObject: updatedTableSchema
|
|
@@ -1214,18 +2101,19 @@ export class SchemaManager {
|
|
|
1214
2101
|
this.emitAutoSchemaEventIfNeeded(ownerTable.vtabModuleName, {
|
|
1215
2102
|
type: 'drop',
|
|
1216
2103
|
objectType: 'index',
|
|
1217
|
-
schemaName,
|
|
1218
|
-
objectName:
|
|
2104
|
+
schemaName: ownerTable.schemaName,
|
|
2105
|
+
objectName: storedIndexName,
|
|
1219
2106
|
});
|
|
1220
2107
|
log(`Successfully dropped index %s from table %s.%s`, indexName, schemaName, ownerTable.name);
|
|
1221
2108
|
}
|
|
1222
2109
|
/**
|
|
1223
2110
|
* Emits an auto schema event for modules that don't have native event support,
|
|
1224
|
-
* if
|
|
2111
|
+
* if the engine needs schema events — i.e. any `onSchemaChange` or
|
|
2112
|
+
* `onTransactionCommit` listener is registered (see `Database._needsSchemaEvents`).
|
|
1225
2113
|
*/
|
|
1226
2114
|
emitAutoSchemaEventIfNeeded(moduleName, event) {
|
|
1227
2115
|
const moduleReg = moduleName ? this.getModule(moduleName) : undefined;
|
|
1228
|
-
if (this.db.
|
|
2116
|
+
if (this.db._needsSchemaEvents() && !hasNativeEventSupport(moduleReg?.module)) {
|
|
1229
2117
|
this.db._getEventEmitter().emitAutoSchemaEvent(moduleName ?? 'memory', event);
|
|
1230
2118
|
}
|
|
1231
2119
|
}
|
|
@@ -1237,7 +2125,36 @@ export class SchemaManager {
|
|
|
1237
2125
|
* @returns A Promise that resolves to the created TableSchema.
|
|
1238
2126
|
* @throws QuereusError on errors (e.g., module not found, create fails, table exists).
|
|
1239
2127
|
*/
|
|
1240
|
-
|
|
2128
|
+
/**
|
|
2129
|
+
* Builds the {@link TableSchema} a CREATE TABLE statement WOULD register,
|
|
2130
|
+
* without touching the module or the catalog. Resolution matches
|
|
2131
|
+
* {@link createTable} exactly (same module-info resolution, same live-session
|
|
2132
|
+
* default collation), so the result is byte-for-byte what registration would
|
|
2133
|
+
* produce. Used by the maintained-table create path
|
|
2134
|
+
* (`createMaintainedTable`) to verify the declared shape against the
|
|
2135
|
+
* derivation body BEFORE any catalog registration — the all-or-nothing
|
|
2136
|
+
* posture of `create table … maintained as`.
|
|
2137
|
+
*/
|
|
2138
|
+
buildDeclaredTableSchema(stmt) {
|
|
2139
|
+
const { moduleName, effectiveModuleArgs, moduleInfo } = this.resolveModuleInfo(stmt);
|
|
2140
|
+
const defaultCollation = normalizeCollationName(this.db.options.getStringOption('default_collation'));
|
|
2141
|
+
return this.buildTableSchemaFromAST(stmt, moduleName, effectiveModuleArgs, moduleInfo, defaultCollation);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* `preferBacking` routes the module instantiation through the durable backing
|
|
2145
|
+
* seam — `createBacking?() ?? create()`, the SAME preference
|
|
2146
|
+
* {@link createBackingTable} uses — instead of `module.create` directly. It is
|
|
2147
|
+
* set ONLY by the maintained-table create path (`createMaintainedTable`), so a
|
|
2148
|
+
* durable-backing module (e.g. lamina) builds the basis `RowStore` that
|
|
2149
|
+
* `getBackingHost` later resolves for row-time maintenance; without it the table
|
|
2150
|
+
* is an ordinary relational collection with no basis store, and the maintained
|
|
2151
|
+
* fill throws `backing host not found`. An ordinary user CREATE leaves it false
|
|
2152
|
+
* and stays byte-for-byte on `module.create`. A module without `createBacking`
|
|
2153
|
+
* (e.g. memory) falls through to `create` regardless, so the flag is a no-op
|
|
2154
|
+
* there. Every gate above (determinism, FK-collation) runs identically in both
|
|
2155
|
+
* modes — the flag only selects the factory method.
|
|
2156
|
+
*/
|
|
2157
|
+
async createTable(stmt, preferBacking = false) {
|
|
1241
2158
|
const targetSchemaName = stmt.table.schema || this.getCurrentSchemaName();
|
|
1242
2159
|
const tableName = stmt.table.name;
|
|
1243
2160
|
const schema = this.getSchema(targetSchemaName);
|
|
@@ -1265,13 +2182,32 @@ export class SchemaManager {
|
|
|
1265
2182
|
throw new QuereusError(`${itemType} ${targetSchemaName}.${tableName} already exists`, StatusCode.CONSTRAINT, undefined, stmt.table.loc?.start.line, stmt.table.loc?.start.column);
|
|
1266
2183
|
}
|
|
1267
2184
|
const { moduleName, effectiveModuleArgs, moduleInfo } = this.resolveModuleInfo(stmt);
|
|
1268
|
-
|
|
2185
|
+
// User-authored CREATE: omitted-COLLATE columns resolve under the live session default.
|
|
2186
|
+
const defaultCollation = normalizeCollationName(this.db.options.getStringOption('default_collation'));
|
|
2187
|
+
const baseTableSchema = this.buildTableSchemaFromAST(stmt, moduleName, effectiveModuleArgs, moduleInfo, defaultCollation);
|
|
1269
2188
|
const hasMutationContext = !!baseTableSchema.mutationContext && baseTableSchema.mutationContext.length > 0;
|
|
1270
|
-
|
|
1271
|
-
|
|
2189
|
+
// `nondeterministic_schema = true` lifts the strict-rejection gate at CREATE TABLE.
|
|
2190
|
+
// The captured artifact at the vtab.update() frontier is fully resolved per row, so
|
|
2191
|
+
// defaults / CHECKs / generated columns containing non-determinism remain replay-safe
|
|
2192
|
+
// (see docs/architecture.md § Constraints and docs/module-authoring.md § Mutation Statements).
|
|
2193
|
+
// The bind-parameter / column-reference pre-walks inside the validators still run
|
|
2194
|
+
// in both modes — those are scope checks, not determinism checks.
|
|
2195
|
+
const allowNonDet = this.db.options.getBooleanOption('nondeterministic_schema');
|
|
2196
|
+
this.validateDefaultDeterminism(baseTableSchema.columns, tableName, hasMutationContext, allowNonDet);
|
|
2197
|
+
this.validateCheckConstraintDeterminism(baseTableSchema.checkConstraints, tableName, allowNonDet);
|
|
1272
2198
|
let tableInstance;
|
|
1273
2199
|
try {
|
|
1274
|
-
|
|
2200
|
+
// `preferBacking` (set only by the maintained-table create path) routes
|
|
2201
|
+
// through the durable backing seam — the SAME `createBacking?() ?? create()`
|
|
2202
|
+
// preference {@link createBackingTable} uses — so a durable-backing module
|
|
2203
|
+
// builds the basis `RowStore` that `getBackingHost` later resolves for
|
|
2204
|
+
// row-time maintenance. An ordinary user CREATE leaves it false and stays
|
|
2205
|
+
// byte-for-byte on `module.create`; a module without `createBacking` (memory)
|
|
2206
|
+
// falls through to `create` regardless.
|
|
2207
|
+
const create = preferBacking
|
|
2208
|
+
? (moduleInfo.module.createBacking?.bind(moduleInfo.module) ?? moduleInfo.module.create.bind(moduleInfo.module))
|
|
2209
|
+
: moduleInfo.module.create.bind(moduleInfo.module);
|
|
2210
|
+
tableInstance = await create(this.db, baseTableSchema);
|
|
1275
2211
|
}
|
|
1276
2212
|
catch (e) {
|
|
1277
2213
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -1279,8 +2215,81 @@ export class SchemaManager {
|
|
|
1279
2215
|
throw new QuereusError(`Module '${moduleName}' create failed for table '${tableName}': ${message}`, code, e instanceof Error ? e : undefined, stmt.loc?.start.line, stmt.loc?.start.column);
|
|
1280
2216
|
}
|
|
1281
2217
|
const completeTableSchema = this.finalizeCreatedTableSchema(tableInstance, tableName, targetSchemaName, moduleName, effectiveModuleArgs, moduleInfo);
|
|
2218
|
+
// Reject a FK whose child/parent column collations declare a same-rank conflict
|
|
2219
|
+
// — the same conflict FK enforcement raises at first DML, surfaced here at CREATE.
|
|
2220
|
+
// `completeTableSchema` is post-reconcile (the store's `reconcilePkCollations` ran
|
|
2221
|
+
// inside `module.create`), so an implicit-default text PK reconciled to the store's
|
|
2222
|
+
// NOCASE keeps `collationExplicit` unset → contributes the engine floor, never a
|
|
2223
|
+
// false conflict. Done BEFORE `addTable` so a conflicting FK leaves the catalog
|
|
2224
|
+
// clean (a self-ref FK resolves against `completeTableSchema` itself). Deliberately
|
|
2225
|
+
// only here — never in `buildTableSchemaFromAST` or the import/rehydrate path, so a
|
|
2226
|
+
// legacy persisted conflicting FK reloads fine and still surfaces at DML.
|
|
2227
|
+
for (const fk of completeTableSchema.foreignKeys ?? []) {
|
|
2228
|
+
validateForeignKeyCollations(this.db, completeTableSchema, fk);
|
|
2229
|
+
}
|
|
1282
2230
|
schema.addTable(completeTableSchema);
|
|
1283
2231
|
log(`Successfully created table %s.%s using module %s`, targetSchemaName, tableName, moduleName);
|
|
2232
|
+
this.changeNotifier.notifyChange({
|
|
2233
|
+
type: 'table_added',
|
|
2234
|
+
// Stored names of the registered table — see canonicalSchemaName for the
|
|
2235
|
+
// emitter/stored-name invariant.
|
|
2236
|
+
schemaName: completeTableSchema.schemaName,
|
|
2237
|
+
objectName: completeTableSchema.name,
|
|
2238
|
+
newObject: completeTableSchema
|
|
2239
|
+
});
|
|
2240
|
+
this.emitAutoSchemaEventIfNeeded(moduleName, {
|
|
2241
|
+
type: 'create',
|
|
2242
|
+
objectType: 'table',
|
|
2243
|
+
schemaName: completeTableSchema.schemaName,
|
|
2244
|
+
objectName: completeTableSchema.name,
|
|
2245
|
+
});
|
|
2246
|
+
return completeTableSchema;
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Creates a backing table from a pre-built `TableSchema` rather than a
|
|
2250
|
+
* CREATE TABLE AST. Used by materialized views, whose backing-table columns
|
|
2251
|
+
* and primary key are derived from the optimized body relation (carrying
|
|
2252
|
+
* full {@link import('../common/datatype.js').ScalarType} fidelity that a
|
|
2253
|
+
* round-trip through SQL type strings would lose).
|
|
2254
|
+
*
|
|
2255
|
+
* Reuses the same internal sequence as {@link createTable} —
|
|
2256
|
+
* `finalizeCreatedTableSchema` → `addTable` → `table_added` notify — so the
|
|
2257
|
+
* backing table behaves like any other table, except the instance is built via
|
|
2258
|
+
* the module's optional `createBacking` when present (`createBacking?() ??
|
|
2259
|
+
* create()`): a durable-backing module routes the backing into its durable
|
|
2260
|
+
* store there; modules without it fall through to `create` (today's behavior).
|
|
2261
|
+
* The supplied schema must carry `vtabModule`/`vtabModuleName` (typically
|
|
2262
|
+
* `memory`).
|
|
2263
|
+
*/
|
|
2264
|
+
async createBackingTable(tableSchema) {
|
|
2265
|
+
const targetSchemaName = tableSchema.schemaName;
|
|
2266
|
+
const tableName = tableSchema.name;
|
|
2267
|
+
const schema = this.getSchema(targetSchemaName);
|
|
2268
|
+
if (!schema) {
|
|
2269
|
+
throw new QuereusError(`Internal error: Schema '${targetSchemaName}' not found.`, StatusCode.INTERNAL);
|
|
2270
|
+
}
|
|
2271
|
+
if (schema.getTable(tableName) || schema.getView(tableName)) {
|
|
2272
|
+
throw new QuereusError(`Backing table ${targetSchemaName}.${tableName} already exists`, StatusCode.CONSTRAINT);
|
|
2273
|
+
}
|
|
2274
|
+
const moduleName = tableSchema.vtabModuleName;
|
|
2275
|
+
const moduleInfo = this.getModule(moduleName);
|
|
2276
|
+
if (!moduleInfo || !moduleInfo.module) {
|
|
2277
|
+
throw new QuereusError(`No virtual table module named '${moduleName}'`, StatusCode.ERROR);
|
|
2278
|
+
}
|
|
2279
|
+
let tableInstance;
|
|
2280
|
+
try {
|
|
2281
|
+
const create = moduleInfo.module.createBacking?.bind(moduleInfo.module)
|
|
2282
|
+
?? moduleInfo.module.create.bind(moduleInfo.module);
|
|
2283
|
+
tableInstance = await create(this.db, tableSchema);
|
|
2284
|
+
}
|
|
2285
|
+
catch (e) {
|
|
2286
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
2287
|
+
const code = e instanceof QuereusError ? e.code : StatusCode.ERROR;
|
|
2288
|
+
throw new QuereusError(`Module '${moduleName}' backing create failed for '${tableName}': ${message}`, code, e instanceof Error ? e : undefined);
|
|
2289
|
+
}
|
|
2290
|
+
const completeTableSchema = this.finalizeCreatedTableSchema(tableInstance, tableName, targetSchemaName, moduleName, tableSchema.vtabArgs ?? {}, moduleInfo);
|
|
2291
|
+
schema.addTable(completeTableSchema);
|
|
2292
|
+
log(`Successfully created backing table %s.%s using module %s`, targetSchemaName, tableName, moduleName);
|
|
1284
2293
|
this.changeNotifier.notifyChange({
|
|
1285
2294
|
type: 'table_added',
|
|
1286
2295
|
schemaName: targetSchemaName,
|
|
@@ -1299,25 +2308,46 @@ export class SchemaManager {
|
|
|
1299
2308
|
* Import catalog objects from DDL statements without triggering storage creation.
|
|
1300
2309
|
* Used when connecting to existing storage that already contains data.
|
|
1301
2310
|
*
|
|
2311
|
+
* Options enable the materialized-view **adopt-without-refill fast path**
|
|
2312
|
+
* (see {@link importMaterializedView}); omitted, every MV refills — the
|
|
2313
|
+
* always-correct default.
|
|
2314
|
+
*
|
|
1302
2315
|
* This method:
|
|
1303
2316
|
* 1. Parses each DDL statement
|
|
1304
|
-
* 2. Registers the schema objects (tables, indexes)
|
|
1305
|
-
* 3. Calls module.connect() instead of module.create()
|
|
2317
|
+
* 2. Registers the schema objects (tables, indexes, views, materialized views)
|
|
2318
|
+
* 3. Calls module.connect() instead of module.create() for tables (a
|
|
2319
|
+
* materialized view's memory backing is re-materialized instead — see
|
|
2320
|
+
* {@link importMaterializedView})
|
|
1306
2321
|
* 4. Skips schema change hooks (since these are existing objects)
|
|
1307
2322
|
*
|
|
1308
|
-
*
|
|
2323
|
+
* Each DDL string may hold **one or more** statements: a catalog entry can
|
|
2324
|
+
* bundle a `CREATE TABLE` immediately followed by the `CREATE INDEX`es that
|
|
2325
|
+
* belong to it. Statements within an entry are imported in document order, so
|
|
2326
|
+
* a table always precedes the indexes that reference it. (Because every
|
|
2327
|
+
* table's indexes are co-located with it in one entry, no global
|
|
2328
|
+
* table-before-index ordering across entries is required.)
|
|
2329
|
+
*
|
|
2330
|
+
* @param ddlStatements Array of DDL strings (each one or more CREATE TABLE / CREATE INDEX, etc.)
|
|
2331
|
+
* @param options Adopt-fast-path options for materialized views — see {@link ImportCatalogOptions}
|
|
1309
2332
|
* @returns Array of imported object names
|
|
1310
2333
|
*/
|
|
1311
|
-
async importCatalog(ddlStatements) {
|
|
1312
|
-
const imported = { tables: [], indexes: [] };
|
|
2334
|
+
async importCatalog(ddlStatements, options) {
|
|
2335
|
+
const imported = { tables: [], indexes: [], views: [], materializedViews: [] };
|
|
1313
2336
|
for (const ddl of ddlStatements) {
|
|
1314
2337
|
try {
|
|
1315
|
-
const result
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
2338
|
+
for (const result of await this.importDDL(ddl, options)) {
|
|
2339
|
+
if (result.type === 'table') {
|
|
2340
|
+
imported.tables.push(result.name);
|
|
2341
|
+
}
|
|
2342
|
+
else if (result.type === 'index') {
|
|
2343
|
+
imported.indexes.push(result.name);
|
|
2344
|
+
}
|
|
2345
|
+
else if (result.type === 'view') {
|
|
2346
|
+
imported.views.push(result.name);
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
imported.materializedViews.push(result.name);
|
|
2350
|
+
}
|
|
1321
2351
|
}
|
|
1322
2352
|
}
|
|
1323
2353
|
catch (e) {
|
|
@@ -1326,26 +2356,285 @@ export class SchemaManager {
|
|
|
1326
2356
|
throw e;
|
|
1327
2357
|
}
|
|
1328
2358
|
}
|
|
1329
|
-
log('Imported catalog: %d tables, %d indexes', imported.tables.length, imported.indexes.length);
|
|
2359
|
+
log('Imported catalog: %d tables, %d indexes, %d views, %d materialized views', imported.tables.length, imported.indexes.length, imported.views.length, imported.materializedViews.length);
|
|
1330
2360
|
return imported;
|
|
1331
2361
|
}
|
|
1332
2362
|
/**
|
|
1333
|
-
* Import a
|
|
2363
|
+
* Import every statement in a DDL string without creating storage, in document
|
|
2364
|
+
* order. A single string may carry several statements (a table bundled with
|
|
2365
|
+
* its indexes); single-statement entries remain valid. An `alter index … tags`
|
|
2366
|
+
* statement (the store bundle's vehicle for exposed-implicit-index user tags)
|
|
2367
|
+
* applies silently against the just-imported table and contributes no result
|
|
2368
|
+
* entry — it modifies an existing object rather than importing one. Any other
|
|
2369
|
+
* unsupported statement type throws — `rehydrateCatalog` relies on this
|
|
2370
|
+
* fail-loud contract to record import errors rather than silently dropping
|
|
2371
|
+
* objects.
|
|
1334
2372
|
*/
|
|
1335
|
-
async
|
|
2373
|
+
async importDDL(ddl, options) {
|
|
1336
2374
|
const parser = new Parser();
|
|
1337
2375
|
const statements = parser.parseAll(ddl);
|
|
1338
|
-
|
|
1339
|
-
|
|
2376
|
+
const results = [];
|
|
2377
|
+
for (const stmt of statements) {
|
|
2378
|
+
if (stmt.type === 'createTable') {
|
|
2379
|
+
const createTable = stmt;
|
|
2380
|
+
// Unified model: a maintained table persists/exports as the canonical
|
|
2381
|
+
// `create table … maintained as <body>` form (see generateMaintainedTableDDL).
|
|
2382
|
+
// That re-parses as a createTable carrying a `maintained` clause, so route
|
|
2383
|
+
// it through the same re-materialize/adopt path as the MV sugar — the plain
|
|
2384
|
+
// `importTable` connect path would try (and fail) to reconnect an ephemeral
|
|
2385
|
+
// backing that was never persisted.
|
|
2386
|
+
if (createTable.maintained) {
|
|
2387
|
+
results.push(await this.importMaterializedView(maintainedImportFromTableStmt(createTable), options));
|
|
2388
|
+
}
|
|
2389
|
+
else {
|
|
2390
|
+
results.push(await this.importTable(createTable));
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
else if (stmt.type === 'createIndex') {
|
|
2394
|
+
results.push(await this.importIndex(stmt));
|
|
2395
|
+
}
|
|
2396
|
+
else if (stmt.type === 'createView') {
|
|
2397
|
+
// Plain views import silently (body planning deferred to first reference).
|
|
2398
|
+
results.push(this.importView(stmt));
|
|
2399
|
+
}
|
|
2400
|
+
else if (stmt.type === 'createMaterializedView') {
|
|
2401
|
+
// Materialized views re-materialize (backing rebuilt from current source
|
|
2402
|
+
// data, row-time maintenance re-registered) — silent like the other arms.
|
|
2403
|
+
// Under `options.trustBackings` a pre-existing durable backing that
|
|
2404
|
+
// passes every adopt gate is adopted without the refill.
|
|
2405
|
+
results.push(await this.importMaterializedView(maintainedImportFromMvStmt(stmt), options));
|
|
2406
|
+
}
|
|
2407
|
+
else if (stmt.type === 'alterIndex') {
|
|
2408
|
+
// Tag re-application on an already-imported object — no result entry.
|
|
2409
|
+
this.applyImportedIndexTags(stmt);
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
throw new QuereusError(`importCatalog does not support statement type: ${stmt.type}`, StatusCode.ERROR);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return results;
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Apply an `alter index … {set|add|drop} tags` statement during catalog import,
|
|
2419
|
+
* **silently** — no `notifyChange`, mirroring {@link importTable}/{@link importIndex}
|
|
2420
|
+
* (a store rehydrating its own catalog must not re-emit persistence events).
|
|
2421
|
+
* Shares {@link resolveIndexTagSwap} with the live {@link updateIndexTags} path,
|
|
2422
|
+
* so the bundle's statement resolves exactly as a user-issued ALTER would —
|
|
2423
|
+
* materialized `IndexSchema` first, then the exposed-implicit-constraint
|
|
2424
|
+
* fallback (the store-bundle case: the `alter index` line follows its
|
|
2425
|
+
* `CREATE TABLE` in the same entry, so the constraint is already registered).
|
|
2426
|
+
* All three action forms map through the shared `freezeTags`/`mutateTagRecord`
|
|
2427
|
+
* helpers, though the bundle generator only emits the whole-set replace form.
|
|
2428
|
+
* An unresolvable target throws NOTFOUND — the bundle and its alter line come
|
|
2429
|
+
* from one `TableSchema` snapshot, so a miss indicates real corruption, which
|
|
2430
|
+
* `rehydrateCatalog` records per-entry.
|
|
2431
|
+
*/
|
|
2432
|
+
applyImportedIndexTags(stmt) {
|
|
2433
|
+
const targetSchemaName = stmt.name.schema ?? this.getCurrentSchemaName();
|
|
2434
|
+
const action = stmt.action;
|
|
2435
|
+
let compute;
|
|
2436
|
+
if (action.type === 'dropTags') {
|
|
2437
|
+
compute = current => this.mutateTagRecord(current, { op: 'drop', keys: action.keys });
|
|
2438
|
+
}
|
|
2439
|
+
else if (action.mode === 'merge') {
|
|
2440
|
+
compute = current => this.mutateTagRecord(current, { op: 'merge', tags: action.tags });
|
|
1340
2441
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
2442
|
+
else {
|
|
2443
|
+
compute = () => this.freezeTags(action.tags);
|
|
2444
|
+
}
|
|
2445
|
+
const { newSchema } = this.resolveIndexTagSwap(targetSchemaName, stmt.name.name, compute);
|
|
2446
|
+
this.getSchemaOrFail(targetSchemaName).addTable(newSchema);
|
|
2447
|
+
log(`Applied imported index tags for %s in schema %s`, stmt.name.name, targetSchemaName);
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Import a plain view from its parsed DDL **without planning the body**.
|
|
2451
|
+
* Registration is silent — no `notifyChange` fires, mirroring
|
|
2452
|
+
* {@link importTable}/{@link importIndex} (a store rehydrating its own catalog
|
|
2453
|
+
* must not re-emit persistence events). Body validation is deferred to first
|
|
2454
|
+
* reference, exactly as {@link importTable} defers create-time work via
|
|
2455
|
+
* `connect`: this makes view rehydration order-independent — a view over
|
|
2456
|
+
* another view, a materialized view, or a not-yet-imported relation registers
|
|
2457
|
+
* regardless of phase order, and a broken body surfaces only when queried.
|
|
2458
|
+
*
|
|
2459
|
+
* The stored `sql` is the canonical {@link createViewToString} rendering (not
|
|
2460
|
+
* the raw entry text, which may bundle several statements). Synchronous: unlike
|
|
2461
|
+
* table/index import there is no module storage to bind, so there is nothing to
|
|
2462
|
+
* await.
|
|
2463
|
+
*/
|
|
2464
|
+
importView(stmt) {
|
|
2465
|
+
// Create the schema if absent (mirrors importTable) so a view rehydrates
|
|
2466
|
+
// even into a schema that holds no tables; its `.name` is the canonical
|
|
2467
|
+
// stored schemaName (see canonicalSchemaName).
|
|
2468
|
+
const schema = this.getOrCreateSchema(stmt.view.schema || this.getCurrentSchemaName());
|
|
2469
|
+
const targetSchemaName = schema.name;
|
|
2470
|
+
const viewName = stmt.view.name;
|
|
2471
|
+
const viewSchema = {
|
|
2472
|
+
name: viewName,
|
|
2473
|
+
schemaName: targetSchemaName,
|
|
2474
|
+
sql: createViewToString(stmt),
|
|
2475
|
+
// Any `with defaults (…)` rides inside stmt.select (→ selectAst).
|
|
2476
|
+
selectAst: stmt.select,
|
|
2477
|
+
columns: stmt.columns ? Object.freeze([...stmt.columns]) : undefined,
|
|
2478
|
+
tags: stmt.tags && Object.keys(stmt.tags).length > 0 ? Object.freeze({ ...stmt.tags }) : undefined,
|
|
2479
|
+
};
|
|
2480
|
+
schema.addView(viewSchema);
|
|
2481
|
+
log(`Imported view %s.%s`, targetSchemaName, viewName);
|
|
2482
|
+
return { type: 'view', name: `${targetSchemaName}.${viewName}` };
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Import a materialized view from its parsed DDL by re-materializing it
|
|
2486
|
+
* through the shared {@link materializeView} core: the body is re-planned
|
|
2487
|
+
* against the current (already-imported) sources, the memory backing table is
|
|
2488
|
+
* rebuilt and filled, and row-time maintenance is re-registered — the same
|
|
2489
|
+
* work the create emitter does, minus the `materialized_view_added` event (a
|
|
2490
|
+
* store rehydrating its own catalog must not re-emit persistence events;
|
|
2491
|
+
* `table_added` still fires for the backing table, exactly as on create).
|
|
2492
|
+
*
|
|
2493
|
+
* Unlike {@link importView}, the body plans EAGERLY (the backing cannot be
|
|
2494
|
+
* filled without running it), so MV import is order-dependent: the body's
|
|
2495
|
+
* sources — including another MV's backing for MV-over-MV — must already be
|
|
2496
|
+
* registered. The store's `rehydrateCatalog` orders MVs after tables/views
|
|
2497
|
+
* and resolves MV-over-MV chains by fixpoint retry. A body that cannot plan,
|
|
2498
|
+
* fills with duplicate keys, or fails the row-time eligibility gate in
|
|
2499
|
+
* `registerMaterializedView` throws (after {@link materializeView} rolls the
|
|
2500
|
+
* half-built backing back), and the caller records it as a per-entry
|
|
2501
|
+
* rehydration error.
|
|
2502
|
+
*
|
|
2503
|
+
* **Adopt fast path.** A pre-existing table at the backing name in the MV's
|
|
2504
|
+
* own backing module (a durable host's phase-1 rehydration of its backing)
|
|
2505
|
+
* is ADOPTED — registered as-is, no body re-execution — iff ALL gates pass
|
|
2506
|
+
* (see {@link tryAdoptPreExistingBacking}); otherwise it is dropped and the
|
|
2507
|
+
* MV refills through {@link materializeView}. Only import ever adopts —
|
|
2508
|
+
* create and refresh are unchanged.
|
|
2509
|
+
*/
|
|
2510
|
+
async importMaterializedView(spec, options) {
|
|
2511
|
+
// Canonical stored schemaName (see canonicalSchemaName) — the schema itself
|
|
2512
|
+
// is created below, after the DML-body gate.
|
|
2513
|
+
const targetSchemaName = this.canonicalSchemaName(spec.schemaName || this.getCurrentSchemaName());
|
|
2514
|
+
const viewName = spec.name;
|
|
2515
|
+
// A DML body (insert/update/delete … returning) parses but is un-creatable —
|
|
2516
|
+
// `planViewBody` rejects it at build time. Reject it here too, BEFORE
|
|
2517
|
+
// materializing: a corrupt or hand-edited catalog entry would otherwise
|
|
2518
|
+
// EXECUTE the mutation against live source tables during rehydrate.
|
|
2519
|
+
if (spec.select.type === 'insert' || spec.select.type === 'update' || spec.select.type === 'delete') {
|
|
2520
|
+
throw new QuereusError(`${spec.select.type.toUpperCase()} cannot be used as a materialized view body`, StatusCode.ERROR);
|
|
2521
|
+
}
|
|
2522
|
+
// Create the schema if absent (mirrors importTable/importView) so an MV
|
|
2523
|
+
// rehydrates even into a schema that holds no tables.
|
|
2524
|
+
this.getOrCreateSchema(targetSchemaName);
|
|
2525
|
+
// Honor the re-parsed `using <module>(...)` clause (the generator emits it
|
|
2526
|
+
// only when non-default). An unknown or capability-less module throws from
|
|
2527
|
+
// buildBackingTableSchema inside materializeView — the caller records it
|
|
2528
|
+
// as a per-entry rehydration error.
|
|
2529
|
+
const backing = normalizeBackingModule(spec.moduleName, spec.moduleArgs);
|
|
2530
|
+
const def = {
|
|
2531
|
+
schemaName: targetSchemaName,
|
|
2532
|
+
viewName,
|
|
2533
|
+
// Any `with defaults (…)` rides inside spec.select (→ selectAst).
|
|
2534
|
+
selectAst: spec.select,
|
|
2535
|
+
bodySql: astToString(spec.select),
|
|
2536
|
+
columns: spec.columns,
|
|
2537
|
+
tags: spec.tags ? Object.freeze({ ...spec.tags }) : undefined,
|
|
2538
|
+
backingModuleName: backing.storedModuleName,
|
|
2539
|
+
backingModuleArgs: backing.storedModuleArgs,
|
|
2540
|
+
};
|
|
2541
|
+
// Derive the backing shape ONCE for the whole import (ordering gate, adopt
|
|
2542
|
+
// gates, and the refill all read it). Throws when the body cannot plan —
|
|
2543
|
+
// deliberately BEFORE any drop, so a not-yet-resolvable body (the store's
|
|
2544
|
+
// MV-over-MV fixpoint: a dependent fails until its upstream's round lands)
|
|
2545
|
+
// errors per-entry with any pre-existing backing preserved — data-safe.
|
|
2546
|
+
const shape = deriveBackingShape(this.db, def.bodySql, def.columns);
|
|
2547
|
+
// Declared-column arity mismatch: the entry can NEVER materialize, so throw
|
|
2548
|
+
// with the backing preserved rather than dropping durable rows for nothing.
|
|
2549
|
+
assertDeclaredColumnArity(def, shape);
|
|
2550
|
+
// Ordering gate: a body source that is itself a pending maintained-table
|
|
2551
|
+
// entry (its own `create materialized view` not yet imported this session)
|
|
2552
|
+
// already pre-exists as a *plain* table, so the body PLANS — but its content
|
|
2553
|
+
// may be about to be replaced by the upstream's own import. Defer this entry
|
|
2554
|
+
// to a later fixpoint round (per-entry error; the store retries).
|
|
2555
|
+
for (const src of shape.sourceTables) {
|
|
2556
|
+
if (options?.pendingDerivations?.has(src)) {
|
|
2557
|
+
throw new QuereusError(`cannot import materialized view '${targetSchemaName}.${viewName}' yet: source '${src}' is a maintained table whose own catalog entry has not imported this session`, StatusCode.ERROR);
|
|
2558
|
+
}
|
|
1344
2559
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
2560
|
+
// A durable backing-host module may have rehydrated the maintained table
|
|
2561
|
+
// itself (phase 1: a plain `create table` bundle under the same name)
|
|
2562
|
+
// before this MV catalog entry imports (phase 3). A pre-existing
|
|
2563
|
+
// derivation-less table owned by the MV's own backing module IS that
|
|
2564
|
+
// rehydrated backing: adopt it when every gate passes, else drop it and
|
|
2565
|
+
// re-materialize from the body. A table in a DIFFERENT module is not
|
|
2566
|
+
// ours — fail the entry rather than dropping user data. The create
|
|
2567
|
+
// emitter keeps the plain collision error.
|
|
2568
|
+
const preExisting = this.getTable(targetSchemaName, viewName);
|
|
2569
|
+
if (preExisting) {
|
|
2570
|
+
if (isMaintainedTable(preExisting)) {
|
|
2571
|
+
throw new QuereusError(`cannot import materialized view '${targetSchemaName}.${viewName}': a maintained table with the same name already exists`, StatusCode.CONSTRAINT);
|
|
2572
|
+
}
|
|
2573
|
+
if ((preExisting.vtabModuleName ?? '').toLowerCase() === backing.moduleName) {
|
|
2574
|
+
if (options?.trustBackings && await this.tryAdoptPreExistingBacking(def, preExisting, backing.moduleName, options.adoptedBackings, shape)) {
|
|
2575
|
+
log(`Adopted materialized view %s.%s (durable backing trusted; refill skipped)`, targetSchemaName, viewName);
|
|
2576
|
+
return { type: 'materializedView', name: `${targetSchemaName}.${viewName}` };
|
|
2577
|
+
}
|
|
2578
|
+
await this.dropTable(targetSchemaName, viewName, /*ifExists*/ true);
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
throw new QuereusError(`cannot import materialized view '${targetSchemaName}.${viewName}': table '${viewName}' already exists in module '${preExisting.vtabModuleName}', not the MV's backing module '${backing.moduleName}'`, StatusCode.CONSTRAINT);
|
|
2582
|
+
}
|
|
1347
2583
|
}
|
|
1348
|
-
|
|
2584
|
+
await materializeView(this.db, def, shape);
|
|
2585
|
+
log(`Imported materialized view %s.%s`, targetSchemaName, viewName);
|
|
2586
|
+
return { type: 'materializedView', name: `${targetSchemaName}.${viewName}` };
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* The adopt-without-refill gate check + adopt for a pre-existing same-module
|
|
2590
|
+
* backing during MV import. Returns true when the backing was adopted; false
|
|
2591
|
+
* means "fall back to drop+refill". `shape` is the caller's pre-derived
|
|
2592
|
+
* backing shape (derived before any drop — see {@link importMaterializedView}).
|
|
2593
|
+
* Gates, of five (the caller already verified gate 1, same-module, and
|
|
2594
|
+
* gate 5, `trustBackings`):
|
|
2595
|
+
*
|
|
2596
|
+
* 2. **Shape** — `backingShapeMatches(preExisting, shape)`: the persisted
|
|
2597
|
+
* backing is column-for-column what the re-planned body would build
|
|
2598
|
+
* (names, logical types, not-null, collation, physical PK).
|
|
2599
|
+
* 3. **bodyHash** — automatic by construction: the catalog persists DDL and
|
|
2600
|
+
* import re-parses it, recomputing `computeBodyHash` from the same
|
|
2601
|
+
* canonical definition — there is no independently persisted hash that
|
|
2602
|
+
* could diverge, so no runtime check is possible or needed.
|
|
2603
|
+
* 4. **Sources** — every table the body reads lives in the SAME module as
|
|
2604
|
+
* the backing (one storage substrate ⇒ the divergence window is the
|
|
2605
|
+
* documented crash window the marker attests against; a cross-module
|
|
2606
|
+
* source — e.g. memory — was itself just recomputed, so persisted backing
|
|
2607
|
+
* rows may be stale relative to it), AND every source that is itself a
|
|
2608
|
+
* maintained table was ADOPTED this session (`adoptedBackings`) — a
|
|
2609
|
+
* refilled upstream may hold new content its dependents must reflect.
|
|
2610
|
+
* (The caller's pending-derivations gate guarantees every maintained
|
|
2611
|
+
* source's own entry has already imported, so derivation presence is
|
|
2612
|
+
* decidable here.)
|
|
2613
|
+
*
|
|
2614
|
+
* A throw from `adoptMaterializedView` itself (the row-time eligibility gate
|
|
2615
|
+
* in registration) propagates — per-entry error, backing left registered as
|
|
2616
|
+
* a plain (derivation-less) table.
|
|
2617
|
+
*/
|
|
2618
|
+
async tryAdoptPreExistingBacking(def, preExisting, backingModuleName, adoptedBackings, shape) {
|
|
2619
|
+
if (!backingShapeMatches(preExisting, shape))
|
|
2620
|
+
return false;
|
|
2621
|
+
for (const qualified of shape.sourceTables) {
|
|
2622
|
+
const dot = qualified.indexOf('.');
|
|
2623
|
+
const sourceSchema = dot >= 0 ? qualified.slice(0, dot) : this.getCurrentSchemaName();
|
|
2624
|
+
const sourceName = dot >= 0 ? qualified.slice(dot + 1) : qualified;
|
|
2625
|
+
const source = this.getTable(sourceSchema, sourceName);
|
|
2626
|
+
if (!source)
|
|
2627
|
+
return false;
|
|
2628
|
+
if ((source.vtabModuleName ?? '').toLowerCase() !== backingModuleName)
|
|
2629
|
+
return false;
|
|
2630
|
+
// A maintained-table source is another MV's backing; require it was
|
|
2631
|
+
// adopted, not refilled, this session.
|
|
2632
|
+
if (isMaintainedTable(source) && !adoptedBackings?.has(qualified))
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
await adoptMaterializedView(this.db, def, preExisting, shape);
|
|
2636
|
+
adoptedBackings?.add(`${def.schemaName}.${def.viewName}`.toLowerCase());
|
|
2637
|
+
return true;
|
|
1349
2638
|
}
|
|
1350
2639
|
/**
|
|
1351
2640
|
* Import a table schema without calling module.create().
|
|
@@ -1355,26 +2644,45 @@ export class SchemaManager {
|
|
|
1355
2644
|
const targetSchemaName = stmt.table.schema || this.getCurrentSchemaName();
|
|
1356
2645
|
const tableName = stmt.table.name;
|
|
1357
2646
|
const { moduleName, effectiveModuleArgs, moduleInfo } = this.resolveModuleInfo(stmt);
|
|
1358
|
-
|
|
2647
|
+
// Rehydrate path: persisted DDL already made any non-BINARY collation explicit,
|
|
2648
|
+
// so an omitted COLLATE is canonical BINARY regardless of the live session default.
|
|
2649
|
+
const tableSchema = this.buildTableSchemaFromAST(stmt, moduleName, effectiveModuleArgs, moduleInfo, 'BINARY');
|
|
1359
2650
|
try {
|
|
1360
|
-
|
|
2651
|
+
// Module-facing stored-name contract (see canonicalSchemaName): the
|
|
2652
|
+
// rehydrated connect receives `tableSchema`'s canonical schemaName and
|
|
2653
|
+
// stored display casing, never the raw persisted-DDL spelling — a module
|
|
2654
|
+
// keying storage by the args addresses the same physical store it created.
|
|
2655
|
+
await moduleInfo.module.connect(this.db, moduleInfo.auxData, moduleName, tableSchema.schemaName, tableSchema.name, effectiveModuleArgs, tableSchema);
|
|
1361
2656
|
}
|
|
1362
2657
|
catch (e) {
|
|
1363
2658
|
const message = e instanceof Error ? e.message : String(e);
|
|
1364
2659
|
throw new QuereusError(`Module '${moduleName}' connect failed during import for table '${tableName}': ${message}`, StatusCode.ERROR);
|
|
1365
2660
|
}
|
|
1366
|
-
|
|
1367
|
-
if (!schema) {
|
|
1368
|
-
const lowerSchemaName = targetSchemaName.toLowerCase();
|
|
1369
|
-
schema = new Schema(lowerSchemaName);
|
|
1370
|
-
this.schemas.set(lowerSchemaName, schema);
|
|
1371
|
-
}
|
|
2661
|
+
const schema = this.getOrCreateSchema(tableSchema.schemaName);
|
|
1372
2662
|
schema.addTable(tableSchema);
|
|
2663
|
+
// Catalog rehydration registers FK-bearing tables silently (no `table_added`
|
|
2664
|
+
// fires) and via `getOrCreateSchema`, which only resets when it *creates* a
|
|
2665
|
+
// schema — so importing a child into an existing schema (e.g. `main`) would
|
|
2666
|
+
// otherwise leave a stale reverse FK index. Cold reopen masks this (the index
|
|
2667
|
+
// is built lazily only after rehydration completes), but a re-import onto a
|
|
2668
|
+
// live, already-built index would under-report — the fatal direction. Reset
|
|
2669
|
+
// directly so the silent-import path upholds the same invariant as the events.
|
|
2670
|
+
// The lens basis-FK gate shares this vector: a basis table imported silently
|
|
2671
|
+
// after the gate was built would otherwise leave it under-reporting too.
|
|
2672
|
+
this.invalidateReverseFkIndex();
|
|
2673
|
+
this.invalidateLensFkGate();
|
|
1373
2674
|
log(`Imported table %s.%s using module %s`, targetSchemaName, tableName, moduleName);
|
|
1374
|
-
return { type: 'table', name: `${
|
|
2675
|
+
return { type: 'table', name: `${tableSchema.schemaName}.${tableSchema.name}` };
|
|
1375
2676
|
}
|
|
1376
2677
|
/**
|
|
1377
2678
|
* Import an index schema without calling module.createIndex().
|
|
2679
|
+
*
|
|
2680
|
+
* Reconstructs the index with full fidelity from the re-parsed DDL so a
|
|
2681
|
+
* `CREATE [UNIQUE] INDEX ... (col [COLLATE x]) [WHERE ...]` survives a
|
|
2682
|
+
* catalog round-trip: per-column collation, the UNIQUE flag, the partial
|
|
2683
|
+
* predicate, and (for a unique index) the synthesized `derivedFromIndex`
|
|
2684
|
+
* UNIQUE constraint — mirroring the live `buildIndexSchema` + the shared
|
|
2685
|
+
* {@link appendIndexToTableSchema} that {@link createIndex} uses.
|
|
1378
2686
|
*/
|
|
1379
2687
|
async importIndex(stmt) {
|
|
1380
2688
|
const targetSchemaName = stmt.table.schema || this.getCurrentSchemaName();
|
|
@@ -1385,9 +2693,10 @@ export class SchemaManager {
|
|
|
1385
2693
|
if (!tableSchema) {
|
|
1386
2694
|
throw new QuereusError(`Cannot import index '${indexName}': table '${tableName}' not found`, StatusCode.ERROR);
|
|
1387
2695
|
}
|
|
1388
|
-
// Build index columns schema
|
|
2696
|
+
// Build index columns schema. Mirrors buildIndexSchema's collation resolution
|
|
2697
|
+
// (per-column COLLATE → table column collation → BINARY).
|
|
1389
2698
|
const indexColumns = stmt.columns.map(col => {
|
|
1390
|
-
const colName = col
|
|
2699
|
+
const { name: colName, collation } = resolveImportedIndexColumn(col);
|
|
1391
2700
|
if (!colName) {
|
|
1392
2701
|
throw new QuereusError(`Expression-based index columns are not supported during import`, StatusCode.ERROR);
|
|
1393
2702
|
}
|
|
@@ -1395,26 +2704,52 @@ export class SchemaManager {
|
|
|
1395
2704
|
if (colIdx === undefined) {
|
|
1396
2705
|
throw new QuereusError(`Column '${colName}' not found in table '${tableName}'`, StatusCode.ERROR);
|
|
1397
2706
|
}
|
|
2707
|
+
const tableColSchema = tableSchema.columns[colIdx];
|
|
1398
2708
|
return {
|
|
1399
2709
|
index: colIdx,
|
|
1400
2710
|
desc: col.direction === 'desc',
|
|
2711
|
+
collation: normalizeCollationName(collation || tableColSchema.collation || 'BINARY'),
|
|
1401
2712
|
};
|
|
1402
2713
|
});
|
|
1403
2714
|
const indexSchema = {
|
|
1404
2715
|
name: indexName,
|
|
1405
2716
|
columns: Object.freeze(indexColumns),
|
|
2717
|
+
unique: stmt.isUnique || undefined,
|
|
2718
|
+
predicate: stmt.where,
|
|
1406
2719
|
tags: stmt.tags && Object.keys(stmt.tags).length > 0 ? Object.freeze({ ...stmt.tags }) : undefined,
|
|
1407
2720
|
};
|
|
1408
|
-
//
|
|
1409
|
-
|
|
1410
|
-
const updatedTableSchema =
|
|
1411
|
-
...tableSchema,
|
|
1412
|
-
indexes: Object.freeze(updatedIndexes),
|
|
1413
|
-
};
|
|
2721
|
+
// Append the index (and synthesize the derived UNIQUE constraint when
|
|
2722
|
+
// unique) without calling module.createIndex() — the storage already exists.
|
|
2723
|
+
const updatedTableSchema = appendIndexToTableSchema(tableSchema, indexSchema);
|
|
1414
2724
|
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
1415
2725
|
schema.addTable(updatedTableSchema);
|
|
1416
2726
|
log(`Imported index %s on table %s.%s`, indexName, targetSchemaName, tableName);
|
|
1417
2727
|
return { type: 'index', name: `${targetSchemaName}.${tableName}.${indexName}` };
|
|
1418
2728
|
}
|
|
1419
2729
|
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Resolves an indexed-column AST node to its underlying column name and optional
|
|
2732
|
+
* collation, for catalog import.
|
|
2733
|
+
*
|
|
2734
|
+
* The parser folds `col COLLATE x` into a `collate` expression wrapping a bare
|
|
2735
|
+
* column reference (see `indexedColumn()` in parser.ts), leaving `col.name`
|
|
2736
|
+
* unset. Since `generateIndexDDL` always emits an explicit `COLLATE <c>` per
|
|
2737
|
+
* column, *every* generated index DDL re-parses into this collate-wrapped form —
|
|
2738
|
+
* so unwrapping it is required for the common case, not just non-BINARY
|
|
2739
|
+
* collations. A genuine expression index (non-column operand) returns an unset
|
|
2740
|
+
* name and is rejected by the caller.
|
|
2741
|
+
*/
|
|
2742
|
+
function resolveImportedIndexColumn(col) {
|
|
2743
|
+
// Bare column reference (`col [ASC|DESC]`) — name set directly by the parser.
|
|
2744
|
+
if (col.name) {
|
|
2745
|
+
return { name: col.name, collation: col.collation };
|
|
2746
|
+
}
|
|
2747
|
+
// Collate-wrapped column (`col COLLATE x`) — unwrap to the column + collation.
|
|
2748
|
+
const expr = col.expr;
|
|
2749
|
+
if (expr?.type === 'collate' && expr.expr.type === 'column' && !expr.expr.table && !expr.expr.schema) {
|
|
2750
|
+
return { name: expr.expr.name, collation: expr.collation };
|
|
2751
|
+
}
|
|
2752
|
+
// Anything else is a genuine expression index — unsupported on import.
|
|
2753
|
+
return { name: undefined, collation: undefined };
|
|
2754
|
+
}
|
|
1420
2755
|
//# sourceMappingURL=manager.js.map
|