@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
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
import { columnsFormDeclaredKey } from './table.js';
|
|
2
|
+
import { QuereusError } from '../common/errors.js';
|
|
3
|
+
import { StatusCode } from '../common/types.js';
|
|
4
|
+
import { astToString } from '../emit/ast-stringify.js';
|
|
5
|
+
import { buildLogicalConstraints } from './lens.js';
|
|
6
|
+
import { computeSchemaHash } from './schema-hasher.js';
|
|
7
|
+
import { validateReservedTags } from './reserved-tags.js';
|
|
8
|
+
import { raiseReservedTagDiagnostics } from './reserved-tags-policy.js';
|
|
9
|
+
import { proveLens, collectColumnRefNames } from './lens-prover.js';
|
|
10
|
+
import { applyAckGovernance, resolveEscalationPolicy } from './lens-ack.js';
|
|
11
|
+
import { createLogger } from '../common/logger.js';
|
|
12
|
+
import { addInd, MAX_INDS_PER_NODE } from '../planner/util/fd-utils.js';
|
|
13
|
+
const log = createLogger('schema:lens-compiler');
|
|
14
|
+
/**
|
|
15
|
+
* Lens compiler — the `apply schema X` step for a **logical** schema.
|
|
16
|
+
*
|
|
17
|
+
* For each declared logical table it: builds the logical spec, aligns it against
|
|
18
|
+
* the basis schema (the default name-based aligner), produces the inlined
|
|
19
|
+
* effective view body, populates the lens slot, and registers the body as an
|
|
20
|
+
* ordinary `ViewSchema`. The query processor then sees a view; reads ride the
|
|
21
|
+
* standard view-resolution path and writes ride view-updateability.
|
|
22
|
+
*
|
|
23
|
+
* v1 is **single-source, name-based** (see `docs/lens.md` § The Default Mapper):
|
|
24
|
+
* a logical table maps to a name-matching basis table, and each logical column
|
|
25
|
+
* to a name-matching basis column. Type/nullability conformance and the n-way
|
|
26
|
+
* decomposition shape are deferred to the prover / decomposition tickets.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Deploys (or re-deploys) a logical schema's lens slots + compiled view bodies.
|
|
30
|
+
*
|
|
31
|
+
* Re-deploy semantics are **clear-and-rebuild**: every existing lens view + slot
|
|
32
|
+
* in the logical schema is dropped, then rebuilt from the current declaration.
|
|
33
|
+
* This is how asymmetric removal falls out for free — a logical table dropped
|
|
34
|
+
* from the declaration is simply not rebuilt (its view + slot vanish), and the
|
|
35
|
+
* basis is never touched (logical removals never cascade to basis storage; see
|
|
36
|
+
* `docs/lens.md` § Deployment).
|
|
37
|
+
*/
|
|
38
|
+
export function deployLogicalSchema(db, declaredSchema, logicalSchemaName) {
|
|
39
|
+
validateLogicalDeclaration(declaredSchema, logicalSchemaName);
|
|
40
|
+
const schemaManager = db.schemaManager;
|
|
41
|
+
const logicalSchema = schemaManager.getSchemaOrFail(logicalSchemaName);
|
|
42
|
+
// The lens block (explicit `over Y` basis + sparse overrides), if declared.
|
|
43
|
+
// Re-read from source on every deploy — that is what lets a rename and a
|
|
44
|
+
// later column-add compose without attribute-ID plumbing (docs/lens.md § D2).
|
|
45
|
+
const lensDecl = db.declaredSchemaManager.getLensDeclaration(logicalSchemaName);
|
|
46
|
+
const overridesByTable = indexOverrides(lensDecl, declaredSchema, logicalSchemaName);
|
|
47
|
+
// Infer the basis lazily, only when there is ≥1 logical table to align. An
|
|
48
|
+
// empty logical declaration (e.g. re-applying X after all its tables are
|
|
49
|
+
// removed) is a pure detach-everything operation and must NOT fail on basis
|
|
50
|
+
// resolution — removal never depends on the basis (asymmetric removal).
|
|
51
|
+
let basis;
|
|
52
|
+
const resolveBasis = () => {
|
|
53
|
+
if (lensDecl) {
|
|
54
|
+
// Explicit `over Y` binding resolves the foundation's default-basis
|
|
55
|
+
// ambiguity (docs/lens.md § D4); `inferDefaultBasis` is NOT consulted.
|
|
56
|
+
const basisSchema = schemaManager.getSchema(lensDecl.basisSchema);
|
|
57
|
+
if (!basisSchema) {
|
|
58
|
+
throw new QuereusError(`lens: basis schema '${lensDecl.basisSchema}' for logical schema '${logicalSchemaName}' does not exist (declared via 'declare lens for ${logicalSchemaName} over ${lensDecl.basisSchema}')`, StatusCode.ERROR);
|
|
59
|
+
}
|
|
60
|
+
return { schema: basisSchema, schemaName: basisSchema.name };
|
|
61
|
+
}
|
|
62
|
+
return inferDefaultBasis(schemaManager, logicalSchemaName);
|
|
63
|
+
};
|
|
64
|
+
// Compile everything FIRST (basis alignment can throw — name mismatch, etc.).
|
|
65
|
+
// Only after every table aligns successfully do we mutate the catalog, so a
|
|
66
|
+
// failed re-apply leaves the existing lens state untouched (atomic deploy).
|
|
67
|
+
// Module mapping advertisements are collected once per deploy (the first time a
|
|
68
|
+
// basis is resolved) and filtered per logical table by the resolver.
|
|
69
|
+
let allAdvertisements;
|
|
70
|
+
const compiled = [];
|
|
71
|
+
// Prover accumulators (docs/lens.md § Coverage checklist). Errors aggregate
|
|
72
|
+
// across every table and throw atomically below — before any catalog mutation,
|
|
73
|
+
// preserving the existing atomic-deploy property. Warnings flow to the report.
|
|
74
|
+
const proveErrors = [];
|
|
75
|
+
const proveWarnings = [];
|
|
76
|
+
const acknowledged = [];
|
|
77
|
+
const obligationsByTable = new Map();
|
|
78
|
+
for (const item of declaredSchema.items) {
|
|
79
|
+
if (item.type !== 'declaredTable')
|
|
80
|
+
continue;
|
|
81
|
+
basis ??= resolveBasis();
|
|
82
|
+
allAdvertisements ??= collectAdvertisements(db, basis.schema);
|
|
83
|
+
const logicalTable = schemaManager.buildLogicalTableSchema(item.tableStmt, logicalSchema.name);
|
|
84
|
+
const override = overridesByTable.get(logicalTable.name.toLowerCase());
|
|
85
|
+
// Resolve (collect → select primary → validate) the advertisement BEFORE body
|
|
86
|
+
// compilation, so a malformed advertisement aborts the deploy atomically. The
|
|
87
|
+
// resolved advertisement is **stored** on the slot, not yet synthesized — the
|
|
88
|
+
// v1 name-match / override body producer below is unchanged by this ticket;
|
|
89
|
+
// `lens-multi-source-decomposition` reads the slot to build the n-way body.
|
|
90
|
+
const { advertisement, auxiliaryAccess } = resolveAdvertisement(allAdvertisements, logicalTable, basis, db, logicalSchemaName, override !== undefined);
|
|
91
|
+
let compiledBody;
|
|
92
|
+
let provenance;
|
|
93
|
+
let effectiveColumns;
|
|
94
|
+
// Whether the body came from `compileDecompositionBody` (the synthesized
|
|
95
|
+
// n-way decomposition). Only that body actually carries the advertised
|
|
96
|
+
// `anchor ⋈ member` joins the existence-anchor IND describes, so it is the
|
|
97
|
+
// only body the IND injection may attach to (R2 gate below).
|
|
98
|
+
let fromDecomposition = false;
|
|
99
|
+
if (override) {
|
|
100
|
+
const merged = compileOverrideBody(logicalTable, logicalSchemaName, basis.schemaName, schemaManager, override, advertisement);
|
|
101
|
+
compiledBody = merged.body;
|
|
102
|
+
provenance = merged.provenance;
|
|
103
|
+
effectiveColumns = merged.effectiveColumns;
|
|
104
|
+
// Override ⊕ advertisement composition (docs/lens.md § Override-vs-advertisement):
|
|
105
|
+
// a *sparse* override (one that relies on gap-fill) must not re-anchor or
|
|
106
|
+
// reference relations outside the advertised decomposition. A *full*
|
|
107
|
+
// hand-authored override (no gap-fill) bypasses the advertisement and is
|
|
108
|
+
// not conflict-checked. Gap-fill *execution* from the advertisement lands
|
|
109
|
+
// in `lens-multi-source-decomposition`; this ticket validates the conflict.
|
|
110
|
+
if (advertisement && provenance.some(p => p.source === 'default')) {
|
|
111
|
+
validateOverrideAdvertisementConflict(advertisement, override, basis.schemaName, schemaManager, logicalSchemaName, logicalTable.name);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (advertisement) {
|
|
115
|
+
// A resolved primary-storage advertisement → synthesize the n-way `get`
|
|
116
|
+
// join body from the decomposition (docs/lens.md § The Default Mapper).
|
|
117
|
+
compiledBody = compileDecompositionBody(logicalTable, logicalSchemaName, basis, advertisement, db);
|
|
118
|
+
provenance = logicalTable.columns.map(c => ({ logicalColumn: c.name, source: 'default' }));
|
|
119
|
+
effectiveColumns = logicalTable.columns.map(c => c.name);
|
|
120
|
+
fromDecomposition = true;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
compiledBody = compileDefaultBody(logicalTable, logicalSchemaName, basis.schema, basis.schemaName);
|
|
124
|
+
provenance = logicalTable.columns.map(c => ({ logicalColumn: c.name, source: 'default' }));
|
|
125
|
+
effectiveColumns = logicalTable.columns.map(c => c.name);
|
|
126
|
+
}
|
|
127
|
+
// Annotate provenance with advertisement-backed member info (introspection).
|
|
128
|
+
if (advertisement)
|
|
129
|
+
annotateProvenanceWithAdvertisement(provenance, advertisement);
|
|
130
|
+
// Inject the existence-anchor IND surface from a primary-storage
|
|
131
|
+
// advertisement (lens-multi-source-ind-injection): one relation-IND per
|
|
132
|
+
// mandatory non-anchor member, threaded to the prover via the slot so the
|
|
133
|
+
// mandatory inner-joins are provably row-loss-free and the put fan-out is
|
|
134
|
+
// sound against a derived existence fact. See docs/lens.md § The module
|
|
135
|
+
// mapping advertisement and docs/optimizer.md § Inclusion Dependency Tracking.
|
|
136
|
+
//
|
|
137
|
+
// Gated (R2) to the synthesized-decomposition body: only
|
|
138
|
+
// `compileDecompositionBody` emits the advertised `anchor ⋈ member` joins the
|
|
139
|
+
// IND describes. A full hand-authored override and the single-source default
|
|
140
|
+
// body carry no such join, so injecting there would describe joins absent from
|
|
141
|
+
// `compiledBody` — leave `injectedInds` undefined. (The future sparse-override
|
|
142
|
+
// gap-fill body — `lens-multi-source-decomposition` — will carry the joins too;
|
|
143
|
+
// extend the gate to it then.)
|
|
144
|
+
const injectedInds = fromDecomposition
|
|
145
|
+
? computeExistenceAnchorInds(advertisement, basis, db)
|
|
146
|
+
: [];
|
|
147
|
+
const slot = {
|
|
148
|
+
logicalTable,
|
|
149
|
+
defaultBasis: { schemaName: basis.schemaName },
|
|
150
|
+
override: override?.select,
|
|
151
|
+
compiledBody,
|
|
152
|
+
columnProvenance: provenance,
|
|
153
|
+
attachedConstraints: buildLogicalConstraints(logicalTable),
|
|
154
|
+
advertisement,
|
|
155
|
+
auxiliaryAccess,
|
|
156
|
+
injectedInds: injectedInds.length > 0 ? injectedInds : undefined,
|
|
157
|
+
};
|
|
158
|
+
// PoC: validate the reserved `quereus.*` tag namespace shape + site on the
|
|
159
|
+
// logical table and its constraints, INSIDE the compile-first loop so an
|
|
160
|
+
// invalid tag fails the deploy atomically (before any catalog mutation).
|
|
161
|
+
// Only shape/site is checked here — no reserved tag's semantics are read.
|
|
162
|
+
validateLensTags(slot);
|
|
163
|
+
// Prove the slot and classify its constraints (docs/lens.md § Coverage
|
|
164
|
+
// checklist). The verdict is recorded on the slot (obligations + readOnly)
|
|
165
|
+
// and its diagnostics are aggregated into the deploy report. Errors are
|
|
166
|
+
// thrown atomically after every table is proved (below), preserving the
|
|
167
|
+
// atomic-deploy contract; warnings never block.
|
|
168
|
+
const prove = proveLens(slot, db);
|
|
169
|
+
slot.obligations = prove.obligations;
|
|
170
|
+
slot.readOnly = prove.readOnly;
|
|
171
|
+
// Acknowledgment + escalation governance (docs/lens.md § Acknowledging
|
|
172
|
+
// advisories). Coded+sited advisories the prover emitted become
|
|
173
|
+
// acknowledgeable in source (the `quereus.lens.ack.<code>` tag, with a
|
|
174
|
+
// recorded fingerprint that re-surfaces them on material change), and the
|
|
175
|
+
// per-table escalation policy promotes specific codes to blocking errors.
|
|
176
|
+
// Escalation errors aggregate with the prover's and throw atomically below.
|
|
177
|
+
const governance = applyAckGovernance(slot, prove.warnings, resolveEscalationPolicy(logicalTable));
|
|
178
|
+
proveErrors.push(...prove.errors, ...governance.errors);
|
|
179
|
+
proveWarnings.push(...governance.warnings);
|
|
180
|
+
acknowledged.push(...governance.acknowledged);
|
|
181
|
+
obligationsByTable.set(logicalTable.name.toLowerCase(), prove.obligations);
|
|
182
|
+
const view = {
|
|
183
|
+
name: logicalTable.name,
|
|
184
|
+
schemaName: logicalSchema.name,
|
|
185
|
+
sql: astToString(compiledBody),
|
|
186
|
+
selectAst: compiledBody,
|
|
187
|
+
// Pin the consumer-facing column names to the *logical* declaration
|
|
188
|
+
// (the contract), independent of the basis
|
|
189
|
+
// column casing. Equivalent to `create view T(<logical cols>) as
|
|
190
|
+
// <body>`: `select * from X.T` then surfaces the logical names, not
|
|
191
|
+
// whatever the basis happens to spell them. Write-through is
|
|
192
|
+
// unaffected (positional passthrough).
|
|
193
|
+
columns: effectiveColumns,
|
|
194
|
+
tags: logicalTable.tags,
|
|
195
|
+
};
|
|
196
|
+
compiled.push({ slot, view });
|
|
197
|
+
}
|
|
198
|
+
// Atomic block: any prover error aborts the deploy BEFORE the catalog mutation
|
|
199
|
+
// below, so a failed re-apply leaves the prior lens state untouched. Every
|
|
200
|
+
// blocking diagnostic is listed (sited) in one error.
|
|
201
|
+
if (proveErrors.length > 0) {
|
|
202
|
+
throw new QuereusError(formatProveErrors(logicalSchemaName, proveErrors), StatusCode.ERROR);
|
|
203
|
+
}
|
|
204
|
+
// Clear-and-rebuild: drop all current lens views + slots, then register the
|
|
205
|
+
// freshly compiled set. A logical schema's views are exclusively lens bodies,
|
|
206
|
+
// so dropping every view is safe and implements detach for tables removed
|
|
207
|
+
// from the declaration (logical removals never touch basis storage).
|
|
208
|
+
for (const view of Array.from(logicalSchema.getAllViews())) {
|
|
209
|
+
logicalSchema.removeView(view.name);
|
|
210
|
+
}
|
|
211
|
+
logicalSchema.clearLensSlots();
|
|
212
|
+
for (const { slot, view } of compiled) {
|
|
213
|
+
logicalSchema.addLensSlot(slot);
|
|
214
|
+
logicalSchema.addView(view);
|
|
215
|
+
log('Deployed lens for %s.%s over %s', logicalSchemaName, slot.logicalTable.name, slot.defaultBasis.schemaName);
|
|
216
|
+
}
|
|
217
|
+
// The lens-slot set just changed (the only slot-mutating site that fires no
|
|
218
|
+
// `SchemaChangeEvent`), so the lens basis-FK gate is stale — reset it directly,
|
|
219
|
+
// sibling to the snapshot rotation. The next basis write rebuilds it and reflects
|
|
220
|
+
// any added / removed logical FK in this deploy.
|
|
221
|
+
schemaManager.invalidateLensFkGate();
|
|
222
|
+
// Capture + rotate the deployed-basis snapshot AFTER a successful catalog
|
|
223
|
+
// mutation, so an aborted re-apply leaves the prior snapshot untouched. The
|
|
224
|
+
// snapshot is the source of truth the `quereus_basis_backfill` differ reads
|
|
225
|
+
// (docs/lens.md § The deployed basis representation).
|
|
226
|
+
const snapshot = buildDeploymentSnapshot(db, compiled, basis, logicalSchemaName);
|
|
227
|
+
db.declaredSchemaManager.rotateDeployedLensSnapshot(logicalSchemaName, snapshot);
|
|
228
|
+
// Errors are already thrown above; a returned report carries only advisories.
|
|
229
|
+
// Persist it on the manager — the stable hook the sibling acknowledgment ticket
|
|
230
|
+
// reads to fingerprint / tally / expand advisories (docs/lens.md § Acknowledging
|
|
231
|
+
// advisories). `apply schema` returning these as result rows is deferred to that
|
|
232
|
+
// ticket (converting the universally-used void statement to relational is a
|
|
233
|
+
// separate, high-blast-radius change); the report is fully produced here.
|
|
234
|
+
const report = { errors: [], warnings: proveWarnings, acknowledged, obligationsByTable };
|
|
235
|
+
db.declaredSchemaManager.setDeployedLensReport(logicalSchemaName, report);
|
|
236
|
+
return report;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Formats the aggregated blocking diagnostics into one atomic deploy error. Each
|
|
240
|
+
* diagnostic's `message` is included verbatim (so a caller / test matching a
|
|
241
|
+
* specific sited substring still matches), prefixed by its stable code.
|
|
242
|
+
*/
|
|
243
|
+
function formatProveErrors(logicalSchemaName, errors) {
|
|
244
|
+
const lines = errors.map(e => ` - [${e.code}] ${e.message}`);
|
|
245
|
+
return `lens: deploy of logical schema '${logicalSchemaName}' blocked by ${errors.length} error(s):\n${lines.join('\n')}`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Builds the {@link LensDeploymentSnapshot} for a just-completed deploy: per
|
|
249
|
+
* logical table, the compiled get-body, its logical columns, and the
|
|
250
|
+
* per-column basis backing (relation + column) derived from the effective body.
|
|
251
|
+
* Also records `basisHash = computeSchemaHash(basis declared schema)` — the
|
|
252
|
+
* migration-safety record. An empty deploy (every logical table removed, no
|
|
253
|
+
* basis resolved) yields an empty-tables snapshot that still rotates, so the
|
|
254
|
+
* detach is reflected.
|
|
255
|
+
*/
|
|
256
|
+
function buildDeploymentSnapshot(db, compiled, basis, logicalSchemaName) {
|
|
257
|
+
const priorBasisName = db.declaredSchemaManager.getDeployedLensSnapshots(logicalSchemaName)?.current?.basisSchemaName;
|
|
258
|
+
const basisSchemaName = basis?.schemaName ?? priorBasisName ?? '';
|
|
259
|
+
const basisDeclared = basisSchemaName
|
|
260
|
+
? db.declaredSchemaManager.getDeclaredSchema(basisSchemaName)
|
|
261
|
+
: undefined;
|
|
262
|
+
const basisHash = basisDeclared ? computeSchemaHash(basisDeclared) : '';
|
|
263
|
+
const tables = new Map();
|
|
264
|
+
if (basis) {
|
|
265
|
+
for (const { slot } of compiled) {
|
|
266
|
+
const relationBacking = deriveRelationBacking(slot.compiledBody, slot.columnProvenance, basis, db.schemaManager);
|
|
267
|
+
const logicalColumns = slot.columnProvenance.map(p => p.logicalColumn);
|
|
268
|
+
const surrogateMemberKeys = deriveSurrogateMemberKeys(slot, basis);
|
|
269
|
+
tables.set(slot.logicalTable.name.toLowerCase(), {
|
|
270
|
+
logicalTable: slot.logicalTable.name,
|
|
271
|
+
getBody: slot.compiledBody,
|
|
272
|
+
logicalColumns,
|
|
273
|
+
relationBacking,
|
|
274
|
+
surrogateMemberKeys,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { basisSchemaName, basisHash, tables };
|
|
279
|
+
}
|
|
280
|
+
/** Lowercased `schema.table` identity for an override source's basis relation. */
|
|
281
|
+
function sourceRelKey(src) {
|
|
282
|
+
return `${src.table.schemaName.toLowerCase()}.${src.table.name.toLowerCase()}`;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The basis columns an engine-generated skeleton insert must supply: NOT NULL,
|
|
286
|
+
* no default, non-generated. A column that is nullable, defaulted, or generated
|
|
287
|
+
* has its own value source, so a skeleton may soundly omit it; these have none,
|
|
288
|
+
* so omitting one would fail an unguarded NOT NULL constraint. Walks the member's
|
|
289
|
+
* *full* schema so it also flags required columns the lens maps to no logical
|
|
290
|
+
* column (which never appear in the relation's `(basisColumn → logicalColumn)`
|
|
291
|
+
* pairs). See `LensRelationBacking.requiredBasisColumns`.
|
|
292
|
+
*/
|
|
293
|
+
function requiredBasisColumnsOf(table) {
|
|
294
|
+
return table.columns
|
|
295
|
+
.filter(c => c.notNull && c.defaultValue === null && !c.generated)
|
|
296
|
+
.map(c => c.name);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Derives, per basis relation, the `(basisColumn → logicalColumn)` pairs it backs
|
|
300
|
+
* for one compiled effective body — the record a later re-decomposition diffs and
|
|
301
|
+
* backfills (docs/lens.md § The deployed basis representation). Two contributions:
|
|
302
|
+
*
|
|
303
|
+
* 1. **Projection.** The body's projection is exactly the logical
|
|
304
|
+
* columns, in declaration order (see `compileDefaultBody` / `compileOverrideBody`);
|
|
305
|
+
* each plain column reference resolves to its FROM source. A computed
|
|
306
|
+
* (non-column) projection has no single basis backing and is omitted.
|
|
307
|
+
* 2. **Shared join keys.** A columnar split joins its members on a shared key but
|
|
308
|
+
* projects it only once (from the anchor). The other members carry the key
|
|
309
|
+
* column too and must be backfilled with it, else a `not null` key fails. So
|
|
310
|
+
* every join-key column equated to a *projected* logical column is threaded to
|
|
311
|
+
* its member, mapped to that logical column.
|
|
312
|
+
*/
|
|
313
|
+
function deriveRelationBacking(body, provenance, basis, schemaManager) {
|
|
314
|
+
const { sources } = collectOverrideSources(body.from, basis.schemaName, schemaManager);
|
|
315
|
+
const byRef = new Map();
|
|
316
|
+
for (const s of sources)
|
|
317
|
+
byRef.set(s.refName.toLowerCase(), s);
|
|
318
|
+
const single = sources.length === 1 ? sources[0] : undefined;
|
|
319
|
+
const result = new Map();
|
|
320
|
+
const add = (src, basisColumn, logicalColumn) => {
|
|
321
|
+
const key = sourceRelKey(src);
|
|
322
|
+
let rb = result.get(key);
|
|
323
|
+
if (!rb) {
|
|
324
|
+
rb = {
|
|
325
|
+
relationId: key,
|
|
326
|
+
basisRelation: { schema: src.table.schemaName, table: src.table.name },
|
|
327
|
+
columns: [],
|
|
328
|
+
requiredBasisColumns: requiredBasisColumnsOf(src.table),
|
|
329
|
+
};
|
|
330
|
+
result.set(key, rb);
|
|
331
|
+
}
|
|
332
|
+
const cols = rb.columns;
|
|
333
|
+
if (!cols.some(c => c.basisColumn.toLowerCase() === basisColumn.toLowerCase())) {
|
|
334
|
+
cols.push({ basisColumn, logicalColumn });
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
// 1. Projection.
|
|
338
|
+
const projected = [];
|
|
339
|
+
for (let i = 0; i < provenance.length && i < body.columns.length; i++) {
|
|
340
|
+
const rc = body.columns[i];
|
|
341
|
+
if (rc.type !== 'column')
|
|
342
|
+
continue;
|
|
343
|
+
const expr = rc.expr;
|
|
344
|
+
if (expr.type !== 'column')
|
|
345
|
+
continue; // computed → no single backing relation
|
|
346
|
+
const colExpr = expr;
|
|
347
|
+
const src = colExpr.table ? byRef.get(colExpr.table.toLowerCase()) : single;
|
|
348
|
+
if (!src)
|
|
349
|
+
continue; // unqualified ref over a multi-source FROM, or an opaque source
|
|
350
|
+
projected.push({ logical: provenance[i].logicalColumn, src, basisColumn: colExpr.name });
|
|
351
|
+
add(src, colExpr.name, provenance[i].logicalColumn);
|
|
352
|
+
}
|
|
353
|
+
// 2. Shared join keys — thread each equated key column to a projected logical column.
|
|
354
|
+
for (const cls of collectJoinKeyEquivalences(body.from, basis.schemaName, schemaManager)) {
|
|
355
|
+
let logical;
|
|
356
|
+
for (const m of cls) {
|
|
357
|
+
const proj = projected.find(p => sourceRelKey(p.src) === sourceRelKey(m.src)
|
|
358
|
+
&& p.basisColumn.toLowerCase() === m.column.toLowerCase());
|
|
359
|
+
if (proj) {
|
|
360
|
+
logical = proj.logical;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!logical)
|
|
365
|
+
continue; // key not surfaced as a logical column → leave to multi-source
|
|
366
|
+
for (const m of cls)
|
|
367
|
+
add(m.src, m.column, logical);
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Collects join-key equivalence classes from a body's FROM. Each `using (cols)`
|
|
373
|
+
* join contributes, per column, the equated key columns across its left/right
|
|
374
|
+
* subtree sources; each `on a.x = b.y` conjunct contributes that pair. Overlapping
|
|
375
|
+
* classes are harmless — threading dedups per relation.
|
|
376
|
+
*/
|
|
377
|
+
function collectJoinKeyEquivalences(from, basisSchemaName, schemaManager) {
|
|
378
|
+
const classes = [];
|
|
379
|
+
if (!from)
|
|
380
|
+
return classes;
|
|
381
|
+
const resolveTableSources = (node) => {
|
|
382
|
+
const out = [];
|
|
383
|
+
const walk = (n) => {
|
|
384
|
+
if (n.type === 'table') {
|
|
385
|
+
const schemaName = n.table.schema ?? basisSchemaName;
|
|
386
|
+
const tbl = schemaManager.getSchema(schemaName)?.getTable(n.table.name);
|
|
387
|
+
if (tbl)
|
|
388
|
+
out.push({ table: tbl, refName: n.alias ?? tbl.name });
|
|
389
|
+
}
|
|
390
|
+
else if (n.type === 'join') {
|
|
391
|
+
walk(n.left);
|
|
392
|
+
walk(n.right);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
walk(node);
|
|
396
|
+
return out;
|
|
397
|
+
};
|
|
398
|
+
const memberFor = (src, colName) => {
|
|
399
|
+
const idx = src.table.columnIndexMap.get(colName.toLowerCase());
|
|
400
|
+
if (idx === undefined)
|
|
401
|
+
return undefined;
|
|
402
|
+
return { src, column: src.table.columns[idx].name };
|
|
403
|
+
};
|
|
404
|
+
const walkJoin = (node) => {
|
|
405
|
+
if (node.type !== 'join')
|
|
406
|
+
return;
|
|
407
|
+
walkJoin(node.left);
|
|
408
|
+
walkJoin(node.right);
|
|
409
|
+
const subtree = [...resolveTableSources(node.left), ...resolveTableSources(node.right)];
|
|
410
|
+
if (node.columns) {
|
|
411
|
+
// USING (cols): equate each named column across every subtree source carrying it.
|
|
412
|
+
for (const col of node.columns) {
|
|
413
|
+
const members = subtree.map(s => memberFor(s, col)).filter((m) => m !== undefined);
|
|
414
|
+
if (members.length > 1)
|
|
415
|
+
classes.push(members);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (node.condition) {
|
|
419
|
+
// ON a.x = b.y [and ...]: best-effort equality-conjunct extraction.
|
|
420
|
+
const byRef = new Map();
|
|
421
|
+
for (const s of subtree)
|
|
422
|
+
byRef.set(s.refName.toLowerCase(), s);
|
|
423
|
+
for (const [left, right] of collectEqualityPairs(node.condition)) {
|
|
424
|
+
if (!left.table || !right.table)
|
|
425
|
+
continue;
|
|
426
|
+
const ls = byRef.get(left.table.toLowerCase());
|
|
427
|
+
const rs = byRef.get(right.table.toLowerCase());
|
|
428
|
+
if (!ls || !rs)
|
|
429
|
+
continue;
|
|
430
|
+
const lm = memberFor(ls, left.name);
|
|
431
|
+
const rm = memberFor(rs, right.name);
|
|
432
|
+
if (lm && rm)
|
|
433
|
+
classes.push([lm, rm]);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
for (const f of from)
|
|
438
|
+
walkJoin(f);
|
|
439
|
+
return classes;
|
|
440
|
+
}
|
|
441
|
+
/** Extracts `col = col` conjuncts from an ON condition (best-effort, AND-only). */
|
|
442
|
+
function collectEqualityPairs(expr) {
|
|
443
|
+
const pairs = [];
|
|
444
|
+
const walk = (e) => {
|
|
445
|
+
if (e.type !== 'binary')
|
|
446
|
+
return;
|
|
447
|
+
const bin = e;
|
|
448
|
+
if (bin.operator.toUpperCase() === 'AND') {
|
|
449
|
+
walk(bin.left);
|
|
450
|
+
walk(bin.right);
|
|
451
|
+
}
|
|
452
|
+
else if (bin.operator === '=' && bin.left.type === 'column' && bin.right.type === 'column') {
|
|
453
|
+
pairs.push([bin.left, bin.right]);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
walk(expr);
|
|
457
|
+
return pairs;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The basis-relation keys (`schema.table`, lowercased) of a slot's surrogate-keyed
|
|
461
|
+
* decomposition members, or undefined when there is no advertisement / the shared
|
|
462
|
+
* key is a logical-tuple. Lets the differ defer an unsound multi-member surrogate
|
|
463
|
+
* split (see `docs/lens.md` § The Default Mapper — evaluate-once-and-thread).
|
|
464
|
+
*/
|
|
465
|
+
function deriveSurrogateMemberKeys(slot, basis) {
|
|
466
|
+
const storage = slot.advertisement?.storage;
|
|
467
|
+
if (!storage || storage.sharedKey.kind !== 'surrogate')
|
|
468
|
+
return undefined;
|
|
469
|
+
const keys = new Set();
|
|
470
|
+
for (const m of storage.members) {
|
|
471
|
+
const schemaName = (m.relation.schema || basis.schemaName).toLowerCase();
|
|
472
|
+
keys.add(`${schemaName}.${m.relation.table.toLowerCase()}`);
|
|
473
|
+
}
|
|
474
|
+
return keys;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* PoC wiring for `reserved-tags.ts`: validates the reserved `quereus.*` tag
|
|
478
|
+
* namespace on one lens slot's logical table (`logical-table` site), each of its
|
|
479
|
+
* logical columns (`logical-column` site — the home of `quereus.lens.writable`),
|
|
480
|
+
* and each of its attached constraints (`logical-constraint` site).
|
|
481
|
+
*
|
|
482
|
+
* Severity policy is the registry's; this caller only routes it: a
|
|
483
|
+
* `severity:'error'` diagnostic (unknown key, mis-sited key, malformed enum/CSV
|
|
484
|
+
* value) throws a {@link QuereusError} with the sited message — consistent with
|
|
485
|
+
* the other compile-time lens errors, and atomic because validation runs before
|
|
486
|
+
* catalog mutation. `severity:'warning'` diagnostics (e.g. an empty
|
|
487
|
+
* `quereus.lens.ack` rationale) are logged via the existing `log` channel; the
|
|
488
|
+
* deploy-summary warning channel (`docs/lens.md:169`) is `3-lens-prover` Phase
|
|
489
|
+
* C's to build. Errors take precedence — warnings only log when none fail.
|
|
490
|
+
*
|
|
491
|
+
* This validates shape/site only — no reserved tag carries behavior read here.
|
|
492
|
+
*/
|
|
493
|
+
function validateLensTags(slot) {
|
|
494
|
+
const diagnostics = [
|
|
495
|
+
...validateReservedTags(slot.logicalTable.tags, 'logical-table'),
|
|
496
|
+
];
|
|
497
|
+
// Each logical column's tags at the `logical-column` site (the home of
|
|
498
|
+
// `quereus.lens.writable`). This also closes a pre-existing gap: a typo'd /
|
|
499
|
+
// mis-sited `quereus.*` key on a logical column was previously never validated.
|
|
500
|
+
for (const col of slot.logicalTable.columns) {
|
|
501
|
+
if (col.tags)
|
|
502
|
+
diagnostics.push(...validateReservedTags(col.tags, 'logical-column'));
|
|
503
|
+
}
|
|
504
|
+
for (const constraint of slot.attachedConstraints) {
|
|
505
|
+
const tags = constraint.kind === 'primaryKey' ? undefined : constraint.constraint.tags;
|
|
506
|
+
if (tags)
|
|
507
|
+
diagnostics.push(...validateReservedTags(tags, 'logical-constraint'));
|
|
508
|
+
}
|
|
509
|
+
raiseReservedTagDiagnostics(diagnostics, {
|
|
510
|
+
log: (diag) => log('lens advisory (%s) on %s.%s: %s', diag.reason, slot.logicalTable.schemaName, slot.logicalTable.name, diag.message),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Rejects every physical construct under a logical declared schema, naming the
|
|
515
|
+
* offending construct and the logical-schema context. Tags are allowed (they
|
|
516
|
+
* are engine-facing and survive into the compiled view).
|
|
517
|
+
*/
|
|
518
|
+
export function validateLogicalDeclaration(declaredSchema, logicalSchemaName) {
|
|
519
|
+
const ctx = `logical schema '${logicalSchemaName}'`;
|
|
520
|
+
for (const item of declaredSchema.items) {
|
|
521
|
+
switch (item.type) {
|
|
522
|
+
case 'declaredTable': {
|
|
523
|
+
if (item.tableStmt.moduleName) {
|
|
524
|
+
throw new QuereusError(`lens: module association 'using ${item.tableStmt.moduleName}(...)' on table '${item.tableStmt.table.name}' is not allowed in ${ctx}; logical tables declare columns and constraints only`, StatusCode.ERROR);
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case 'declaredIndex': {
|
|
529
|
+
const kind = item.indexStmt.isUnique ? 'unique index' : 'index';
|
|
530
|
+
throw new QuereusError(`lens: ${kind} '${item.indexStmt.index.name}' is not allowed in ${ctx}; indexes are a basis-layer construct (a materialized view over the basis)`, StatusCode.ERROR);
|
|
531
|
+
}
|
|
532
|
+
case 'declaredMaterializedView': {
|
|
533
|
+
throw new QuereusError(`lens: materialized view '${item.viewStmt.view.name}' is not allowed in ${ctx}; materialized views are a basis-layer construct`, StatusCode.ERROR);
|
|
534
|
+
}
|
|
535
|
+
// declaredView / declaredSeed / declaredAssertion / declareIgnored:
|
|
536
|
+
// not part of the logical-table surface in v1 — neither rejected nor
|
|
537
|
+
// processed by the lens compiler (only declaredTable becomes a slot).
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Infers the default basis for a logical schema (MVP binding — there is no
|
|
543
|
+
* `declare lens for X over Y` yet). The basis is the single registered
|
|
544
|
+
* **physical** schema that contains ≥1 table, excluding the logical schema
|
|
545
|
+
* itself and `temp`. See `docs/lens.md` § Default-basis inference.
|
|
546
|
+
*/
|
|
547
|
+
export function inferDefaultBasis(schemaManager, logicalSchemaName) {
|
|
548
|
+
const lowerLogical = logicalSchemaName.toLowerCase();
|
|
549
|
+
const candidates = [];
|
|
550
|
+
for (const schema of schemaManager._getAllSchemas()) {
|
|
551
|
+
const lowerName = schema.name.toLowerCase();
|
|
552
|
+
if (lowerName === lowerLogical)
|
|
553
|
+
continue;
|
|
554
|
+
if (lowerName === 'temp')
|
|
555
|
+
continue;
|
|
556
|
+
if (schema.kind !== 'physical')
|
|
557
|
+
continue;
|
|
558
|
+
let hasTable = false;
|
|
559
|
+
for (const _t of schema.getAllTables()) {
|
|
560
|
+
hasTable = true;
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
if (!hasTable)
|
|
564
|
+
continue;
|
|
565
|
+
candidates.push({ schema, schemaName: schema.name });
|
|
566
|
+
}
|
|
567
|
+
if (candidates.length === 1) {
|
|
568
|
+
return candidates[0];
|
|
569
|
+
}
|
|
570
|
+
throw new QuereusError(`lens: cannot infer a default basis for logical schema '${logicalSchemaName}' (found ${candidates.length} candidates); supply 'declare lens for ${logicalSchemaName} over <basis>'`, StatusCode.ERROR);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* The default name-based aligner: produces `select <logical columns> from B.T'`
|
|
574
|
+
* for one logical table `L.T` over basis schema `B`.
|
|
575
|
+
*
|
|
576
|
+
* - The basis table is matched by name (case-insensitive).
|
|
577
|
+
* - Each logical column is matched to a basis column by name (case-insensitive).
|
|
578
|
+
* - The projection lists exactly the logical columns, in declaration order, so
|
|
579
|
+
* a basis table with extra columns is correctly projected down.
|
|
580
|
+
*
|
|
581
|
+
* The empty-key (singleton) case needs no special path: a `primary key ()`
|
|
582
|
+
* logical table over a `primary key ()` basis table is an ordinary single-source
|
|
583
|
+
* projection.
|
|
584
|
+
*/
|
|
585
|
+
export function compileDefaultBody(logicalTable, logicalSchemaName, basisSchema, basisSchemaName) {
|
|
586
|
+
const logicalName = logicalTable.name;
|
|
587
|
+
const basisTable = basisSchema.getTable(logicalName);
|
|
588
|
+
if (!basisTable) {
|
|
589
|
+
throw new QuereusError(`lens: logical table '${logicalSchemaName}.${logicalName}' has no basis backing`, StatusCode.ERROR);
|
|
590
|
+
}
|
|
591
|
+
const columns = [];
|
|
592
|
+
for (const col of logicalTable.columns) {
|
|
593
|
+
const basisColIdx = basisTable.columnIndexMap.get(col.name.toLowerCase());
|
|
594
|
+
if (basisColIdx === undefined) {
|
|
595
|
+
throw new QuereusError(`lens: logical column '${logicalSchemaName}.${logicalName}.${col.name}' has no basis backing`, StatusCode.ERROR);
|
|
596
|
+
}
|
|
597
|
+
// Single source → an unqualified column reference is unambiguous. Reference
|
|
598
|
+
// the basis column by its actual name; the consumer-facing column *names*
|
|
599
|
+
// (and casing) are pinned to the logical declaration via the registered
|
|
600
|
+
// view's explicit column list (see `deployLogicalSchema`), so the basis
|
|
601
|
+
// spelling never leaks through `select * from Logical.T`.
|
|
602
|
+
const basisColName = basisTable.columns[basisColIdx].name;
|
|
603
|
+
columns.push({
|
|
604
|
+
type: 'column',
|
|
605
|
+
expr: { type: 'column', name: basisColName },
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
type: 'select',
|
|
610
|
+
columns,
|
|
611
|
+
from: [{
|
|
612
|
+
type: 'table',
|
|
613
|
+
table: { type: 'identifier', name: basisTable.name, schema: basisSchemaName },
|
|
614
|
+
}],
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
// ===========================================================================
|
|
618
|
+
// n-way decomposition synthesis (docs/lens.md § The Default Mapper)
|
|
619
|
+
// ===========================================================================
|
|
620
|
+
//
|
|
621
|
+
// When a logical table is backed by a resolved primary-storage advertisement,
|
|
622
|
+
// the body producer synthesizes the `get` join instead of the single-source
|
|
623
|
+
// name aligner: a left-deep equi-join rooted at the existence anchor, mandatory
|
|
624
|
+
// members inner-joined and optional members outer-joined, EAV pivot members
|
|
625
|
+
// projected as correlated scalar subqueries (not joined), and the projection
|
|
626
|
+
// resolving each logical column to its advertised backing expression. Read
|
|
627
|
+
// direction only — the `put` fan-out + IND injection are sibling tickets; a
|
|
628
|
+
// multi-source body remains write-rejected by view-updateability.
|
|
629
|
+
/**
|
|
630
|
+
* Synthesizes the n-way `get` body for a logical table backed by a resolved
|
|
631
|
+
* primary-storage advertisement.
|
|
632
|
+
*
|
|
633
|
+
* The synthesized FROM is a left-deep join tree rooted at `anchorRelationId`:
|
|
634
|
+
* every other non-EAV member is inner-joined (`presence:'mandatory'`) or
|
|
635
|
+
* outer-joined (`presence:'optional'`) onto the anchor via a positional
|
|
636
|
+
* key-equi-join over `sharedKey.keyColumnsByRelation`. Optional members are
|
|
637
|
+
* outer-joined so a logical row missing an optional component survives with that
|
|
638
|
+
* component's columns null (inner-joining everywhere would silently drop rows).
|
|
639
|
+
*
|
|
640
|
+
* The empty-key (singleton) case is not a special path: an empty per-member key
|
|
641
|
+
* column list makes the equi-join conjunction vacuously true (`on 1 = 1`), so a
|
|
642
|
+
* `primary key ()` table over a 0-or-1-row anchor reads 0-or-1 row.
|
|
643
|
+
*
|
|
644
|
+
* EAV pivot members are NOT join members (joining a triple store would multiply
|
|
645
|
+
* rows); each EAV-backed logical column is projected as a correlated scalar
|
|
646
|
+
* subquery keyed by the attribute literal.
|
|
647
|
+
*/
|
|
648
|
+
function compileDecompositionBody(logicalTable, logicalSchemaName, basis, advertisement, db) {
|
|
649
|
+
const storage = advertisement.storage;
|
|
650
|
+
if (!storage) {
|
|
651
|
+
// A primary advertisement always carries a storage shape (validated at
|
|
652
|
+
// resolution); guard defensively rather than emit an unsound body.
|
|
653
|
+
throw new QuereusError(`lens: decomposition for logical table '${logicalSchemaName}.${logicalTable.name}' has a primary advertisement with no storage shape`, StatusCode.ERROR);
|
|
654
|
+
}
|
|
655
|
+
const logicalName = logicalTable.name;
|
|
656
|
+
// Resolve every member's basis table (re-resolved here for the synthesized
|
|
657
|
+
// FROM's actual table name + schema casing; existence is validated already).
|
|
658
|
+
const memberTables = new Map();
|
|
659
|
+
for (const member of storage.members) {
|
|
660
|
+
const table = resolveBasisRelation(db, member, basis);
|
|
661
|
+
if (!table) {
|
|
662
|
+
throw new QuereusError(`lens: decomposition for logical table '${logicalSchemaName}.${logicalName}' references basis relation '${member.relation.schema}.${member.relation.table}' (member '${member.relationId}'), which does not exist`, StatusCode.ERROR);
|
|
663
|
+
}
|
|
664
|
+
memberTables.set(member.relationId, table);
|
|
665
|
+
}
|
|
666
|
+
const anchor = storage.members.find(m => m.relationId === storage.anchorRelationId);
|
|
667
|
+
if (!anchor) {
|
|
668
|
+
throw new QuereusError(`lens: decomposition for logical table '${logicalSchemaName}.${logicalName}' names anchor '${storage.anchorRelationId}', which is not among the members`, StatusCode.ERROR);
|
|
669
|
+
}
|
|
670
|
+
const anchorTable = memberTables.get(anchor.relationId);
|
|
671
|
+
const anchorKeys = storage.sharedKey.keyColumnsByRelation.get(anchor.relationId) ?? [];
|
|
672
|
+
// The join set: the anchor (root) + every non-EAV member. EAV pivot members
|
|
673
|
+
// back columns via a correlated subquery, never a join.
|
|
674
|
+
const joinedMembers = new Set([anchor.relationId]);
|
|
675
|
+
for (const member of storage.members) {
|
|
676
|
+
if (member.relationId === anchor.relationId)
|
|
677
|
+
continue;
|
|
678
|
+
if (member.attributePivot)
|
|
679
|
+
continue; // EAV → subquery, not joined
|
|
680
|
+
joinedMembers.add(member.relationId);
|
|
681
|
+
}
|
|
682
|
+
// Build the left-deep join tree, anchor first.
|
|
683
|
+
let from = memberTableSource(anchorTable, basis.schemaName, anchor.relationId);
|
|
684
|
+
for (const member of storage.members) {
|
|
685
|
+
if (!joinedMembers.has(member.relationId) || member.relationId === anchor.relationId)
|
|
686
|
+
continue;
|
|
687
|
+
const memberTable = memberTables.get(member.relationId);
|
|
688
|
+
const memberKeys = storage.sharedKey.keyColumnsByRelation.get(member.relationId) ?? [];
|
|
689
|
+
from = {
|
|
690
|
+
type: 'join',
|
|
691
|
+
joinType: member.presence === 'mandatory' ? 'inner' : 'left',
|
|
692
|
+
left: from,
|
|
693
|
+
right: memberTableSource(memberTable, basis.schemaName, member.relationId),
|
|
694
|
+
condition: buildKeyEquiJoin(anchor.relationId, anchorKeys, member.relationId, memberKeys),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
// Projection: each logical column → its advertised backing expression.
|
|
698
|
+
const aliasOf = (relationId) => joinedMembers.has(relationId) ? relationId : undefined;
|
|
699
|
+
const eavAnchor = anchorKeys.length > 0
|
|
700
|
+
? { alias: anchor.relationId, keyColumn: anchorKeys[0] }
|
|
701
|
+
: undefined;
|
|
702
|
+
const columns = [];
|
|
703
|
+
for (const col of logicalTable.columns) {
|
|
704
|
+
const res = resolveAdvertisedColumn(col.name, storage, basis.schemaName, aliasOf, eavAnchor);
|
|
705
|
+
let expr;
|
|
706
|
+
if (res.kind === 'expr') {
|
|
707
|
+
expr = res.expr;
|
|
708
|
+
}
|
|
709
|
+
else if (res.kind === 'unreachable') {
|
|
710
|
+
throw new QuereusError(`lens: decomposition for logical table '${logicalSchemaName}.${logicalName}' maps column '${col.name}' to member '${res.member}', which is not part of the synthesized join (an EAV pivot member backs columns through its attribute pivot, not a direct column mapping)`, StatusCode.ERROR);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
// Name-match against a join member (anchor first), qualified by the
|
|
714
|
+
// member's alias — the decomposition analogue of the single-source path.
|
|
715
|
+
const nm = nameMatchAgainstMembers(col.name, storage, memberTables, joinedMembers, anchor.relationId);
|
|
716
|
+
if (!nm) {
|
|
717
|
+
throw new QuereusError(`lens: decomposition for logical table '${logicalSchemaName}.${logicalName}' cannot resolve column '${col.name}': it is not mapped by any advertised member, not EAV-backed, and no decomposition member has a same-named column`, StatusCode.ERROR);
|
|
718
|
+
}
|
|
719
|
+
expr = nm;
|
|
720
|
+
}
|
|
721
|
+
columns.push({ type: 'column', expr, alias: col.name });
|
|
722
|
+
}
|
|
723
|
+
return { type: 'select', columns, from: [from] };
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Computes the existence-anchor inclusion dependencies for a decomposition
|
|
727
|
+
* (`lens-multi-source-ind-injection`, docs/lens.md § The module mapping
|
|
728
|
+
* advertisement). For each **mandatory**, non-anchor, non-EAV member, injects one
|
|
729
|
+
* IND asserting the existence **anchor's** shared-key tuple is included in that
|
|
730
|
+
* member's key — the propagated fact that lets the prover discharge the
|
|
731
|
+
* no-row-loss obligation of the anchor-rooted inner join against a threaded
|
|
732
|
+
* existence fact rather than re-deriving decomposition structure. The surrogate
|
|
733
|
+
* join carries no declared SQL FK, so `seedTableForeignKeyInds` / `lookupCoveringFK`
|
|
734
|
+
* are structurally blind to it; this is the `IndTarget.kind:'relation'` producer
|
|
735
|
+
* reserved by Wave 1.
|
|
736
|
+
*
|
|
737
|
+
* Direction + soundness. `compileDecompositionBody` builds a left-deep join
|
|
738
|
+
* **rooted at the anchor**, inner-joining each mandatory member (`anchor ⋈ member`);
|
|
739
|
+
* the logical entities are the anchor rows (one per logical row). The no-row-loss
|
|
740
|
+
* obligation the prover discharges is therefore "no anchor row is dropped"
|
|
741
|
+
* (`tSide = anchor`, `lookup = member`), which it reads from an IND **on the
|
|
742
|
+
* anchor** of the form `anchor.key ⊆ member.key`. `presence:'mandatory'` ("every
|
|
743
|
+
* logical row has it") guarantees exactly that: every anchor (= logical) row has a
|
|
744
|
+
* matching member row on the shared key ⇒ `anchor.key ⊆ member.key`, total
|
|
745
|
+
* (`nullRejecting:false`) — exactly the existence fact the anchor-rooted inner
|
|
746
|
+
* join's row-preservation obligation needs. The converse (`member ⊆ anchor`) is
|
|
747
|
+
* **intentionally not asserted**: no stated property guarantees member→anchor
|
|
748
|
+
* referential integrity, so emitting it would over-claim (a mandatory-member row
|
|
749
|
+
* whose key is absent from the anchor is simply filtered by the inner join — reads
|
|
750
|
+
* stay correct, but the converse fact would be false).
|
|
751
|
+
*
|
|
752
|
+
* Guards (over-claim is unsound — Wave 1's § Enforcement readiness):
|
|
753
|
+
* - **mandatory only** — an optional member is outer-joined; its absence is
|
|
754
|
+
* exactly what the outer join preserves, so an IND would over-claim.
|
|
755
|
+
* - **total only** (`nullRejecting:false`) — a mandatory member's existence is
|
|
756
|
+
* total: every logical row has it.
|
|
757
|
+
* - **EAV pivots excluded** — they are projected as correlated subqueries, never
|
|
758
|
+
* inner-joined, so there is no row-loss obligation to discharge.
|
|
759
|
+
* - **empty key → none** — a singleton (`primary key ()`) has no witnessing key
|
|
760
|
+
* tuple; existence is the anchor's own 0-or-1-row property.
|
|
761
|
+
*
|
|
762
|
+
* `cols` are the **anchor's** shared-key column indices on the anchor's basis
|
|
763
|
+
* relation; `target.targetCols` the **member's** key column indices on the member's
|
|
764
|
+
* basis relation; `target.relationId` is the member (not the anchor). Anchor/member
|
|
765
|
+
* key columns pair positionally, matching the get-synthesis equi-join. Uses `addInd`
|
|
766
|
+
* + `MAX_INDS_PER_NODE` for dedup/cap consistency with the Wave-1 FK seeding;
|
|
767
|
+
* multiple mandatory members produce distinct INDs (same `cols` = anchor key,
|
|
768
|
+
* different `target.relationId`), so dedup is unaffected.
|
|
769
|
+
*/
|
|
770
|
+
function computeExistenceAnchorInds(advertisement, basis, db) {
|
|
771
|
+
const storage = advertisement.storage;
|
|
772
|
+
if (!storage)
|
|
773
|
+
return [];
|
|
774
|
+
const anchor = storage.members.find(m => m.relationId === storage.anchorRelationId);
|
|
775
|
+
if (!anchor)
|
|
776
|
+
return []; // validated at resolution; defensive
|
|
777
|
+
const anchorTable = resolveBasisRelation(db, anchor, basis);
|
|
778
|
+
if (!anchorTable)
|
|
779
|
+
return []; // validated at resolution; defensive
|
|
780
|
+
const anchorKeyIdx = mapKeyColumnsToIndices(storage.sharedKey.keyColumnsByRelation.get(anchor.relationId) ?? [], anchorTable);
|
|
781
|
+
// Empty anchor key (singleton / `primary key ()`): no witnessing tuple to thread.
|
|
782
|
+
if (!anchorKeyIdx || anchorKeyIdx.length === 0)
|
|
783
|
+
return [];
|
|
784
|
+
let inds = [];
|
|
785
|
+
for (const member of storage.members) {
|
|
786
|
+
if (member.relationId === anchor.relationId)
|
|
787
|
+
continue;
|
|
788
|
+
if (member.presence !== 'mandatory')
|
|
789
|
+
continue; // optional → outer-joined → no IND (over-claim guard)
|
|
790
|
+
if (member.attributePivot)
|
|
791
|
+
continue; // EAV pivot → projected as subquery, never inner-joined
|
|
792
|
+
const memberTable = resolveBasisRelation(db, member, basis);
|
|
793
|
+
if (!memberTable)
|
|
794
|
+
continue; // validated at resolution; defensive
|
|
795
|
+
const memberKeyIdx = mapKeyColumnsToIndices(storage.sharedKey.keyColumnsByRelation.get(member.relationId) ?? [], memberTable);
|
|
796
|
+
if (!memberKeyIdx)
|
|
797
|
+
continue;
|
|
798
|
+
// Pair positionally up to the shorter list (matching the get-synthesis equi-join).
|
|
799
|
+
const n = Math.min(memberKeyIdx.length, anchorKeyIdx.length);
|
|
800
|
+
if (n === 0)
|
|
801
|
+
continue; // member has no key tuple to witness inclusion
|
|
802
|
+
// `anchor.key ⊆ member.key`, total: THIS = anchor (cols = anchor key indices),
|
|
803
|
+
// target = the MEMBER (the totality direction `mandatory` guarantees and the
|
|
804
|
+
// anchor-rooted inner join's no-row-loss obligation consumes). See doc above.
|
|
805
|
+
inds = addInd(inds, {
|
|
806
|
+
cols: anchorKeyIdx.slice(0, n),
|
|
807
|
+
target: { kind: 'relation', relationId: member.relationId, targetCols: memberKeyIdx.slice(0, n) },
|
|
808
|
+
nullRejecting: false,
|
|
809
|
+
}, { cap: MAX_INDS_PER_NODE });
|
|
810
|
+
}
|
|
811
|
+
return inds;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Maps shared-key column names to their indices on `table`, preserving order.
|
|
815
|
+
* Returns undefined if any column is missing — the advertisement validator
|
|
816
|
+
* already guarantees existence, so this is a defensive guard.
|
|
817
|
+
*/
|
|
818
|
+
function mapKeyColumnsToIndices(keys, table) {
|
|
819
|
+
const idx = [];
|
|
820
|
+
for (const k of keys) {
|
|
821
|
+
const i = table.columnIndexMap.get(k.toLowerCase());
|
|
822
|
+
if (i === undefined)
|
|
823
|
+
return undefined;
|
|
824
|
+
idx.push(i);
|
|
825
|
+
}
|
|
826
|
+
return idx;
|
|
827
|
+
}
|
|
828
|
+
/** A FROM `table` source for one decomposition member, aliased by its relationId. */
|
|
829
|
+
function memberTableSource(table, basisSchemaName, alias) {
|
|
830
|
+
return {
|
|
831
|
+
type: 'table',
|
|
832
|
+
table: { type: 'identifier', name: table.name, schema: table.schemaName || basisSchemaName },
|
|
833
|
+
alias,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Builds the per-member key-equi-join ON condition: a positional conjunction of
|
|
838
|
+
* `member.kᵢ = anchor.kᵢ` over the two relations' shared-key column lists (paired
|
|
839
|
+
* by index, since a surrogate may be spelled differently per relation). An empty
|
|
840
|
+
* key column list (the `primary key ()` singleton) yields the vacuously-true
|
|
841
|
+
* `1 = 1` — no singleton-specific branch (docs/lens.md § The Default Mapper).
|
|
842
|
+
*/
|
|
843
|
+
function buildKeyEquiJoin(anchorAlias, anchorKeys, memberAlias, memberKeys) {
|
|
844
|
+
const n = Math.min(anchorKeys.length, memberKeys.length);
|
|
845
|
+
const eqs = [];
|
|
846
|
+
for (let i = 0; i < n; i++) {
|
|
847
|
+
eqs.push({
|
|
848
|
+
type: 'binary',
|
|
849
|
+
operator: '=',
|
|
850
|
+
left: { type: 'column', name: memberKeys[i], table: memberAlias },
|
|
851
|
+
right: { type: 'column', name: anchorKeys[i], table: anchorAlias },
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
if (eqs.length === 0) {
|
|
855
|
+
// Singleton / empty key: vacuously true (`on 1 = 1`).
|
|
856
|
+
return {
|
|
857
|
+
type: 'binary',
|
|
858
|
+
operator: '=',
|
|
859
|
+
left: { type: 'literal', value: 1 },
|
|
860
|
+
right: { type: 'literal', value: 1 },
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
return eqs.reduce((acc, e) => ({ type: 'binary', operator: 'AND', left: acc, right: e }));
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Resolves one logical column to its advertised backing expression, re-qualified
|
|
867
|
+
* to the caller-supplied alias for the backing member. Shared by the pure
|
|
868
|
+
* decomposition body (`compileDecompositionBody`) and the override gap-fill path
|
|
869
|
+
* (`compileOverrideBody`), so both honor the same precedence:
|
|
870
|
+
*
|
|
871
|
+
* 1. **Explicit per-member mapping** wins → the mapped `basisExpr`, re-qualified
|
|
872
|
+
* to the member's alias. When the member is not reachable from the caller's
|
|
873
|
+
* FROM (`aliasOf` returns undefined), reports `unreachable`.
|
|
874
|
+
* 2. **EAV attribute pivot** (exactly one pivot member + an entity correlation
|
|
875
|
+
* key) → a correlated scalar subquery keyed by the attribute literal.
|
|
876
|
+
* 3. Otherwise `none` — the caller falls back to its own name-match path.
|
|
877
|
+
*
|
|
878
|
+
* Matches `annotateProvenanceWithAdvertisement`'s attribution order (explicit
|
|
879
|
+
* mapping, then the sole EAV member), so the synthesized projection and the
|
|
880
|
+
* `quereus_effective_lens` provenance stay consistent.
|
|
881
|
+
*/
|
|
882
|
+
function resolveAdvertisedColumn(logicalColumn, storage, basisSchemaName, aliasOf, eavAnchor) {
|
|
883
|
+
const lc = logicalColumn.toLowerCase();
|
|
884
|
+
// 1. Explicit per-member mapping.
|
|
885
|
+
for (const member of storage.members) {
|
|
886
|
+
const mapping = member.columns.find(m => m.logicalColumn.toLowerCase() === lc);
|
|
887
|
+
if (!mapping)
|
|
888
|
+
continue;
|
|
889
|
+
const alias = aliasOf(member.relationId);
|
|
890
|
+
if (!alias)
|
|
891
|
+
return { kind: 'unreachable', member: member.relationId };
|
|
892
|
+
return { kind: 'expr', expr: requalifyColumnRefs(mapping.basisExpr, alias) };
|
|
893
|
+
}
|
|
894
|
+
// 2. EAV attribute pivot — a correlated scalar subquery keyed by the attribute
|
|
895
|
+
// literal (only with exactly one pivot member + an entity correlation key).
|
|
896
|
+
const eavMembers = storage.members.filter(m => m.attributePivot);
|
|
897
|
+
if (eavMembers.length === 1 && eavAnchor) {
|
|
898
|
+
return { kind: 'expr', expr: buildEavSubquery(eavMembers[0], logicalColumn, basisSchemaName, eavAnchor) };
|
|
899
|
+
}
|
|
900
|
+
// 3. Not advertised → caller falls back to name-match.
|
|
901
|
+
return { kind: 'none' };
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Builds the correlated scalar subquery for one EAV-backed logical column:
|
|
905
|
+
* `(select p.<value> from <pivot> p where p.<entity> = anchor.<key> and
|
|
906
|
+
* p.<attribute> = '<logicalColumn>')`. Keeps every EAV column independently
|
|
907
|
+
* nullable (a logical row may have a triple for some attributes and not others)
|
|
908
|
+
* and rides the existing scalar-subquery read path with no new runtime.
|
|
909
|
+
*/
|
|
910
|
+
function buildEavSubquery(pivot, logicalColumn, basisSchemaName, eavAnchor) {
|
|
911
|
+
const piv = pivot.attributePivot;
|
|
912
|
+
const pivotAlias = pivot.relationId;
|
|
913
|
+
const pivotSchema = pivot.relation.schema || basisSchemaName;
|
|
914
|
+
const query = {
|
|
915
|
+
type: 'select',
|
|
916
|
+
columns: [{ type: 'column', expr: { type: 'column', name: piv.valueColumn, table: pivotAlias } }],
|
|
917
|
+
from: [{
|
|
918
|
+
type: 'table',
|
|
919
|
+
table: { type: 'identifier', name: pivot.relation.table, schema: pivotSchema },
|
|
920
|
+
alias: pivotAlias,
|
|
921
|
+
}],
|
|
922
|
+
where: {
|
|
923
|
+
type: 'binary',
|
|
924
|
+
operator: 'AND',
|
|
925
|
+
left: {
|
|
926
|
+
type: 'binary',
|
|
927
|
+
operator: '=',
|
|
928
|
+
left: { type: 'column', name: piv.entityColumn, table: pivotAlias },
|
|
929
|
+
right: { type: 'column', name: eavAnchor.keyColumn, table: eavAnchor.alias },
|
|
930
|
+
},
|
|
931
|
+
right: {
|
|
932
|
+
type: 'binary',
|
|
933
|
+
operator: '=',
|
|
934
|
+
left: { type: 'column', name: piv.attributeColumn, table: pivotAlias },
|
|
935
|
+
right: { type: 'literal', value: logicalColumn },
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
return { type: 'subquery', query };
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Deep-clones a member's `basisExpr` and re-qualifies every `column` reference in
|
|
943
|
+
* it to `alias` (the member's alias in the synthesized FROM). The stored mapping
|
|
944
|
+
* references the member's own columns, possibly bare or self-qualified; this
|
|
945
|
+
* rewrites them all to the alias so the projection is unambiguous over the join.
|
|
946
|
+
* Reuses the reflective walk shape of `collectColumnRefNames`, rewriting rather
|
|
947
|
+
* than collecting.
|
|
948
|
+
*/
|
|
949
|
+
function requalifyColumnRefs(expr, alias) {
|
|
950
|
+
const rewrite = (node) => {
|
|
951
|
+
if (Array.isArray(node))
|
|
952
|
+
return node.map(rewrite);
|
|
953
|
+
if (node && typeof node === 'object' && 'type' in node) {
|
|
954
|
+
const src = node;
|
|
955
|
+
const out = {};
|
|
956
|
+
for (const key of Object.keys(src))
|
|
957
|
+
out[key] = rewrite(src[key]);
|
|
958
|
+
if (src.type === 'column') {
|
|
959
|
+
out.table = alias;
|
|
960
|
+
out.schema = undefined;
|
|
961
|
+
}
|
|
962
|
+
return out;
|
|
963
|
+
}
|
|
964
|
+
return node;
|
|
965
|
+
};
|
|
966
|
+
return rewrite(expr);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Resolves a name-match logical column against the decomposition's join members
|
|
970
|
+
* (anchor first, then advertisement order): the first member whose basis table
|
|
971
|
+
* carries a same-named column, qualified by that member's alias. Returns
|
|
972
|
+
* undefined when no join member has the column (the caller errors precisely).
|
|
973
|
+
*/
|
|
974
|
+
function nameMatchAgainstMembers(logicalColumn, storage, memberTables, joinedMembers, anchorRelationId) {
|
|
975
|
+
const lc = logicalColumn.toLowerCase();
|
|
976
|
+
const order = [anchorRelationId, ...storage.members.map(m => m.relationId).filter(id => id !== anchorRelationId)];
|
|
977
|
+
for (const relationId of order) {
|
|
978
|
+
if (!joinedMembers.has(relationId))
|
|
979
|
+
continue;
|
|
980
|
+
const table = memberTables.get(relationId);
|
|
981
|
+
const idx = table?.columnIndexMap.get(lc);
|
|
982
|
+
if (idx !== undefined) {
|
|
983
|
+
return { type: 'column', name: table.columns[idx].name, table: relationId };
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return undefined;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Indexes a lens block's overrides by lowercased logical-table name, validating
|
|
990
|
+
* that each names a logical table the declaration actually carries. The check
|
|
991
|
+
* is skipped for an empty declaration (a pure detach-everything re-apply, which
|
|
992
|
+
* never aligns a basis).
|
|
993
|
+
*/
|
|
994
|
+
function indexOverrides(lensDecl, declaredSchema, logicalSchemaName) {
|
|
995
|
+
const byTable = new Map();
|
|
996
|
+
if (!lensDecl)
|
|
997
|
+
return byTable;
|
|
998
|
+
const declaredTableNames = new Set();
|
|
999
|
+
for (const item of declaredSchema.items) {
|
|
1000
|
+
if (item.type === 'declaredTable')
|
|
1001
|
+
declaredTableNames.add(item.tableStmt.table.name.toLowerCase());
|
|
1002
|
+
}
|
|
1003
|
+
for (const ov of lensDecl.overrides) {
|
|
1004
|
+
const key = ov.table.toLowerCase();
|
|
1005
|
+
if (declaredTableNames.size > 0 && !declaredTableNames.has(key)) {
|
|
1006
|
+
throw new QuereusError(`lens: override 'view ${ov.table} as ...' references logical table '${logicalSchemaName}.${ov.table}', which is not declared in logical schema '${logicalSchemaName}'`, StatusCode.ERROR);
|
|
1007
|
+
}
|
|
1008
|
+
byTable.set(key, ov);
|
|
1009
|
+
}
|
|
1010
|
+
return byTable;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Composes one effective **read** body for a logical table that has a
|
|
1014
|
+
* `declare lens` override, per docs/lens.md § D2:
|
|
1015
|
+
*
|
|
1016
|
+
* covered columns (override projection) ⊕ default-mapper gap-fill.
|
|
1017
|
+
*
|
|
1018
|
+
* Coverage is read **by name** from the override's output column names (alias or
|
|
1019
|
+
* bare name, and `*`-expansion of FROM-source columns). Each uncovered logical
|
|
1020
|
+
* column is gap-filled from a same-named basis column of the override's FROM.
|
|
1021
|
+
* When a gap is not reachable from the FROM (basis lacks the column, or a partial
|
|
1022
|
+
* cross-basis join), the compile errors rather than emit an unsound body — every
|
|
1023
|
+
* logical column must map to basis. The composition is recomputed on every deploy.
|
|
1024
|
+
*/
|
|
1025
|
+
function compileOverrideBody(logicalTable, logicalSchemaName, basisSchemaName, schemaManager, override, advertisement) {
|
|
1026
|
+
const select = override.select;
|
|
1027
|
+
const logicalName = logicalTable.name;
|
|
1028
|
+
// Every FROM source the override names must live in the declared basis — an
|
|
1029
|
+
// override referencing a *different* existing schema (e.g. `Z.Foo` while the
|
|
1030
|
+
// lens is `over Y`) would silently re-anchor the body to Z (docs/lens.md § D4).
|
|
1031
|
+
validateOverrideBasisSources(select, basisSchemaName, logicalSchemaName, logicalName);
|
|
1032
|
+
// Resolve FROM-source basis tables once — used for both `*` expansion and
|
|
1033
|
+
// gap-fill. Opaque sources (subquery / function / unresolvable table) are
|
|
1034
|
+
// tracked so a gap that actually needs them errors precisely.
|
|
1035
|
+
const { sources, hasOpaqueSource } = collectOverrideSources(select.from, basisSchemaName, schemaManager);
|
|
1036
|
+
const qualify = sources.length > 1;
|
|
1037
|
+
// Advertisement-driven gap-fill needs to map an advertised member relationId to
|
|
1038
|
+
// its reference name (alias / table name) in the override's FROM. A member the
|
|
1039
|
+
// override's FROM does not include resolves to undefined (gap-fill then falls
|
|
1040
|
+
// back to name-match, or errors precisely).
|
|
1041
|
+
const overrideSourceByRel = new Map();
|
|
1042
|
+
for (const s of sources) {
|
|
1043
|
+
overrideSourceByRel.set(`${s.table.schemaName.toLowerCase()}.${s.table.name.toLowerCase()}`, s.refName);
|
|
1044
|
+
}
|
|
1045
|
+
const overrideAliasOf = (relationId) => {
|
|
1046
|
+
const member = advertisement?.storage?.members.find(m => m.relationId === relationId);
|
|
1047
|
+
if (!member)
|
|
1048
|
+
return undefined;
|
|
1049
|
+
const key = `${(member.relation.schema || basisSchemaName).toLowerCase()}.${member.relation.table.toLowerCase()}`;
|
|
1050
|
+
return overrideSourceByRel.get(key);
|
|
1051
|
+
};
|
|
1052
|
+
// Coverage map: lowercased output-column name -> the expression producing it,
|
|
1053
|
+
// plus the override column's `with inverse` clause when authored — carried per
|
|
1054
|
+
// covered column into the composed body so the write path consumes it there
|
|
1055
|
+
// (docs/view-updateability.md § Authored inverses; a gap-filled column never
|
|
1056
|
+
// has one).
|
|
1057
|
+
const coverage = new Map();
|
|
1058
|
+
let hasStar = false;
|
|
1059
|
+
let starTable;
|
|
1060
|
+
for (const col of select.columns) {
|
|
1061
|
+
if (col.type === 'all') {
|
|
1062
|
+
hasStar = true;
|
|
1063
|
+
if (col.table)
|
|
1064
|
+
starTable = col.table.toLowerCase();
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
const outName = deriveColumnOutputName(col);
|
|
1068
|
+
if (!outName) {
|
|
1069
|
+
// A computed (non-column) projection term without an alias maps to no
|
|
1070
|
+
// logical column — it would be silently dropped, then the same-named
|
|
1071
|
+
// logical column gap-filled from the basis (a wrong, surprising read).
|
|
1072
|
+
throw new QuereusError(`lens: override for logical table '${logicalSchemaName}.${logicalName}' has a computed projection term '${astToString(col.expr)}' with no output name; add an alias (... as <name>) so it maps to a logical column`, StatusCode.ERROR);
|
|
1073
|
+
}
|
|
1074
|
+
coverage.set(outName.toLowerCase(), { expr: col.expr, inverse: col.inverse });
|
|
1075
|
+
}
|
|
1076
|
+
// `*` covers every FROM-source column not already explicitly covered.
|
|
1077
|
+
if (hasStar) {
|
|
1078
|
+
for (const src of sources) {
|
|
1079
|
+
if (starTable && src.refName.toLowerCase() !== starTable)
|
|
1080
|
+
continue;
|
|
1081
|
+
for (const c of src.table.columns) {
|
|
1082
|
+
const key = c.name.toLowerCase();
|
|
1083
|
+
if (!coverage.has(key))
|
|
1084
|
+
coverage.set(key, { expr: columnRef(c.name, qualify ? src.refName : undefined) });
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const composed = [];
|
|
1089
|
+
const provenance = [];
|
|
1090
|
+
const effectiveColumns = [];
|
|
1091
|
+
for (const col of logicalTable.columns) {
|
|
1092
|
+
const key = col.name.toLowerCase();
|
|
1093
|
+
const covered = coverage.get(key);
|
|
1094
|
+
if (covered !== undefined) {
|
|
1095
|
+
composed.push({ type: 'column', expr: covered.expr, alias: col.name, inverse: covered.inverse });
|
|
1096
|
+
provenance.push({ logicalColumn: col.name, source: 'override' });
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
// Advertisement-driven gap-fill (richer than name-match): resolve the
|
|
1100
|
+
// uncovered column against the advertised member mapping, re-qualified to
|
|
1101
|
+
// its alias in the override's FROM. Fall back to today's FROM name-match
|
|
1102
|
+
// when the advertisement does not back it; error precisely when the
|
|
1103
|
+
// advertisement backs it from a member the FROM omits and name-match
|
|
1104
|
+
// cannot reach it either (the same fidelity-boundary discipline as gapFillError).
|
|
1105
|
+
let resolved;
|
|
1106
|
+
let unreachableMember;
|
|
1107
|
+
if (advertisement?.storage) {
|
|
1108
|
+
const res = resolveAdvertisedColumn(col.name, advertisement.storage, basisSchemaName, overrideAliasOf, undefined);
|
|
1109
|
+
if (res.kind === 'expr') {
|
|
1110
|
+
resolved = res.expr;
|
|
1111
|
+
}
|
|
1112
|
+
else if (res.kind === 'unreachable') {
|
|
1113
|
+
unreachableMember = res.member;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (!resolved) {
|
|
1117
|
+
const ref = gapFillRef(col.name, sources, qualify);
|
|
1118
|
+
if (ref) {
|
|
1119
|
+
resolved = ref;
|
|
1120
|
+
}
|
|
1121
|
+
else if (unreachableMember) {
|
|
1122
|
+
throw new QuereusError(`lens: override for logical table '${logicalSchemaName}.${logicalName}' leaves column '${col.name}' to advertisement gap-fill, but its backing member '${unreachableMember}' is absent from the override's FROM; cover it explicitly or include the member relation in the FROM`, StatusCode.ERROR);
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
throw new QuereusError(gapFillError(logicalSchemaName, logicalName, col.name, sources, hasOpaqueSource), StatusCode.ERROR);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
composed.push({ type: 'column', expr: resolved, alias: col.name });
|
|
1129
|
+
provenance.push({ logicalColumn: col.name, source: 'default' });
|
|
1130
|
+
}
|
|
1131
|
+
effectiveColumns.push(col.name);
|
|
1132
|
+
}
|
|
1133
|
+
// Preserve every non-projection clause of the override (where/group/having/
|
|
1134
|
+
// order/limit/distinct/with — the filter shape, etc.); replace only the
|
|
1135
|
+
// projection with the composed logical-column list.
|
|
1136
|
+
const body = { ...select, columns: composed };
|
|
1137
|
+
return { body, provenance, effectiveColumns };
|
|
1138
|
+
}
|
|
1139
|
+
/** Walks an override's FROM tree, collecting introspectable basis-table sources. */
|
|
1140
|
+
function collectOverrideSources(from, basisSchemaName, schemaManager) {
|
|
1141
|
+
const sources = [];
|
|
1142
|
+
let hasOpaqueSource = false;
|
|
1143
|
+
const walk = (node) => {
|
|
1144
|
+
switch (node.type) {
|
|
1145
|
+
case 'table': {
|
|
1146
|
+
const schemaName = node.table.schema ?? basisSchemaName;
|
|
1147
|
+
const tbl = schemaManager.getSchema(schemaName)?.getTable(node.table.name);
|
|
1148
|
+
if (!tbl) {
|
|
1149
|
+
hasOpaqueSource = true;
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
sources.push({ table: tbl, refName: node.alias ?? tbl.name });
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
case 'join': {
|
|
1156
|
+
walk(node.left);
|
|
1157
|
+
walk(node.right);
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
default:
|
|
1161
|
+
// subquerySource / functionSource — not introspectable in v1.
|
|
1162
|
+
hasOpaqueSource = true;
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
if (from)
|
|
1167
|
+
for (const f of from)
|
|
1168
|
+
walk(f);
|
|
1169
|
+
return { sources, hasOpaqueSource };
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Validates that every `table` source reachable from an override's body resolves
|
|
1173
|
+
* to the declared basis schema. A table qualified with a *different* existing
|
|
1174
|
+
* schema would otherwise bind there silently (re-anchoring the lens off its
|
|
1175
|
+
* `over Y` basis); reject it at deploy time. Unqualified tables default to the
|
|
1176
|
+
* basis and are fine; tables qualified with the basis name are fine.
|
|
1177
|
+
*
|
|
1178
|
+
* The walk is reflective over the entire override `select` AST, so it descends
|
|
1179
|
+
* into subquery-source FROM trees, function-source argument subqueries, `with`
|
|
1180
|
+
* CTE bodies, compound (`union`/`intersect`/…) legs, and scalar/`where`/`in`/
|
|
1181
|
+
* `exists` subqueries — a cross-basis `z.Foo` in any of those nested positions is
|
|
1182
|
+
* flagged too, not only top-level `table`/`join` sources. Descent is *not* gated
|
|
1183
|
+
* on a `type` discriminant: some containers that hold nested SELECTs are plain
|
|
1184
|
+
* wrappers without one — notably `compound` (`{ op, select }`) and `orderBy`
|
|
1185
|
+
* clauses — so a type-gated walk would skip the tables nested under them.
|
|
1186
|
+
*
|
|
1187
|
+
* No CTE-name / alias scope tracking is needed. The check fires only on a `table`
|
|
1188
|
+
* node carrying an explicit, non-basis *schema qualifier*; CTE references and
|
|
1189
|
+
* FROM aliases are always bare (SQL has no `schema.cte` form) and the compiler
|
|
1190
|
+
* resolves a bare FROM table to the basis (see `collectOverrideSources`), so a
|
|
1191
|
+
* bare name is always either the basis or a CTE — never a cross-basis relation.
|
|
1192
|
+
* Only a schema-qualified table can be cross-basis, and that can never name a
|
|
1193
|
+
* CTE/alias, so the walk needs no in-scope-name set to avoid false positives.
|
|
1194
|
+
*/
|
|
1195
|
+
function validateOverrideBasisSources(select, basisSchemaName, logicalSchemaName, logicalName) {
|
|
1196
|
+
const lowerBasis = basisSchemaName.toLowerCase();
|
|
1197
|
+
const stack = [select];
|
|
1198
|
+
while (stack.length > 0) {
|
|
1199
|
+
const node = stack.pop();
|
|
1200
|
+
if (!node || typeof node !== 'object')
|
|
1201
|
+
continue;
|
|
1202
|
+
if (node.type === 'table') {
|
|
1203
|
+
const source = node;
|
|
1204
|
+
const schema = source.table.schema;
|
|
1205
|
+
if (schema && schema.toLowerCase() !== lowerBasis) {
|
|
1206
|
+
throw new QuereusError(`lens: override for logical table '${logicalSchemaName}.${logicalName}' references basis relation '${schema}.${source.table.name}' outside the declared basis '${basisSchemaName}' (the lens is declared 'over ${basisSchemaName}'); an override's FROM may only reference the declared basis`, StatusCode.ERROR);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
// Reflective descent: push every nested object/array element. Arrays are
|
|
1210
|
+
// transparent here — Object.values yields their elements.
|
|
1211
|
+
for (const value of Object.values(node)) {
|
|
1212
|
+
if (value && typeof value === 'object')
|
|
1213
|
+
stack.push(value);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/** Output name a result column contributes: its alias, or the bare column name. */
|
|
1218
|
+
function deriveColumnOutputName(col) {
|
|
1219
|
+
if (col.alias)
|
|
1220
|
+
return col.alias;
|
|
1221
|
+
return col.expr.type === 'column' ? col.expr.name : undefined;
|
|
1222
|
+
}
|
|
1223
|
+
/** Builds a column reference, optionally qualified by a source ref name. */
|
|
1224
|
+
function columnRef(name, refName) {
|
|
1225
|
+
return refName ? { type: 'column', name, table: refName } : { type: 'column', name };
|
|
1226
|
+
}
|
|
1227
|
+
/** Finds a same-named basis column across the override's FROM sources. */
|
|
1228
|
+
function gapFillRef(colName, sources, qualify) {
|
|
1229
|
+
const lower = colName.toLowerCase();
|
|
1230
|
+
for (const src of sources) {
|
|
1231
|
+
const idx = src.table.columnIndexMap.get(lower);
|
|
1232
|
+
if (idx !== undefined) {
|
|
1233
|
+
// Reference the basis column by its actual name (casing); the logical
|
|
1234
|
+
// alias is applied by the caller.
|
|
1235
|
+
return columnRef(src.table.columns[idx].name, qualify ? src.refName : undefined);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return undefined;
|
|
1239
|
+
}
|
|
1240
|
+
/** Diagnostic for an uncovered logical column the FROM can't gap-fill. */
|
|
1241
|
+
function gapFillError(logicalSchemaName, logicalName, colName, sources, hasOpaqueSource) {
|
|
1242
|
+
const sourceDesc = sources.length === 0
|
|
1243
|
+
? 'the override FROM exposes no introspectable basis table'
|
|
1244
|
+
: `basis source(s) ${sources.map(s => s.table.name).join(', ')} have no column '${colName}'`;
|
|
1245
|
+
if (sources.length > 1 || hasOpaqueSource) {
|
|
1246
|
+
// Cross-basis / partial-coverage fidelity boundary (docs/lens.md § D2).
|
|
1247
|
+
return `lens: override for logical table '${logicalSchemaName}.${logicalName}' covers only some columns; uncovered column '${colName}' is not reachable from the override's FROM (${sourceDesc}) — it would need a basis source the single-source mapper cannot join in. Cover it explicitly.`;
|
|
1248
|
+
}
|
|
1249
|
+
// Every logical column must map to basis (docs/lens.md § Gap-fill fidelity boundary).
|
|
1250
|
+
return `lens: override for logical table '${logicalSchemaName}.${logicalName}' leaves column '${colName}' uncovered and ${sourceDesc} to gap-fill it from — cover it explicitly in the override projection (every logical column must map to basis).`;
|
|
1251
|
+
}
|
|
1252
|
+
// ===========================================================================
|
|
1253
|
+
// Module mapping advertisement resolution (docs/lens.md § The Default Mapper)
|
|
1254
|
+
// ===========================================================================
|
|
1255
|
+
//
|
|
1256
|
+
// The protocol seam: a virtual-table module advertises how a set of its basis
|
|
1257
|
+
// relations decomposes a logical table (columnar split / EAV / column-family /
|
|
1258
|
+
// nd-tree). This compiler **resolves** (collects + selects the single primary +
|
|
1259
|
+
// validates) and **stores** the advertisement on the lens slot; it does NOT
|
|
1260
|
+
// synthesize the n-way body — that is `lens-multi-source-decomposition`, which
|
|
1261
|
+
// reads `slot.advertisement`. Validation aborts the deploy atomically (before
|
|
1262
|
+
// any catalog mutation), aggregating every problem with a named site, matching
|
|
1263
|
+
// the contract `validateLensTags` already follows.
|
|
1264
|
+
/**
|
|
1265
|
+
* Collects the mapping advertisements every module owning ≥1 table in `basis`
|
|
1266
|
+
* recognizes, deduplicated by the advertisement `id`. A generic module
|
|
1267
|
+
* (memory/store) returns tag-derived advertisements; a generic module that scans
|
|
1268
|
+
* the whole schema may be hit once per distinct module instance, so the same
|
|
1269
|
+
* tag-derived advertisement can appear twice — the `id` dedup collapses those.
|
|
1270
|
+
*/
|
|
1271
|
+
function collectAdvertisements(db, basis) {
|
|
1272
|
+
const modules = new Set();
|
|
1273
|
+
for (const table of basis.getAllTables()) {
|
|
1274
|
+
if (table.vtabModule)
|
|
1275
|
+
modules.add(table.vtabModule);
|
|
1276
|
+
}
|
|
1277
|
+
const byId = new Map();
|
|
1278
|
+
for (const module of modules) {
|
|
1279
|
+
const ads = module.getMappingAdvertisements?.(db, basis) ?? [];
|
|
1280
|
+
for (const ad of ads) {
|
|
1281
|
+
if (!byId.has(ad.id))
|
|
1282
|
+
byId.set(ad.id, ad);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return Array.from(byId.values());
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Resolves the advertisements for one logical table: filters to the table,
|
|
1289
|
+
* selects the single `primary-storage` (accommodation #5 — two is an error),
|
|
1290
|
+
* keeps the rest as `auxiliary-access`, and validates the primary's structural
|
|
1291
|
+
* coherence. Returns the resolved slot fields; throws (aggregated) on any
|
|
1292
|
+
* validation failure so the deploy aborts before catalog mutation.
|
|
1293
|
+
*
|
|
1294
|
+
* `hasOverride` relaxes the per-column coverage check: when an override exists
|
|
1295
|
+
* its own coverage validation (`compileOverrideBody`) owns the column-coverage
|
|
1296
|
+
* verdict, so the advertisement is only checked for internal coherence.
|
|
1297
|
+
*/
|
|
1298
|
+
function resolveAdvertisement(all, logicalTable, basis, db, logicalSchemaName, hasOverride) {
|
|
1299
|
+
const lower = logicalTable.name.toLowerCase();
|
|
1300
|
+
const matching = all.filter(a => a.logicalTable.toLowerCase() === lower);
|
|
1301
|
+
if (matching.length === 0)
|
|
1302
|
+
return {};
|
|
1303
|
+
const primaries = matching.filter(a => a.role === 'primary-storage');
|
|
1304
|
+
const auxiliaries = matching.filter(a => a.role === 'auxiliary-access');
|
|
1305
|
+
const errors = [];
|
|
1306
|
+
if (primaries.length > 1) {
|
|
1307
|
+
errors.push(`has ${primaries.length} primary-storage advertisements (${primaries.map(p => `'${p.id}'`).join(', ')}); at most one is allowed`);
|
|
1308
|
+
}
|
|
1309
|
+
const advertisement = primaries[0];
|
|
1310
|
+
if (advertisement) {
|
|
1311
|
+
validatePrimaryAdvertisement(advertisement, logicalTable, basis, db, hasOverride, errors);
|
|
1312
|
+
}
|
|
1313
|
+
for (const aux of auxiliaries) {
|
|
1314
|
+
validateAuxiliaryAdvertisement(aux, basis, db, errors);
|
|
1315
|
+
}
|
|
1316
|
+
if (errors.length > 0) {
|
|
1317
|
+
throw new QuereusError(`lens: advertisement for logical table '${logicalSchemaName}.${logicalTable.name}' is invalid: ${errors.join('; ')}`, StatusCode.ERROR);
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
advertisement,
|
|
1321
|
+
auxiliaryAccess: auxiliaries.length > 0 ? auxiliaries : undefined,
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Validates a `primary-storage` advertisement's structural coherence. Each check
|
|
1326
|
+
* pushes a sited message to `errors` (aggregated by the caller); none mutate the
|
|
1327
|
+
* catalog. See `docs/lens.md` § The Default Mapper for the field semantics.
|
|
1328
|
+
*/
|
|
1329
|
+
function validatePrimaryAdvertisement(ad, logicalTable, basis, db, hasOverride, errors) {
|
|
1330
|
+
const storage = ad.storage;
|
|
1331
|
+
if (!storage) {
|
|
1332
|
+
errors.push(`role 'primary-storage' requires a storage shape`);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
// The IND existence-anchor contract: id == anchorRelationId (so the INDs the
|
|
1336
|
+
// synthesis ticket injects, with IndTarget.kind:'relation'.relationId, and the
|
|
1337
|
+
// join it builds agree on the anchor).
|
|
1338
|
+
if (ad.id !== storage.anchorRelationId) {
|
|
1339
|
+
errors.push(`advertisement id '${ad.id}' must equal storage.anchorRelationId '${storage.anchorRelationId}'`);
|
|
1340
|
+
}
|
|
1341
|
+
const memberByRelationId = new Map();
|
|
1342
|
+
for (const member of storage.members)
|
|
1343
|
+
memberByRelationId.set(member.relationId, member);
|
|
1344
|
+
if (!memberByRelationId.has(storage.anchorRelationId)) {
|
|
1345
|
+
errors.push(`anchor '${storage.anchorRelationId}' is not among the members (${storage.members.map(m => `'${m.relationId}'`).join(', ') || 'none'})`);
|
|
1346
|
+
}
|
|
1347
|
+
// Resolve each member's basis table; validate column / pivot existence.
|
|
1348
|
+
const memberTables = new Map();
|
|
1349
|
+
for (const member of storage.members) {
|
|
1350
|
+
const table = resolveBasisRelation(db, member, basis);
|
|
1351
|
+
if (!table) {
|
|
1352
|
+
errors.push(`member '${member.relationId}' references basis relation '${member.relation.schema}.${member.relation.table}', which does not exist`);
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
memberTables.set(member.relationId, table);
|
|
1356
|
+
for (const mapping of member.columns) {
|
|
1357
|
+
for (const refName of collectColumnRefNames(mapping.basisExpr)) {
|
|
1358
|
+
if (!table.columnIndexMap.has(refName.toLowerCase())) {
|
|
1359
|
+
errors.push(`member '${member.relationId}' maps logical column '${mapping.logicalColumn}' to basis expression referencing column '${refName}', which does not exist on '${table.name}'`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (member.attributePivot) {
|
|
1364
|
+
for (const [role, col] of [
|
|
1365
|
+
['entity', member.attributePivot.entityColumn],
|
|
1366
|
+
['attribute', member.attributePivot.attributeColumn],
|
|
1367
|
+
['value', member.attributePivot.valueColumn],
|
|
1368
|
+
]) {
|
|
1369
|
+
if (!table.columnIndexMap.has(col.toLowerCase())) {
|
|
1370
|
+
errors.push(`member '${member.relationId}' attributePivot ${role} column '${col}' does not exist on '${table.name}'`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
// Shared key: surrogate ⇒ the anchor's shared-key column declares a DEFAULT (the
|
|
1376
|
+
// engine evaluates it once per row and threads it via the EC — it chooses no ID
|
|
1377
|
+
// policy of its own); logical-tuple ⇒ the supplied logical PK, with each member's
|
|
1378
|
+
// key columns matching the logical PK arity.
|
|
1379
|
+
const sharedKey = storage.sharedKey;
|
|
1380
|
+
if (sharedKey.kind === 'surrogate') {
|
|
1381
|
+
// The surrogate's value must come from the anchor key column's declared
|
|
1382
|
+
// `default` (replacing the retired engine-invented `integer-auto` mint): an
|
|
1383
|
+
// INSERT evaluates it once per row, and the EC threads the captured value to
|
|
1384
|
+
// every member. A single-column anchor key whose column has no default is a
|
|
1385
|
+
// deploy-time error — there is nowhere for the surrogate to come from.
|
|
1386
|
+
const anchorKeys = sharedKey.keyColumnsByRelation.get(storage.anchorRelationId) ?? [];
|
|
1387
|
+
const anchorTable = memberTables.get(storage.anchorRelationId);
|
|
1388
|
+
if (anchorTable && anchorKeys.length === 1) {
|
|
1389
|
+
const keyCol = anchorTable.columns.find(c => c.name.toLowerCase() === anchorKeys[0].toLowerCase());
|
|
1390
|
+
if (keyCol && keyCol.defaultValue === null) {
|
|
1391
|
+
errors.push(`shared key is 'surrogate' but anchor '${storage.anchorRelationId}' key column '${anchorKeys[0]}' declares no DEFAULT; a surrogate's value comes from that column's default (e.g. \`default (coalesce((select max(${anchorKeys[0]}) from ${anchorTable.name}), 0) + mutation_ordinal())\`) — the engine no longer auto-generates one`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// A surrogate is not tied to the logical PK arity, but the equi-join pairs
|
|
1395
|
+
// each member's key columns positionally with the anchor's, so they must all
|
|
1396
|
+
// share one arity. Validate it here: an under-arity member would otherwise
|
|
1397
|
+
// silently under-join (`buildKeyEquiJoin` pairs by `Math.min`) rather than
|
|
1398
|
+
// error, multiplying rows instead of stitching them.
|
|
1399
|
+
const anchorArity = sharedKey.keyColumnsByRelation.get(storage.anchorRelationId)?.length;
|
|
1400
|
+
if (anchorArity !== undefined) {
|
|
1401
|
+
for (const member of storage.members) {
|
|
1402
|
+
const keyCols = sharedKey.keyColumnsByRelation.get(member.relationId);
|
|
1403
|
+
if (keyCols && keyCols.length !== anchorArity) {
|
|
1404
|
+
errors.push(`shared key is 'surrogate' but member '${member.relationId}' has ${keyCols.length} key column(s), not the anchor '${storage.anchorRelationId}' surrogate arity (${anchorArity})`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (sharedKey.kind === 'logical-tuple') {
|
|
1410
|
+
const pkArity = logicalTable.primaryKeyDefinition.length;
|
|
1411
|
+
for (const member of storage.members) {
|
|
1412
|
+
const keyCols = sharedKey.keyColumnsByRelation.get(member.relationId);
|
|
1413
|
+
if (keyCols && keyCols.length !== pkArity) {
|
|
1414
|
+
errors.push(`shared key is 'logical-tuple' but member '${member.relationId}' has ${keyCols.length} key column(s), not the logical primary key's arity (${pkArity})`);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
// keyColumnsByRelation covers every member and each named column exists.
|
|
1419
|
+
for (const member of storage.members) {
|
|
1420
|
+
const keyCols = sharedKey.keyColumnsByRelation.get(member.relationId);
|
|
1421
|
+
if (keyCols === undefined) {
|
|
1422
|
+
errors.push(`shared key has no key columns for member '${member.relationId}'`);
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const table = memberTables.get(member.relationId);
|
|
1426
|
+
if (!table)
|
|
1427
|
+
continue; // already reported as a missing relation
|
|
1428
|
+
for (const col of keyCols) {
|
|
1429
|
+
if (!table.columnIndexMap.has(col.toLowerCase())) {
|
|
1430
|
+
errors.push(`shared key column '${col}' for member '${member.relationId}' does not exist on '${table.name}'`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
// Stitch-key / EAV-conflict-target uniqueness (docs/lens.md § The `put`
|
|
1435
|
+
// fan-out). The put fan-out cedes the matched rows to the matched
|
|
1436
|
+
// UPDATE only through the materialize INSERT's `on conflict (<target>) do nothing`,
|
|
1437
|
+
// which the runtime fires solely on a declared PK / UNIQUE violation; the get side
|
|
1438
|
+
// is sound only when that same target is 1:1 (a non-unique columnar stitch key
|
|
1439
|
+
// multiplies the equi-join, a non-unique `(entity, attr)` makes the EAV correlated
|
|
1440
|
+
// subquery multi-valued). So every member's materialize conflict target must equal a
|
|
1441
|
+
// declared PRIMARY KEY or non-partial UNIQUE on its basis table. Validated once here
|
|
1442
|
+
// at deploy — the only place that governs both directions of the lens — so the
|
|
1443
|
+
// plan-time materialize builders may rely on it without re-checking. The anchor is
|
|
1444
|
+
// validated too: its own stitch key must be unique for the logical-PK / surrogate
|
|
1445
|
+
// identity to be 1:1.
|
|
1446
|
+
for (const member of storage.members) {
|
|
1447
|
+
const table = memberTables.get(member.relationId);
|
|
1448
|
+
if (!table)
|
|
1449
|
+
continue; // missing relation already reported above
|
|
1450
|
+
if (member.attributePivot) {
|
|
1451
|
+
// EAV: the conflict target is `(entity, attribute)`, NOT the stitch key
|
|
1452
|
+
// (`entity` alone, which is deliberately one-to-many across attributes).
|
|
1453
|
+
const target = [member.attributePivot.entityColumn, member.attributePivot.attributeColumn];
|
|
1454
|
+
const idx = resolveColumnIndices(table, target);
|
|
1455
|
+
if (idx && !columnsFormDeclaredKey(table, idx)) {
|
|
1456
|
+
errors.push(`EAV pivot member '${member.relationId}' conflict target (${target.join(', ')}) is not a declared PRIMARY KEY or UNIQUE constraint on '${table.name}'; the get-side correlated subquery requires (entity, attribute) single-valued and the per-attribute materialize INSERT cedes matched triples via \`on conflict (${target.join(', ')}) do nothing\` — declare a PRIMARY KEY / UNIQUE on those columns`);
|
|
1457
|
+
}
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
const keyCols = sharedKey.keyColumnsByRelation.get(member.relationId) ?? [];
|
|
1461
|
+
if (keyCols.length === 0)
|
|
1462
|
+
continue; // singleton (`primary key ()`) — no stitch, no materialize path
|
|
1463
|
+
const idx = resolveColumnIndices(table, keyCols);
|
|
1464
|
+
if (idx && !columnsFormDeclaredKey(table, idx)) {
|
|
1465
|
+
errors.push(`member '${member.relationId}' stitch key (${keyCols.join(', ')}) is not a declared PRIMARY KEY or UNIQUE constraint on basis relation '${table.name}'; the decomposition equi-join requires a 1:1 stitch and the optional-member materialize INSERT's \`on conflict (${keyCols.join(', ')}) do nothing\` only cedes matched rows against a declared unique — declare a PRIMARY KEY / UNIQUE on those columns`);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
// Column coverage (only when there is no override — an override's own coverage
|
|
1469
|
+
// validation owns the verdict otherwise). Every logical column must be backed
|
|
1470
|
+
// by exactly one member mapping, or by an EAV pivot member, or be coverable by
|
|
1471
|
+
// name-match against the basis. Otherwise the advertisement claims the table
|
|
1472
|
+
// but leaves the column unbacked and uncovered → error, naming the column.
|
|
1473
|
+
if (!hasOverride) {
|
|
1474
|
+
const backedBy = buildColumnBackingMap(storage);
|
|
1475
|
+
const hasEavMember = storage.members.some(m => m.attributePivot);
|
|
1476
|
+
const nameMatchTable = basis.schema.getTable(logicalTable.name);
|
|
1477
|
+
for (const col of logicalTable.columns) {
|
|
1478
|
+
const lc = col.name.toLowerCase();
|
|
1479
|
+
if (backedBy.get(lc) === 'ambiguous') {
|
|
1480
|
+
errors.push(`logical column '${col.name}' is backed by more than one member mapping (must be exactly one)`);
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (backedBy.has(lc))
|
|
1484
|
+
continue; // exactly one member maps it
|
|
1485
|
+
if (hasEavMember)
|
|
1486
|
+
continue; // an EAV pivot member backs it generically
|
|
1487
|
+
if (nameMatchTable?.columnIndexMap.has(lc))
|
|
1488
|
+
continue; // left to name-match
|
|
1489
|
+
errors.push(`logical column '${col.name}' is left unbacked by the advertisement and is not coverable by name-match (cover it with a member mapping, an EAV pivot, or a name-matching basis column)`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Minimal validation for an `auxiliary-access` advertisement: every member
|
|
1495
|
+
* relation must resolve. The advertised predicate forms are not validated here —
|
|
1496
|
+
* the read-path consumer (`rule-lens-auxiliary-access`, `lens-access-shape-path-selection`)
|
|
1497
|
+
* matches them at plan time through its recognizer registry and silently degrades
|
|
1498
|
+
* to scan for any form it cannot serve, so an unknown form is never an error.
|
|
1499
|
+
*/
|
|
1500
|
+
function validateAuxiliaryAdvertisement(ad, basis, db, errors) {
|
|
1501
|
+
if (!ad.storage)
|
|
1502
|
+
return; // an auxiliary may carry access-only shape
|
|
1503
|
+
for (const member of ad.storage.members) {
|
|
1504
|
+
if (!resolveBasisRelation(db, member, basis)) {
|
|
1505
|
+
errors.push(`auxiliary advertisement '${ad.id}' member '${member.relationId}' references basis relation '${member.relation.schema}.${member.relation.table}', which does not exist`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Builds `logicalColumn(lower) -> member relationId | 'ambiguous'` from the
|
|
1511
|
+
* explicit per-member column mappings (EAV pivots are handled separately by the
|
|
1512
|
+
* caller). A column mapped by two members is `'ambiguous'`.
|
|
1513
|
+
*/
|
|
1514
|
+
function buildColumnBackingMap(storage) {
|
|
1515
|
+
const backedBy = new Map();
|
|
1516
|
+
for (const member of storage.members) {
|
|
1517
|
+
for (const mapping of member.columns) {
|
|
1518
|
+
const lc = mapping.logicalColumn.toLowerCase();
|
|
1519
|
+
backedBy.set(lc, backedBy.has(lc) ? 'ambiguous' : member.relationId);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return backedBy;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Resolves a list of column names to their indices on `table` via `columnIndexMap`.
|
|
1526
|
+
* Returns `undefined` if any name is unresolved — the caller (key-column / pivot
|
|
1527
|
+
* existence loops) already reports an unresolved name, so the uniqueness check just
|
|
1528
|
+
* skips rather than double-reporting.
|
|
1529
|
+
*/
|
|
1530
|
+
function resolveColumnIndices(table, names) {
|
|
1531
|
+
const out = [];
|
|
1532
|
+
for (const n of names) {
|
|
1533
|
+
const i = table.columnIndexMap.get(n.toLowerCase());
|
|
1534
|
+
if (i === undefined)
|
|
1535
|
+
return undefined; // unresolved name already reported elsewhere
|
|
1536
|
+
out.push(i);
|
|
1537
|
+
}
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
/** Resolves a member's {@link BasisRelationRef} to a concrete basis table. */
|
|
1541
|
+
function resolveBasisRelation(db, member, basis) {
|
|
1542
|
+
const schemaName = member.relation.schema || basis.schemaName;
|
|
1543
|
+
const schema = schemaName.toLowerCase() === basis.schemaName.toLowerCase()
|
|
1544
|
+
? basis.schema
|
|
1545
|
+
: db.schemaManager.getSchema(schemaName);
|
|
1546
|
+
return schema?.getTable(member.relation.table);
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Annotates each provenance entry with the member `relationId` that backs its
|
|
1550
|
+
* logical column, when the resolved advertisement maps it. Explicit mappings win;
|
|
1551
|
+
* a column with no explicit mapping is attributed to the sole EAV pivot member
|
|
1552
|
+
* when one exists. Surfaced by `quereus_effective_lens`.
|
|
1553
|
+
*/
|
|
1554
|
+
function annotateProvenanceWithAdvertisement(provenance, ad) {
|
|
1555
|
+
const storage = ad.storage;
|
|
1556
|
+
if (!storage)
|
|
1557
|
+
return;
|
|
1558
|
+
const backedBy = buildColumnBackingMap(storage);
|
|
1559
|
+
const eavMembers = storage.members.filter(m => m.attributePivot);
|
|
1560
|
+
const soleEav = eavMembers.length === 1 ? eavMembers[0].relationId : undefined;
|
|
1561
|
+
for (const p of provenance) {
|
|
1562
|
+
const explicit = backedBy.get(p.logicalColumn.toLowerCase());
|
|
1563
|
+
if (explicit && explicit !== 'ambiguous') {
|
|
1564
|
+
p.advertisedBy = explicit;
|
|
1565
|
+
}
|
|
1566
|
+
else if (soleEav) {
|
|
1567
|
+
p.advertisedBy = soleEav;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Override ⊕ advertisement conflict (docs/lens.md § Override-vs-advertisement
|
|
1573
|
+
* composition): a *sparse* override (one that relies on gap-fill) may correct an
|
|
1574
|
+
* advertised column mapping, but must NOT re-anchor or reference basis relations
|
|
1575
|
+
* outside the advertised decomposition. Every introspectable basis-table source
|
|
1576
|
+
* in the override's FROM must be one of the advertisement's member relations;
|
|
1577
|
+
* otherwise the developer is silently re-anchoring and must instead author a full
|
|
1578
|
+
* hand-authored body (which bypasses the advertisement entirely). Opaque sources
|
|
1579
|
+
* (subquery / function) are not introspectable and do not trip the check.
|
|
1580
|
+
*/
|
|
1581
|
+
function validateOverrideAdvertisementConflict(ad, override, basisSchemaName, schemaManager, logicalSchemaName, logicalName) {
|
|
1582
|
+
const storage = ad.storage;
|
|
1583
|
+
if (!storage)
|
|
1584
|
+
return;
|
|
1585
|
+
const memberRelations = new Set(storage.members.map(m => `${(m.relation.schema || basisSchemaName).toLowerCase()}.${m.relation.table.toLowerCase()}`));
|
|
1586
|
+
const { sources } = collectOverrideSources(override.select.from, basisSchemaName, schemaManager);
|
|
1587
|
+
for (const src of sources) {
|
|
1588
|
+
const key = `${src.table.schemaName.toLowerCase()}.${src.table.name.toLowerCase()}`;
|
|
1589
|
+
if (!memberRelations.has(key)) {
|
|
1590
|
+
throw new QuereusError(`lens: override for logical table '${logicalSchemaName}.${logicalName}' references basis relation '${src.table.schemaName}.${src.table.name}', which is not part of the advertised decomposition (anchor '${storage.anchorRelationId}', members: ${storage.members.map(m => `'${m.relation.table}'`).join(', ')}); a partial override may not re-anchor or change the shared key — cover every logical column explicitly to author a full body that bypasses the advertisement, or align the override's FROM with the decomposition`, StatusCode.ERROR);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
//# sourceMappingURL=lens-compiler.js.map
|