@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,1687 @@
|
|
|
1
|
+
import { isRelationalNode } from '../nodes/plan-node.js';
|
|
2
|
+
import { checkSatisfiability } from '../analysis/sat-checker.js';
|
|
3
|
+
import { splitConjuncts } from '../analysis/predicate-conjuncts.js';
|
|
4
|
+
import { SetOperationNode } from '../nodes/set-operation-node.js';
|
|
5
|
+
import { buildSelectStmt } from '../building/select.js';
|
|
6
|
+
import { buildExpression } from '../building/expression.js';
|
|
7
|
+
import { FilterNode } from '../nodes/filter.js';
|
|
8
|
+
import { ProjectNode } from '../nodes/project-node.js';
|
|
9
|
+
import { RegisteredScope } from '../scopes/registered.js';
|
|
10
|
+
import { ColumnReferenceNode } from '../nodes/reference.js';
|
|
11
|
+
import { raiseMutationDiagnostic } from './mutation-diagnostic.js';
|
|
12
|
+
import { propagate } from './propagate.js';
|
|
13
|
+
import { MS_UPDATE_KEYS_CTE, isJoinBody, isInnerJoinBody, analyzeJoinView, analyzeMultiSourceInsert, decomposeUpdate, decomposeDelete, buildMultiSourceKeyCapture, capturedSideIndices, withKeyCapture } from './multi-source.js';
|
|
14
|
+
import { cloneExpr, transformExpr } from './scope-transform.js';
|
|
15
|
+
import { unwrapPassthroughSubquery } from '../util/set-op-wrapper.js';
|
|
16
|
+
/**
|
|
17
|
+
* Set-operation membership-column write decomposition — the **first set-op view
|
|
18
|
+
* writability** in the engine (docs/view-updateability.md § Set Operations).
|
|
19
|
+
*
|
|
20
|
+
* The read half (`set-op-membership-read`) reifies a binary set operation's branch
|
|
21
|
+
* provenance as first-class `existence`-sited boolean columns and, per row, computes
|
|
22
|
+
* a runtime membership probe (`inA ≡ tuple ∈ A`). This module delivers the write
|
|
23
|
+
* payoff: a membership column **is** the branch presence, and *writing* it drives the
|
|
24
|
+
* branch's existence:
|
|
25
|
+
*
|
|
26
|
+
* ```sql
|
|
27
|
+
* -- U = select id, x from A union exists left as inA, exists right as inB select id, x from B
|
|
28
|
+
* update U set inB = true where id = 3 -- a row in A only ⇒ INSERT it into B
|
|
29
|
+
* update U set inB = false where id = 3 -- a row in B ⇒ DELETE the matching B row
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* **The substrate (Halloween-safe via an up-front capture).** Every affected view
|
|
33
|
+
* row — its data columns AND its membership-probe flags — is captured ONCE
|
|
34
|
+
* (`buildSetOpCapture`), *before* any branch op fires, into the same context-backed
|
|
35
|
+
* key relation the multi-source path uses ({@link MS_UPDATE_KEYS_CTE}). The per-branch
|
|
36
|
+
* ops then read that immutable capture rather than re-scanning the view (which reads
|
|
37
|
+
* the very branches being written), so a branch insert/delete can never perturb the
|
|
38
|
+
* affected set out from under a sibling branch op. The capture rides the existing
|
|
39
|
+
* `ViewMutationNode.identityCapture` side input + the void/drain runtime path — no new
|
|
40
|
+
* runtime substrate (see `runtime/emit/view-mutation.ts`).
|
|
41
|
+
*
|
|
42
|
+
* **A branch is itself a view body — routed per-branch via recursive `propagate`.**
|
|
43
|
+
* Each operand of the set operation is a relational sub-plan (`select … from B`). A
|
|
44
|
+
* **single-source** branch's per-branch op is lowered to an AST `BaseOp` against a
|
|
45
|
+
* **synthetic branch view-like** and run back through {@link propagate} — reusing the
|
|
46
|
+
* single-source spine verbatim (the branch's own σ predicate, renames, and base routing are
|
|
47
|
+
* honored by its spine).
|
|
48
|
+
*
|
|
49
|
+
* A **multi-source (INNER join) branch/leg** ({@link isInnerJoinBody}) is **composed** rather
|
|
50
|
+
* than routed through plain {@link propagate} (`set-op-write-multisource-leg-compose`): plain
|
|
51
|
+
* `propagate` builds NO capture, so its emitted multi-source predicates (`k<side>_<j>`) would
|
|
52
|
+
* bind to the outer set-op capture (view-output columns) — the internal `k.k0_0 isn't a column`
|
|
53
|
+
* error. Instead the fan builds the join branch itself (mirroring `buildViewMutation`'s
|
|
54
|
+
* orchestration): {@link analyzeJoinView} + {@link decomposeUpdate}/{@link decomposeDelete} +
|
|
55
|
+
* {@link buildMultiSourceKeyCapture} build an **inner per-branch base-PK capture** under a fresh
|
|
56
|
+
* `__vmupd_keys$N` name (minted monotonically — two join branches never collide), filtered by the
|
|
57
|
+
* SAME {@link buildMemberExists} predicate (so it scans the OUTER set-op capture), and bubble it
|
|
58
|
+
* up on {@link SetOpWritePlan.nestedCaptures} (materialized outer-first then inner, via
|
|
59
|
+
* `ViewMutationNode.nestedCaptures`). INSERT into a join branch is deferred to
|
|
60
|
+
* `set-op-write-multisource-leg-insert` (needs the plan-level shared-surrogate envelope) — a clean
|
|
61
|
+
* reject + `is_insertable_into = NO`; an OUTER (left/right/full) / cross / non-equi join leg is
|
|
62
|
+
* likewise deferred (a clean classification reject). A branch that bottoms out in a base table emits one base op; a branch
|
|
63
|
+
* that is itself a `SetOperationNode` (a **subtree operand**, `nestable-flagged-set-ops`)
|
|
64
|
+
* recurses *here* for the unambiguous fan-out — a data-column UPDATE, a DELETE, and a
|
|
65
|
+
* `set <subtreeFlag> = false` drop fan out to every member leaf, sharing the ONE up-front
|
|
66
|
+
* capture (`fanBranchDataUpdate` / `fanBranchDelete`, detected via {@link analyzeSetOpBranches}).
|
|
67
|
+
* A **union / union all** subtree fans freely (a leaf ⊆ the subtree). An **`except` /
|
|
68
|
+
* `intersect`** subtree fan is **membership-gated** (`set-op-membership-nested-except`): a leaf
|
|
69
|
+
* can hold rows the subtree excludes, so the recursion AND-s the captured subtree-membership
|
|
70
|
+
* boundary flag (`exists <branch> as <flag>`) into each leaf's member-exists, restricting the
|
|
71
|
+
* fan to genuine members — one conjunct per non-union boundary descended. A flag-less non-union
|
|
72
|
+
* boundary has no boundary probe to gate on and stays deferred (rejected).
|
|
73
|
+
* The genuinely ambiguous inserts into a subtree — `set <subtreeFlag> = true`, a
|
|
74
|
+
* surfaced-inner-flag write, and insert-through routing into a subtree side — have no single
|
|
75
|
+
* deterministic target leaf (product-coordinate addressing) and are rejected, pointing at
|
|
76
|
+
* `set-op-membership-nested`.
|
|
77
|
+
*
|
|
78
|
+
* **A LEFT operand can be a subtree too** (`set-op-leftwrap-write`). A parenthesized LEFT
|
|
79
|
+
* compound operand — `(A∪B) union[…] (C∪D)`, a *parallel-sibling* shape — is lifted by the parser
|
|
80
|
+
* into a `select * from (A∪B) as values_N` passthrough wrapper so the SELECT-level `compound` slot
|
|
81
|
+
* can host the outer operator. {@link buildBranch} **unwraps** that wrapper (via
|
|
82
|
+
* {@link unwrapBranchSelect}, the same {@link unwrapPassthroughSubquery} predicate the read/plan
|
|
83
|
+
* path uses) so the wrapped left operand is a first-class subtree operand — its data cols,
|
|
84
|
+
* `isNested`, and fan-out recursion all derive from the inner compound, exactly as the
|
|
85
|
+
* (always-direct) right compound operand. So the unambiguous fan-out (data UPDATE / DELETE /
|
|
86
|
+
* `set <subtreeFlag> = false`) reaches the LEFT subtree's leaves, while the ambiguous inserts
|
|
87
|
+
* into it stay deferred (`set-op-membership-nested`). The static surfaces walk both operands too.
|
|
88
|
+
*
|
|
89
|
+
* **Per-branch correlation.** A fan-out / membership-delete op identifies the branch's
|
|
90
|
+
* affected rows by a correlated `exists (select 1 from __vmupd_keys k where <k matches
|
|
91
|
+
* the branch row's data tuple>)` — a NULL-safe full-data-tuple match (set operations
|
|
92
|
+
* treat `NULL = NULL` as equal, and the engine has no `IS NOT DISTINCT FROM`, so each
|
|
93
|
+
* column is matched `k.c = b.c or (k.c is null and b.c is null)`). The branch's own
|
|
94
|
+
* columns are qualified with the synthetic branch-view name so {@link propagate}'s
|
|
95
|
+
* single-source correlation (the `__vm_self` self-alias) binds them to the lowered base
|
|
96
|
+
* row; the `k.*` capture columns stay unqualified-to-the-branch and resolve to the
|
|
97
|
+
* injected `__vmupd_keys` relation. The membership-INSERT branch instead reads the
|
|
98
|
+
* captured rows **absent** from the branch via the probe flag (`where not k.<flag>`),
|
|
99
|
+
* so a `set <flag> = true` is a clean no-op for rows already present (and across
|
|
100
|
+
* operators — `except`'s always-false right flag inserts every visible row, `intersect`'s
|
|
101
|
+
* always-true flags insert none).
|
|
102
|
+
*
|
|
103
|
+
* **Scope.** union / union all / except / intersect membership writes, data-column fan-out,
|
|
104
|
+
* delete fan-out, and insert-through, at any nesting depth for the unambiguous fan-out
|
|
105
|
+
* operations (data UPDATE / DELETE / `set <subtreeFlag> = false`). The ambiguous inserts
|
|
106
|
+
* into a subtree (`set <subtreeFlag> = true`, surfaced-inner-flag writes, insert-through into
|
|
107
|
+
* a subtree side) are `set-op-membership-nested` (product-coordinate addressing); non-literal
|
|
108
|
+
* boolean membership writes and the `strict` unspecified-case policy stay deferred.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* True iff `selectAst` is a **binary set-operation body carrying ≥1 membership flag**
|
|
112
|
+
* (`<setop> exists <branch> as <name>`) — the shape this write path decomposes. An
|
|
113
|
+
* AST peek (no plan built), the write-side shadow of the read half's combinator. A
|
|
114
|
+
* plain (flag-less) set-op body returns `false` and keeps rejecting `unsupported-set-op`
|
|
115
|
+
* through the single-source spine (there is no membership column to address a branch by).
|
|
116
|
+
* `diff` is excluded (the parser already rejects membership on it).
|
|
117
|
+
*/
|
|
118
|
+
export function isSetOpMembershipBody(selectAst) {
|
|
119
|
+
return selectAst.type === 'select'
|
|
120
|
+
&& !!selectAst.compound
|
|
121
|
+
&& selectAst.compound.op !== 'diff'
|
|
122
|
+
&& !!selectAst.compound.existence
|
|
123
|
+
&& selectAst.compound.existence.length > 0;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* True iff a set-op membership body is reportable-writable by the static surfaces — the
|
|
127
|
+
* no-plan shadow of {@link analyzeSetOpView}'s pre-write rejections: an outer LIMIT/OFFSET
|
|
128
|
+
* (the body is not decomposable — a write would escape the limited window), a non-SELECT
|
|
129
|
+
* right operand, a `select *` leg, or a computed (non-plain-column) leg. Lets the
|
|
130
|
+
* `column_info` / `view_info` static surfaces gate the membership-writable claim on the SAME
|
|
131
|
+
* shape the dynamic write enforces, instead of reporting writable from the membership flag's
|
|
132
|
+
* presence alone.
|
|
133
|
+
*
|
|
134
|
+
* **Recursive** (`nestable-flagged-set-ops`): an operand is branch-writable iff it is a
|
|
135
|
+
* plain-leg leaf OR a (recursively) branch-writable set-op body — so a nested view reports
|
|
136
|
+
* `is_updatable` / `is_deletable` = YES, agreeing with the dynamic accept (data + delete
|
|
137
|
+
* fan-out recurse through a subtree operand). Inserts into a subtree are deferred, so
|
|
138
|
+
* insertability is gated separately on {@link setOpHasSubtreeOperand}.
|
|
139
|
+
*/
|
|
140
|
+
export function isSetOpBranchWritable(selectAst) {
|
|
141
|
+
if (selectAst.type !== 'select' || !selectAst.compound)
|
|
142
|
+
return false;
|
|
143
|
+
return isSetOpBodyWritable(selectAst);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* True iff a set-op body (its compound + both operands) is recursively branch-writable.
|
|
147
|
+
* Mirrors the dynamic write's pre-write rejections at this level — an outer LIMIT/OFFSET
|
|
148
|
+
* (a write would escape the window), then each operand checked via {@link isOperandWritable}.
|
|
149
|
+
*
|
|
150
|
+
* Threads each operand's **boundary-flag presence** (`exists <branch> as <flag>` declared on
|
|
151
|
+
* THIS compound for that side) into {@link isOperandWritable}: an `except` / `intersect` subtree
|
|
152
|
+
* operand is writable only when its side carries a boundary flag to gate the fan on, mirroring
|
|
153
|
+
* the dynamic `gateFlagForNonUnionSubtree` requirement.
|
|
154
|
+
*/
|
|
155
|
+
function isSetOpBodyWritable(selectAst) {
|
|
156
|
+
if (!selectAst.compound)
|
|
157
|
+
return false;
|
|
158
|
+
if (selectAst.limit || selectAst.offset)
|
|
159
|
+
return false;
|
|
160
|
+
const ex = selectAst.compound.existence ?? [];
|
|
161
|
+
const leftFlag = ex.some(e => e.branch === 'left');
|
|
162
|
+
const rightFlag = ex.some(e => e.branch === 'right');
|
|
163
|
+
return isOperandWritable(leftBranchSelect(selectAst), leftFlag)
|
|
164
|
+
&& isOperandWritable(selectAst.compound.select, rightFlag);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* True iff an operand (a compound leg) is recursively branch-writable: a (recursively)
|
|
168
|
+
* writable set-op body, OR a plain-column leaf (the shape that round-trips a base column
|
|
169
|
+
* through the branch's single-source spine). A non-SELECT operand, a `select *` leg, or a
|
|
170
|
+
* computed leg is non-writable — the dynamic write rejects all three.
|
|
171
|
+
*
|
|
172
|
+
* `hasGatingFlag` is whether the parent compound declared a boundary membership flag for this
|
|
173
|
+
* operand's side. A **union / union all** subtree ignores it (a leaf ⊆ the subtree, so the
|
|
174
|
+
* leaf-presence correlation already implies membership — no gate needed). An **`except` /
|
|
175
|
+
* `intersect`** subtree is writable IFF `hasGatingFlag` (the captured boundary flag gates the
|
|
176
|
+
* fan to genuine members — `set-op-membership-nested-except`); a flag-less non-union boundary
|
|
177
|
+
* stays deferred, so this returns `false`, agreeing with the dynamic reject. Leaf operands
|
|
178
|
+
* ignore `hasGatingFlag`.
|
|
179
|
+
*/
|
|
180
|
+
function isOperandWritable(operand, hasGatingFlag) {
|
|
181
|
+
if (operand.type !== 'select')
|
|
182
|
+
return false;
|
|
183
|
+
// Unwrap a parenthesized LEFT compound operand's `select * from (compound)` wrapper so it is
|
|
184
|
+
// classified as the subtree it is, mirroring the dynamic `buildBranch` unwrap — a no-op on a
|
|
185
|
+
// direct operand (`set-op-leftwrap-write`).
|
|
186
|
+
const effective = unwrapBranchSelect(operand);
|
|
187
|
+
if (effective.compound && effective.compound.op !== 'diff') {
|
|
188
|
+
// An except / intersect subtree needs a boundary flag to gate the membership fan on.
|
|
189
|
+
if (!isUnionLikeSubtree(effective.compound.op) && !hasGatingFlag)
|
|
190
|
+
return false;
|
|
191
|
+
return isSetOpBodyWritable(effective);
|
|
192
|
+
}
|
|
193
|
+
// A leaf operand's data columns must be plain (optionally renamed) base columns; on top of
|
|
194
|
+
// that it is writable when it is single-source OR a **multi-source INNER join leg** (the
|
|
195
|
+
// join-leg compose — `set-op-write-multisource-leg-compose`, which builds an inner per-branch
|
|
196
|
+
// capture chained off the outer set-op capture). An OUTER (left/right/full) / cross join leg is
|
|
197
|
+
// deferred, so it reports non-writable here — matching the dynamic `buildBranch` reject exactly.
|
|
198
|
+
// (`isInnerJoinBody` keys only on `joinType`, so a NON-EQUI inner join is admitted and composes,
|
|
199
|
+
// exactly as the standalone join-view path and the flag-less route admit it.) This recurses to
|
|
200
|
+
// leaves at every depth, so a
|
|
201
|
+
// nested join leaf is classified too. Insertability through a join leg is deferred separately
|
|
202
|
+
// (gated in `schema.ts`).
|
|
203
|
+
if (tryBranchColumnNames(effective) === null)
|
|
204
|
+
return false;
|
|
205
|
+
return !isJoinBody(effective) || isInnerJoinBody(effective);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* True iff a subtree operand's set operator is **union-like** (`union` / `unionAll`), whose
|
|
209
|
+
* result is a SUPERSET of each operand — every resident leaf row is a member, so the fan-out
|
|
210
|
+
* needs no membership gate. `except` / `intersect` are NOT union-like: a leaf can hold rows the
|
|
211
|
+
* subtree excludes, so their fan-out is gated on the captured subtree-membership boundary flag
|
|
212
|
+
* (`set-op-membership-nested-except`). This helper branches the gate logic (no extra conjunct
|
|
213
|
+
* for union-like, accumulate the boundary flag otherwise); a flag-less non-union boundary
|
|
214
|
+
* remains the lone deferral.
|
|
215
|
+
*/
|
|
216
|
+
function isUnionLikeSubtree(op) {
|
|
217
|
+
return op === 'union' || op === 'unionAll';
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* True iff a membership body has a **subtree (compound) operand** — an inner
|
|
221
|
+
* `SetOperationNode` operand on EITHER side. Insert-through into a multi-leaf subtree has no
|
|
222
|
+
* single deterministic target leaf (product-coordinate addressing — `set-op-membership-nested`),
|
|
223
|
+
* so the static `is_insertable_into` surface gates to `NO` when this holds, while
|
|
224
|
+
* data/delete fan-out (which touches every member leaf) stays writable. A parenthesized LEFT
|
|
225
|
+
* compound operand is lifted into a `select * from (compound)` wrapper, so the left is unwrapped
|
|
226
|
+
* before probing — a parallel-sibling view (`set-op-leftwrap-write`) also reports NO.
|
|
227
|
+
*/
|
|
228
|
+
export function setOpHasSubtreeOperand(selectAst) {
|
|
229
|
+
if (selectAst.type !== 'select' || !selectAst.compound)
|
|
230
|
+
return false;
|
|
231
|
+
const left = unwrapBranchSelect(leftBranchSelect(selectAst));
|
|
232
|
+
return isSubtreeOperand(left) || isSubtreeOperand(selectAst.compound.select);
|
|
233
|
+
}
|
|
234
|
+
/** True iff an operand SELECT is itself a (non-diff) set-op subtree. */
|
|
235
|
+
function isSubtreeOperand(operand) {
|
|
236
|
+
return operand.type === 'select' && !!operand.compound && operand.compound.op !== 'diff';
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* True iff EVERY multi-source (INNER join) leg/branch of this set-op body would accept an
|
|
240
|
+
* implicit INSERT through the plan-level shared-surrogate envelope
|
|
241
|
+
* (`set-op-write-multisource-leg-insert`). A body with NO join leg returns `true` (its
|
|
242
|
+
* single-source legs are the existing insert-through path); a body with ≥1 join leg returns
|
|
243
|
+
* `true` only when every such leg's `analyzeMultiSourceInsert` probe succeeds.
|
|
244
|
+
*
|
|
245
|
+
* A purely structural inner-vs-outer predicate is INSUFFICIENT: an inner equi-join leg can still
|
|
246
|
+
* reject dynamically with `unsupported-decomposition-key` (a composite shared key — the CV shape),
|
|
247
|
+
* `unsupported-join` (a non-equi ON), or `no-default` (the shared key is neither supplied nor
|
|
248
|
+
* defaulted — e.g. the SJ self-join, whose anchor key `emp.mgr` has no default). So this re-derives
|
|
249
|
+
* the branches the same way the dynamic write does (membership via {@link analyzeSetOpView}, else
|
|
250
|
+
* {@link analyzeFlaglessSetOpView}) and probes each `isMultiSource` leg with
|
|
251
|
+
* {@link analyzeMultiSourceInsert} under a synthetic implicit (empty-column) `InsertStmt`, in
|
|
252
|
+
* try/catch — a leg that throws (composite key, non-equi, no-default, uncovered NOT NULL, …) ⇒
|
|
253
|
+
* `false`. The empty `columns` makes `analyzeMultiSourceInsert` use the implicit supply set, and an
|
|
254
|
+
* inner join leg has no existence columns, so the empty `values` is never read.
|
|
255
|
+
*
|
|
256
|
+
* This is only ever reached after {@link isSetOpBranchWritable} / {@link isSetOpFlaglessWritableBody}
|
|
257
|
+
* already passed (an outer-join branch / non-flagless body short-circuits to the conservative
|
|
258
|
+
* all-`NO` row upstream, never reaching here), so the branch re-derivation will not throw on the
|
|
259
|
+
* body shape; only the per-leg `analyzeMultiSourceInsert` probe may. It must not leak a structured
|
|
260
|
+
* diagnostic out of the read TVF, so every probe is caught.
|
|
261
|
+
*/
|
|
262
|
+
export function setOpJoinLegsInsertable(ctx, view) {
|
|
263
|
+
const branches = isSetOpMembershipBody(view.selectAst)
|
|
264
|
+
? analyzeSetOpView(ctx, view).branches
|
|
265
|
+
: analyzeFlaglessSetOpView(ctx, view).legs.map(l => l.branch);
|
|
266
|
+
for (const branch of branches) {
|
|
267
|
+
if (!branch.isMultiSource)
|
|
268
|
+
continue;
|
|
269
|
+
const probeStmt = {
|
|
270
|
+
type: 'insert',
|
|
271
|
+
table: { type: 'identifier', name: branch.view.name },
|
|
272
|
+
columns: [],
|
|
273
|
+
source: { type: 'values', values: [] },
|
|
274
|
+
};
|
|
275
|
+
try {
|
|
276
|
+
analyzeMultiSourceInsert(ctx, branch.view, probeStmt);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return false; // a leg whose insert envelope rejects (composite / non-equi / no-default key)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The **surfaced inner-branch membership-flag names** of a (possibly nested) set-op body —
|
|
286
|
+
* every flag declared on a subtree operand, surfaced as a readable-but-non-writable column of
|
|
287
|
+
* the outer view. Returned in the plan's recursive `[L flags] ++ [R flags] ++ [own flags]`
|
|
288
|
+
* attribute layout (`SetOperationNode.buildAttributes`), so this list lands element-for-element
|
|
289
|
+
* on the plan-derived `analysis.surfacedInnerFlagNames` (`viewColNames` minus the leading data
|
|
290
|
+
* cols and trailing own flags). Empty for a binary (non-nested) body. Writing one addresses a
|
|
291
|
+
* branch *inside* a subtree operand (product-coordinate addressing), so `buildUpdate` rejects it
|
|
292
|
+
* with a `set-op-membership-nested` diagnostic and the `column_info` surface reports it
|
|
293
|
+
* `is_updatable = NO`.
|
|
294
|
+
*/
|
|
295
|
+
export function surfacedInnerFlagNames(selectAst) {
|
|
296
|
+
const out = [];
|
|
297
|
+
if (selectAst.type === 'select' && selectAst.compound) {
|
|
298
|
+
// Walk BOTH operands in plan layout order: `[L operand surfaced] ++ [R operand surfaced]`
|
|
299
|
+
// (this node's OWN flags are `analysis.flags`, not surfaced-inner, and are excluded here).
|
|
300
|
+
// The unwrap of a parenthesized LEFT compound operand's `select * from (compound)` wrapper
|
|
301
|
+
// (`set-op-leftwrap-write`) lives INSIDE the recursion, so it applies uniformly at every
|
|
302
|
+
// level — pass the raw left/right operands here, not pre-unwrapped.
|
|
303
|
+
collectSubtreeFlagNames(leftBranchSelect(selectAst), out);
|
|
304
|
+
collectSubtreeFlagNames(selectAst.compound.select, out);
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Collect every membership-flag name `operand` (and its deeper subtree operands) surfaces, in
|
|
310
|
+
* the plan's recursive `[L flags] ++ [R flags] ++ [own flags]` attribute layout
|
|
311
|
+
* (`SetOperationNode.buildAttributes`): descend the LEFT leg, then the RIGHT leg, THEN append
|
|
312
|
+
* this node's OWN flags — so the result lands element-for-element on the plan-derived
|
|
313
|
+
* `analysis.surfacedInnerFlagNames` regardless of which leg declared a flag, at any depth.
|
|
314
|
+
*
|
|
315
|
+
* Each operand is first unwrapped via {@link unwrapBranchSelect} (a no-op on a direct operand)
|
|
316
|
+
* so a parenthesized LEFT compound operand's `select * from (compound)` wrapper is descended too
|
|
317
|
+
* (`set-op-leftwrap-write`) — a flag declared on either leg of a left- OR right-side subtree is
|
|
318
|
+
* reached, and a write to one rejects with the clean `set-op-membership-nested` diagnostic
|
|
319
|
+
* rather than `unknown-view-column`.
|
|
320
|
+
*/
|
|
321
|
+
function collectSubtreeFlagNames(operand, out) {
|
|
322
|
+
if (operand.type !== 'select')
|
|
323
|
+
return;
|
|
324
|
+
const effective = unwrapBranchSelect(operand);
|
|
325
|
+
if (!effective.compound || effective.compound.op === 'diff')
|
|
326
|
+
return;
|
|
327
|
+
collectSubtreeFlagNames(leftBranchSelect(effective), out);
|
|
328
|
+
collectSubtreeFlagNames(effective.compound.select, out);
|
|
329
|
+
for (const e of effective.compound.existence ?? [])
|
|
330
|
+
out.push(e.name);
|
|
331
|
+
}
|
|
332
|
+
/** Decompose a set-op membership-column view mutation. Throws a structured diagnostic for unsupported shapes. */
|
|
333
|
+
export function buildSetOpWrite(ctx, view, req) {
|
|
334
|
+
const analysis = analyzeSetOpView(ctx, view);
|
|
335
|
+
switch (req.op) {
|
|
336
|
+
case 'insert': return buildInsertThrough(ctx, view, analysis, req.stmt);
|
|
337
|
+
case 'update': return buildUpdate(ctx, view, analysis, req.stmt);
|
|
338
|
+
case 'delete': return buildDelete(ctx, view, analysis, req.stmt);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// --- analysis -------------------------------------------------------------
|
|
342
|
+
function analyzeSetOpView(ctx, view) {
|
|
343
|
+
if (view.selectAst.type !== 'select' || !view.selectAst.compound) {
|
|
344
|
+
raiseMutationDiagnostic({
|
|
345
|
+
reason: 'unsupported-set-op',
|
|
346
|
+
table: view.name,
|
|
347
|
+
message: `cannot write through view '${view.name}': not a set-operation body`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const sel = view.selectAst;
|
|
351
|
+
const compound = sel.compound;
|
|
352
|
+
if (compound.op === 'diff') {
|
|
353
|
+
raiseMutationDiagnostic({
|
|
354
|
+
reason: 'unsupported-set-op',
|
|
355
|
+
table: view.name,
|
|
356
|
+
message: `cannot write through view '${view.name}': a DIFF (symmetric difference) body has no single addressable branch per row`,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (!compound.existence || compound.existence.length === 0) {
|
|
360
|
+
raiseMutationDiagnostic({
|
|
361
|
+
reason: 'unsupported-set-op',
|
|
362
|
+
table: view.name,
|
|
363
|
+
message: `cannot write through view '${view.name}': a set-operation body is writable only through its membership columns; declare 'exists <branch> as <flag>' to address a branch`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
// LIMIT / OFFSET on the outer compound would put the capture's filter above the
|
|
367
|
+
// window — a write would escape it. Reject (parity with the join / single-source spine).
|
|
368
|
+
if (sel.limit || sel.offset) {
|
|
369
|
+
raiseMutationDiagnostic({
|
|
370
|
+
reason: 'unsupported-limit',
|
|
371
|
+
table: view.name,
|
|
372
|
+
message: `cannot write through view '${view.name}': a LIMIT/OFFSET set-operation body is not decomposable (a write would escape the limited window)`,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const flags = compound.existence.map(e => ({ name: e.name, side: e.branch }));
|
|
376
|
+
// Plan the body ONCE: its root attributes are the view output columns (data columns
|
|
377
|
+
// then the appended membership flags — `set-op-membership-read`'s combinator surface).
|
|
378
|
+
const root = buildSelectStmt(ctx, sel);
|
|
379
|
+
if (!isRelationalNode(root)) {
|
|
380
|
+
raiseMutationDiagnostic({
|
|
381
|
+
reason: 'no-base-lineage',
|
|
382
|
+
table: view.name,
|
|
383
|
+
message: `cannot write through view '${view.name}': the set-operation body did not produce a relation`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const relRoot = root;
|
|
387
|
+
const attrs = relRoot.getAttributes();
|
|
388
|
+
const viewColNames = attrs.map(a => a.name);
|
|
389
|
+
const viewColTypes = attrs.map(a => a.type);
|
|
390
|
+
// Data-column count is the recursive DATA arity of the planned set operation
|
|
391
|
+
// (`SetOperationNode.dataColumnCount()`), NOT `attrs.length - flags.length`: with a nested
|
|
392
|
+
// (flagged) subtree operand, the surfaced inner flags inflate `attrs.length`, so subtracting
|
|
393
|
+
// only the OWN flags would mis-count them as data columns (`nestable-flagged-set-ops`).
|
|
394
|
+
const setOpNode = findSetOpNode(relRoot);
|
|
395
|
+
if (!setOpNode) {
|
|
396
|
+
raiseMutationDiagnostic({
|
|
397
|
+
reason: 'no-base-lineage',
|
|
398
|
+
table: view.name,
|
|
399
|
+
message: `cannot write through view '${view.name}': the set-operation body produced no SetOperationNode`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const dataColCount = setOpNode.dataColumnCount();
|
|
403
|
+
if (dataColCount <= 0) {
|
|
404
|
+
raiseMutationDiagnostic({
|
|
405
|
+
reason: 'unsupported-set-op',
|
|
406
|
+
table: view.name,
|
|
407
|
+
message: `cannot write through view '${view.name}': the set operation exposes no data columns alongside its membership flags`,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
const dataColNames = viewColNames.slice(0, dataColCount);
|
|
411
|
+
// Surfaced inner flags sit BETWEEN the data columns and this node's own flags in the
|
|
412
|
+
// `[data] ++ [L flags] ++ [R flags] ++ [own flags]` layout — `viewColNames` minus the
|
|
413
|
+
// leading data columns and the trailing own flags. Empty for a binary (non-nested) body.
|
|
414
|
+
const surfacedInnerFlagNames = viewColNames.slice(dataColCount, viewColNames.length - flags.length);
|
|
415
|
+
// A scope resolving each view output column name to its producing attribute over the
|
|
416
|
+
// planned root — the same shape `createSetOperationScope` builds for the body itself,
|
|
417
|
+
// reused here so the user predicate / capture projections resolve byte-identically.
|
|
418
|
+
// Parented to `ctx.scope` so a user WHERE's parameters (`where id = ?`), CTE refs, and
|
|
419
|
+
// other ambient symbols still resolve — a view output column shadows them (checked
|
|
420
|
+
// first), and a base-only name still fails to resolve (the statement scope exposes no
|
|
421
|
+
// base columns), so the encapsulation guard is unchanged.
|
|
422
|
+
const viewColScope = new RegisteredScope(ctx.scope);
|
|
423
|
+
attrs.forEach((attr, i) => {
|
|
424
|
+
viewColScope.registerSymbol(attr.name.toLowerCase(), (exp, s) => new ColumnReferenceNode(s, exp, attr.type, attr.id, i));
|
|
425
|
+
});
|
|
426
|
+
const branches = [
|
|
427
|
+
buildBranch(view, 'left', leftBranchSelect(sel), dataColCount, flags),
|
|
428
|
+
buildBranch(view, 'right', rightBranchSelect(view, compound.select), dataColCount, flags),
|
|
429
|
+
];
|
|
430
|
+
return { op: compound.op, root: relRoot, viewColScope, viewColNames, viewColTypes, dataColCount, dataColNames, surfacedInnerFlagNames, flags, branches };
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* The `SetOperationNode` inside a planned body root — the root itself for a bare compound
|
|
434
|
+
* body, else found by descending the relational spine (a body with an outer ORDER BY wraps
|
|
435
|
+
* the set op in a `SortNode`). Its recursive `dataColumnCount()` is the data arity the
|
|
436
|
+
* surfaced-inner-flag count subtraction (`attrs.length - flags.length`) over-counts.
|
|
437
|
+
*/
|
|
438
|
+
function findSetOpNode(node) {
|
|
439
|
+
if (node instanceof SetOperationNode)
|
|
440
|
+
return node;
|
|
441
|
+
for (const child of node.getRelations()) {
|
|
442
|
+
const found = findSetOpNode(child);
|
|
443
|
+
if (found)
|
|
444
|
+
return found;
|
|
445
|
+
}
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Membership-free branch analysis of a nested (subtree) operand: its two inner branches,
|
|
450
|
+
* built without the membership gate `analyzeSetOpView` enforces (a flag-less subtree has no
|
|
451
|
+
* `compound.existence`). The data arity is the OUTER body's (`dataColCount`) — set
|
|
452
|
+
* operations preserve data columns at every depth (the `SetOperationNode` constructor
|
|
453
|
+
* enforces `dataArity(left) === dataArity(right)`), so an inner leaf has exactly the same
|
|
454
|
+
* data columns the outer capture froze. Used by the data/delete fan-out recursion.
|
|
455
|
+
*/
|
|
456
|
+
function analyzeSetOpBranches(view, branchView, dataColCount) {
|
|
457
|
+
const sel = branchView.selectAst;
|
|
458
|
+
const compound = sel.compound;
|
|
459
|
+
const innerFlags = (compound.existence ?? []).map(e => ({ name: e.name, side: e.branch }));
|
|
460
|
+
return [
|
|
461
|
+
buildBranch(view, 'left', leftBranchSelect(sel), dataColCount, innerFlags),
|
|
462
|
+
buildBranch(view, 'right', rightBranchSelect(view, compound.select), dataColCount, innerFlags),
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
/** The left operand's SELECT — the compound statement stripped of its outer modifiers. */
|
|
466
|
+
function leftBranchSelect(sel) {
|
|
467
|
+
const { compound: _c, orderBy: _o, limit: _l, offset: _f, ...leftCore } = sel;
|
|
468
|
+
return leftCore;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* The **effective** operand SELECT of a (possibly parenthesized-compound) operand: the inner
|
|
472
|
+
* compound when `branchSelect` is a pure `select * from (<compound>) as values_N` passthrough
|
|
473
|
+
* wrapper (the shape the parser lifts a parenthesized LEFT compound operand into,
|
|
474
|
+
* `set-op-leftwrap-arity`), else `branchSelect` unchanged. Shared with the read/plan path
|
|
475
|
+
* (`select-compound.ts`'s `unwrapToSelect`, via the same {@link unwrapPassthroughSubquery}
|
|
476
|
+
* predicate) so neither path drifts on what a pure wrapper is.
|
|
477
|
+
*
|
|
478
|
+
* Threading it through {@link buildBranch} makes the wrapped LEFT operand a first-class subtree
|
|
479
|
+
* operand for the write path's unambiguous fan-out (data UPDATE / DELETE / `set <flag> = false`),
|
|
480
|
+
* exactly as the (always-direct) right compound operand already is (`set-op-leftwrap-write`). The
|
|
481
|
+
* unwrap is a no-op on a direct operand (a leaf SELECT, or the right side's direct compound), so
|
|
482
|
+
* applying it uniformly to both sides is safe. A non-SELECT inner (a `select * from (values…)`)
|
|
483
|
+
* stays the wrapper and is rejected downstream as a `select *` leg.
|
|
484
|
+
*/
|
|
485
|
+
function unwrapBranchSelect(branchSelect) {
|
|
486
|
+
const inner = unwrapPassthroughSubquery(branchSelect);
|
|
487
|
+
return inner && inner.type === 'select' ? inner : branchSelect;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The **left-most leaf** SELECT of a (possibly nested, possibly wrapped) operand — the leg whose
|
|
491
|
+
* projection positionally aligns to the set operation's data columns (a `SetOperationNode`
|
|
492
|
+
* preserves its left child's column ids verbatim at every depth). A direct operand IS its own
|
|
493
|
+
* leaf; a nested compound descends its left leg, unwrapping each `select * from (compound)`
|
|
494
|
+
* wrapper. This is what {@link branchColumnNames} reads its data-column names from: a right-spine
|
|
495
|
+
* nested operand's left leg is a direct leaf (so `branchSelect.columns` already named it), but a
|
|
496
|
+
* LEFT-spine nested operand's left leg is itself wrapped (`set-op-leftwrap-write`), so a single
|
|
497
|
+
* `.columns` read there would see the wrapper's `*` — the descent reaches the real leaf instead.
|
|
498
|
+
*/
|
|
499
|
+
function leftmostLeafSelect(branchSelect) {
|
|
500
|
+
let cur = unwrapBranchSelect(branchSelect);
|
|
501
|
+
while (cur.compound && cur.compound.op !== 'diff') {
|
|
502
|
+
cur = unwrapBranchSelect(leftBranchSelect(cur));
|
|
503
|
+
}
|
|
504
|
+
return cur;
|
|
505
|
+
}
|
|
506
|
+
/** The right operand's SELECT, stripped of any leg-local ORDER BY / LIMIT / OFFSET. */
|
|
507
|
+
function rightBranchSelect(view, right) {
|
|
508
|
+
if (right.type !== 'select') {
|
|
509
|
+
raiseMutationDiagnostic({
|
|
510
|
+
reason: 'unsupported-set-op',
|
|
511
|
+
table: view.name,
|
|
512
|
+
message: `cannot write through view '${view.name}': the right branch is a ${right.type.toUpperCase()} operand, which is not a recursively-writable body (v1 supports SELECT operands)`,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const { orderBy: _o, limit: _l, offset: _f, ...core } = right;
|
|
516
|
+
return core;
|
|
517
|
+
}
|
|
518
|
+
/** Build one recursively-writable branch view-like from an operand SELECT. */
|
|
519
|
+
function buildBranch(view, side, branchSelect, dataColCount, flags) {
|
|
520
|
+
// Unwrap a parenthesized LEFT compound operand's `select * from (<compound>)` wrapper to its
|
|
521
|
+
// inner compound, so a wrapped left operand is a first-class subtree operand (its data cols,
|
|
522
|
+
// `isNested`, and recursion all derive from the inner) — `set-op-leftwrap-write`. A no-op on a
|
|
523
|
+
// direct operand.
|
|
524
|
+
const effectiveSelect = unwrapBranchSelect(branchSelect);
|
|
525
|
+
// A subtree operand carries its own (non-diff) compound; its fan-out recurses to leaves.
|
|
526
|
+
const isNested = !!effectiveSelect.compound && effectiveSelect.compound.op !== 'diff';
|
|
527
|
+
// A non-nested **multi-source (INNER join) leg** is now writable for UPDATE / DELETE
|
|
528
|
+
// (`set-op-write-multisource-leg-compose`): its data/delete fan builds an inner per-branch
|
|
529
|
+
// base-PK capture chained off the outer set-op capture (a fresh `__vmupd_keys$N` name), so
|
|
530
|
+
// the join branch decomposes through the multi-source machinery without colliding with the
|
|
531
|
+
// outer capture. An OUTER (left/right/full) / cross join leg is deferred — reject it cleanly
|
|
532
|
+
// here (never routing it to `propagate`, which would mishandle the un-composed capture).
|
|
533
|
+
// (`isInnerJoinBody` keys only on `joinType`, so a NON-EQUI inner join is admitted and
|
|
534
|
+
// composes here, exactly as the standalone join-view path admits it — not rejected.) A nested
|
|
535
|
+
// (subtree) operand is NOT classified here: its join leaves are reached as non-nested branches
|
|
536
|
+
// via `analyzeSetOpBranches` → `buildBranch` and classified at that depth, so this covers every
|
|
537
|
+
// leaf at every nesting level.
|
|
538
|
+
const isMultiSource = !isNested && isInnerJoinBody(effectiveSelect);
|
|
539
|
+
if (!isNested && !isMultiSource && isJoinBody(effectiveSelect)) {
|
|
540
|
+
raiseMutationDiagnostic({
|
|
541
|
+
reason: 'unsupported-set-op',
|
|
542
|
+
table: view.name,
|
|
543
|
+
message: `cannot write through view '${view.name}': the ${side} branch is an OUTER (left/right/full) or cross join leg, which is not a plain INNER join — its set-op write composition is deferred (set-op-write-multisource-leg-compose ships inner-join legs)`,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const dataColNames = branchColumnNames(view, side, effectiveSelect);
|
|
547
|
+
if (dataColNames.length !== dataColCount) {
|
|
548
|
+
raiseMutationDiagnostic({
|
|
549
|
+
reason: 'unsupported-set-op',
|
|
550
|
+
table: view.name,
|
|
551
|
+
message: `cannot write through view '${view.name}': the ${side} branch projects ${dataColNames.length} columns but the set operation exposes ${dataColCount} data columns`,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const branchView = {
|
|
555
|
+
name: `__setop_${side}`,
|
|
556
|
+
schemaName: view.schemaName,
|
|
557
|
+
selectAst: effectiveSelect,
|
|
558
|
+
};
|
|
559
|
+
const flag = flags.find(f => f.side === side);
|
|
560
|
+
return { side, view: branchView, dataColNames, isNested, isMultiSource, ...(flag ? { flag } : {}) };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The branch operand's projected column names (positional). v1 admits plain column
|
|
564
|
+
* references (with optional rename) — the shape that round-trips a base column through
|
|
565
|
+
* the branch's single-source spine. A `select *` leg or a computed projection is
|
|
566
|
+
* rejected (the nested / computed-branch generality is deferred): a `*` has no static
|
|
567
|
+
* name list to align positionally, and a computed leg column has no base column to
|
|
568
|
+
* write a fanned-out value into.
|
|
569
|
+
*/
|
|
570
|
+
function branchColumnNames(view, side, branchSelect) {
|
|
571
|
+
// The data-column names are the left-most leaf's projection (a set op preserves its left
|
|
572
|
+
// child's column ids at every depth) — descend through a nested / left-wrapped operand so a
|
|
573
|
+
// LEFT-spine compound branch derives names from its real leaf, not a wrapper's `*`.
|
|
574
|
+
const leaf = leftmostLeafSelect(branchSelect);
|
|
575
|
+
const names = tryBranchColumnNames(leaf);
|
|
576
|
+
if (names)
|
|
577
|
+
return names;
|
|
578
|
+
// `tryBranchColumnNames` returned `null` ⇒ a `*` or computed leg; re-derive the
|
|
579
|
+
// specific reason for the per-side diagnostic (the shared probe only yields the
|
|
580
|
+
// boolean, so the static surface and this path cannot drift on what counts as writable).
|
|
581
|
+
for (const rc of leaf.columns) {
|
|
582
|
+
if (rc.type === 'all') {
|
|
583
|
+
raiseMutationDiagnostic({
|
|
584
|
+
reason: 'unsupported-set-op',
|
|
585
|
+
table: view.name,
|
|
586
|
+
message: `cannot write through view '${view.name}': the ${side} branch uses 'select *'; list its columns explicitly so each maps to a writable branch column`,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
if (rc.expr.type !== 'column') {
|
|
590
|
+
raiseMutationDiagnostic({
|
|
591
|
+
reason: 'unsupported-set-op',
|
|
592
|
+
table: view.name,
|
|
593
|
+
message: `cannot write through view '${view.name}': the ${side} branch projects a computed column; v1 supports plain (optionally renamed) base columns in a writable branch`,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Unreachable: `tryBranchColumnNames` returns `null` only for a `*`/computed leg, both
|
|
598
|
+
// handled above. Guard defensively rather than returning a wrong (empty) name list.
|
|
599
|
+
raiseMutationDiagnostic({
|
|
600
|
+
reason: 'unsupported-set-op',
|
|
601
|
+
table: view.name,
|
|
602
|
+
message: `cannot write through view '${view.name}': the ${side} branch is not a writable plain-column projection`,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* The non-throwing core of {@link branchColumnNames}: the branch operand's projected
|
|
607
|
+
* plain-column names (positional, honoring a leg rename via `rc.alias ?? rc.expr.name`),
|
|
608
|
+
* or `null` when the leg is not a writable shape — a `select *` (`rc.type === 'all'`, no
|
|
609
|
+
* static name list to align positionally) or a computed (`rc.expr.type !== 'column'`)
|
|
610
|
+
* projection (no base column to write a fanned-out value into). Shared by the dynamic
|
|
611
|
+
* write path and the static {@link isSetOpBranchWritable} probe so neither can drift on
|
|
612
|
+
* what a writable leg is.
|
|
613
|
+
*/
|
|
614
|
+
function tryBranchColumnNames(branchSelect) {
|
|
615
|
+
const names = [];
|
|
616
|
+
for (const rc of branchSelect.columns) {
|
|
617
|
+
if (rc.type === 'all' || rc.expr.type !== 'column')
|
|
618
|
+
return null;
|
|
619
|
+
names.push(rc.alias ?? rc.expr.name);
|
|
620
|
+
}
|
|
621
|
+
return names;
|
|
622
|
+
}
|
|
623
|
+
// --- capture --------------------------------------------------------------
|
|
624
|
+
/**
|
|
625
|
+
* Build the up-front affected-row capture: `Project_{all view cols}(Filter_{userWhere}
|
|
626
|
+
* (setOpRoot))`, materialized ONCE before any branch op fires. Every probe-driven write
|
|
627
|
+
* reads it back through {@link MS_UPDATE_KEYS_CTE}, so the data columns AND the
|
|
628
|
+
* membership-probe flags are frozen at their pre-mutation values (Halloween-safe). The
|
|
629
|
+
* capture column shape (one per view output column, by name) is what each branch op's
|
|
630
|
+
* `k.<col>` reference and the membership-INSERT projection resolve against.
|
|
631
|
+
*/
|
|
632
|
+
function buildSetOpCapture(ctx, analysis, where) {
|
|
633
|
+
const scope = analysis.viewColScope;
|
|
634
|
+
const filtered = where
|
|
635
|
+
? new FilterNode(scope, analysis.root, buildExpression({ ...ctx, scope }, cloneExpr(where)))
|
|
636
|
+
: analysis.root;
|
|
637
|
+
const projections = analysis.viewColNames.map(name => ({
|
|
638
|
+
node: buildExpression({ ...ctx, scope }, { type: 'column', name }),
|
|
639
|
+
alias: name,
|
|
640
|
+
}));
|
|
641
|
+
const source = new ProjectNode(scope, filtered, projections, undefined, undefined, false);
|
|
642
|
+
const keyColumns = analysis.viewColNames.map((name, i) => ({ name, type: analysis.viewColTypes[i] }));
|
|
643
|
+
return { source, descriptor: {}, keyColumns };
|
|
644
|
+
}
|
|
645
|
+
/** Build a fresh {@link JoinLegFan} over the up-front outer set-op capture. */
|
|
646
|
+
function makeJoinLegFan(outerCapture) {
|
|
647
|
+
const captures = [];
|
|
648
|
+
let counter = 0;
|
|
649
|
+
return { outerCapture, captures, mintName: () => `${MS_UPDATE_KEYS_CTE}$${++counter}` };
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Compose one multi-source (INNER join) branch's data UPDATE / DELETE: build the branch base ops
|
|
653
|
+
* via the multi-source decomposer (referencing a fresh inner `__vmupd_keys$N` capture) PLUS the
|
|
654
|
+
* inner per-branch base-PK capture itself, filtered by the branch's `memberExists` predicate (the
|
|
655
|
+
* SAME data-tuple match against the OUTER set-op capture the single-source fan builds), and record
|
|
656
|
+
* the inner capture into the fan accumulator. The inner capture is built under a context with the
|
|
657
|
+
* OUTER capture injected (so its `memberExists` filter's `from __vmupd_keys` resolves), and over
|
|
658
|
+
* the branch's planned join body (`analyzeJoinView`) — so the join leg decomposes through the
|
|
659
|
+
* multi-source machinery without colliding with the outer capture.
|
|
660
|
+
*
|
|
661
|
+
* `decompose` selects UPDATE vs DELETE — both build `BaseOp[]` against the base tables whose
|
|
662
|
+
* predicates read back the fresh inner capture; the inner capture's identity is the branch's
|
|
663
|
+
* affected (captured-and-member-matched) rows. `branchStmt.where` IS the `memberExists` predicate,
|
|
664
|
+
* reused verbatim as the inner capture's filter.
|
|
665
|
+
*/
|
|
666
|
+
function fanMultiSourceBranch(ctx, branch, branchStmt, fan, decompose) {
|
|
667
|
+
const joinAnalysis = analyzeJoinView(ctx, branch.view);
|
|
668
|
+
const name = fan.mintName();
|
|
669
|
+
const baseOps = decompose(joinAnalysis, name);
|
|
670
|
+
const sides = capturedSideIndices(baseOps, joinAnalysis);
|
|
671
|
+
// Inject the OUTER set-op capture so the inner capture's `memberExists` filter (`exists (…
|
|
672
|
+
// from __vmupd_keys k …)`) resolves against it; the inner capture is built over the branch's
|
|
673
|
+
// planned join body under its own fresh name.
|
|
674
|
+
const ctxWithOuter = withKeyCapture(ctx, fan.outerCapture);
|
|
675
|
+
const capture = buildMultiSourceKeyCapture(ctxWithOuter, branch.view, branchStmt.where, joinAnalysis, sides, undefined, name);
|
|
676
|
+
fan.captures.push(capture);
|
|
677
|
+
return baseOps;
|
|
678
|
+
}
|
|
679
|
+
function buildUpdate(ctx, view, analysis, stmt) {
|
|
680
|
+
rejectReturning(view, stmt.returning);
|
|
681
|
+
const flips = new Map();
|
|
682
|
+
const dataAssignments = [];
|
|
683
|
+
for (const asg of stmt.assignments) {
|
|
684
|
+
const flag = analysis.flags.find(f => f.name.toLowerCase() === asg.column.toLowerCase());
|
|
685
|
+
if (flag) {
|
|
686
|
+
const value = asBooleanLiteral(asg.value);
|
|
687
|
+
if (value === undefined) {
|
|
688
|
+
raiseMutationDiagnostic({
|
|
689
|
+
reason: 'unsupported-set-op',
|
|
690
|
+
column: asg.column,
|
|
691
|
+
table: view.name,
|
|
692
|
+
message: `cannot write through view '${view.name}': the membership column '${asg.column}' must be assigned a boolean literal (true/false); a per-row branch on a non-literal value is deferred`,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
const existing = flips.get(flag.side);
|
|
696
|
+
if (existing !== undefined && existing !== value) {
|
|
697
|
+
raiseMutationDiagnostic({
|
|
698
|
+
reason: 'conflicting-assignment',
|
|
699
|
+
column: asg.column,
|
|
700
|
+
table: view.name,
|
|
701
|
+
message: `cannot write through view '${view.name}': the ${flag.side} branch's membership is assigned both true and false in one statement`,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
flips.set(flag.side, value);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
// A surfaced inner flag (`inB`/`inC`) IS a view column, but writing it addresses a
|
|
708
|
+
// branch INSIDE a subtree operand (product-coordinate addressing) — deferred to
|
|
709
|
+
// `set-op-membership-nested`. Reject with a clean diagnostic (NOT `unknown-view-column`,
|
|
710
|
+
// which would mislead — the name resolves).
|
|
711
|
+
if (analysis.surfacedInnerFlagNames.some(n => n.toLowerCase() === asg.column.toLowerCase())) {
|
|
712
|
+
raiseMutationDiagnostic({
|
|
713
|
+
reason: 'unsupported-set-op',
|
|
714
|
+
column: asg.column,
|
|
715
|
+
table: view.name,
|
|
716
|
+
message: `cannot write through view '${view.name}': '${asg.column}' is a surfaced inner-branch membership flag of a nested set operation; writing it addresses a branch inside a subtree operand (product-coordinate addressing) — deferred to set-op-membership-nested`,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
const position = analysis.dataColNames.findIndex(n => n.toLowerCase() === asg.column.toLowerCase());
|
|
720
|
+
if (position < 0) {
|
|
721
|
+
raiseMutationDiagnostic({
|
|
722
|
+
reason: 'unknown-view-column',
|
|
723
|
+
column: asg.column,
|
|
724
|
+
table: view.name,
|
|
725
|
+
message: `cannot write through view '${view.name}': '${asg.column}' is not a data or membership column of the set operation`,
|
|
726
|
+
suggestion: `view '${view.name}' exposes: ${analysis.viewColNames.join(', ')}.`,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
dataAssignments.push({ position, value: asg.value });
|
|
730
|
+
}
|
|
731
|
+
// Contradiction: a `false` flip removes the row from its branch, but a data
|
|
732
|
+
// assignment fans out to every member branch (including that one). The two effects on
|
|
733
|
+
// the same branch contradict (write a column of a row being deleted). Reject rather
|
|
734
|
+
// than silently pick one (parity with the join-existence write's `set npCol, hasB=false`).
|
|
735
|
+
const anyFalseFlip = [...flips.values()].some(v => v === false);
|
|
736
|
+
if (anyFalseFlip && dataAssignments.length > 0) {
|
|
737
|
+
raiseMutationDiagnostic({
|
|
738
|
+
reason: 'conflicting-assignment',
|
|
739
|
+
table: view.name,
|
|
740
|
+
message: `cannot write through view '${view.name}': a membership-flag write removes a branch (= false) while the same statement also writes a data column that fans out to that branch — the two effects contradict`,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
const capture = buildSetOpCapture(ctx, analysis, stmt.where);
|
|
744
|
+
// A multi-source (INNER join) branch the fan reaches builds its own inner per-branch capture
|
|
745
|
+
// chained off this outer capture; the fan accumulates them (`set-op-write-multisource-leg-compose`).
|
|
746
|
+
const fan = makeJoinLegFan(capture);
|
|
747
|
+
const baseOps = [];
|
|
748
|
+
// A `set <joinFlag> = true` membership flip into a multi-source (INNER join) branch is built via
|
|
749
|
+
// the plan-level shared-surrogate envelope and spliced as a nested `ViewMutationNode` child; the
|
|
750
|
+
// accumulator threads into `buildBranchMembershipInsert` (mirror of the `fan` threading) and
|
|
751
|
+
// rides the plan (`set-op-write-multisource-leg-insert`). The nested envelope source reads the
|
|
752
|
+
// OUTER `__vmupd_keys` capture (built above for every UPDATE), so `buildSetOpMutation`'s
|
|
753
|
+
// capture-injected `opCtx` resolves its `from __vmupd_keys`.
|
|
754
|
+
const joinLegInserts = [];
|
|
755
|
+
// Data fan-out: update the row in every member leaf, recursing through a subtree operand
|
|
756
|
+
// to its leaves. The full-data-tuple `exists` correlation restricts each leaf update to
|
|
757
|
+
// the rows actually present there (a non-member leaf matches no row), so the per-branch
|
|
758
|
+
// membership is honored without an explicit flag gate.
|
|
759
|
+
if (dataAssignments.length > 0) {
|
|
760
|
+
for (const branch of analysis.branches) {
|
|
761
|
+
baseOps.push(...fanBranchDataUpdate(ctx, view, analysis, branch, dataAssignments, stmt, fan));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
// Membership flips. `= true` inserts into the branch (rejected for a subtree — a
|
|
765
|
+
// multi-leaf insert has no single target leaf); `= false` is a delete fan-out (recurses
|
|
766
|
+
// through a subtree to drop the row from its resident leaves).
|
|
767
|
+
for (const branch of analysis.branches) {
|
|
768
|
+
const flip = flips.get(branch.side);
|
|
769
|
+
if (flip === undefined)
|
|
770
|
+
continue;
|
|
771
|
+
if (flip) {
|
|
772
|
+
baseOps.push(...buildBranchMembershipInsert(ctx, view, analysis, branch, dataAssignments, stmt, joinLegInserts));
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
baseOps.push(...fanBranchDelete(ctx, view, analysis, branch, stmt, fan));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (baseOps.length === 0 && joinLegInserts.length === 0) {
|
|
779
|
+
// Unreachable: the parser requires ≥1 assignment, and every assignment routes to a
|
|
780
|
+
// flip or a data fan-out above. Guard defensively. (A lone join-branch `= true` flip
|
|
781
|
+
// produces no base op of its own — it splices a nested envelope child — so it is
|
|
782
|
+
// admitted via the `joinLegInserts` arm.)
|
|
783
|
+
raiseMutationDiagnostic({
|
|
784
|
+
reason: 'unsupported-set-op',
|
|
785
|
+
table: view.name,
|
|
786
|
+
message: `cannot write through view '${view.name}': the update names no writable set-operation column`,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
baseOps,
|
|
791
|
+
capture,
|
|
792
|
+
nestedCaptures: fan.captures.length > 0 ? fan.captures : undefined,
|
|
793
|
+
joinLegInserts: joinLegInserts.length > 0 ? joinLegInserts : undefined,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* The captured subtree-membership boundary flag to gate a delete / data fan-out into an
|
|
798
|
+
* `except` / `intersect` subtree operand on — `branch.flag.name`, the `exists <branch> as
|
|
799
|
+
* <flag>` the OUTER compound declared for this side (a view output column, present in the
|
|
800
|
+
* capture, so `k.<flag>` probes "is this captured row a member of the subtree").
|
|
801
|
+
*
|
|
802
|
+
* Gating the recursion on this flag restores soundness: for `except` / `intersect` a leaf can
|
|
803
|
+
* hold rows the subtree EXCLUDES (e.g. a row in both B and C is absent from `B except C`); if
|
|
804
|
+
* an OUTER operand makes that row visible it enters the capture, and a blind fan-out would
|
|
805
|
+
* delete / mutate it in the inner leaves even though it is NOT a subtree member. AND-ing
|
|
806
|
+
* `k.<flag>` into the leaf member-exists restricts the fan to genuine members, making the
|
|
807
|
+
* nested fan behave exactly like the proven binary `except` / `intersect` fan.
|
|
808
|
+
*
|
|
809
|
+
* A **flag-less** non-union boundary (`A union[inA] (B except C)` — no `inSub`) surfaces no
|
|
810
|
+
* boundary probe column to gate on, so it stays **deferred**: reject cleanly, naming
|
|
811
|
+
* `set-op-membership-nested-except` (kept greppable as the remaining deferral).
|
|
812
|
+
*/
|
|
813
|
+
function gateFlagForNonUnionSubtree(view, branch) {
|
|
814
|
+
if (branch.flag)
|
|
815
|
+
return branch.flag.name;
|
|
816
|
+
const op = branch.view.selectAst.compound?.op;
|
|
817
|
+
raiseMutationDiagnostic({
|
|
818
|
+
reason: 'unsupported-set-op',
|
|
819
|
+
table: view.name,
|
|
820
|
+
message: `cannot write through view '${view.name}': a delete / data fan-out through a flag-less ${(op ?? 'set').toUpperCase()} subtree operand is deferred — without a declared subtree-membership flag ('exists <branch> as <flag>') there is no captured boundary probe to gate the fan on, so it could touch leaf rows the subtree excludes (set-op-membership-nested-except)`,
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Accumulate the membership gate for descending into a nested (subtree) `branch`: a **union /
|
|
825
|
+
* union all** subtree adds nothing (a leaf ⊆ the subtree, so leaf-presence already implies
|
|
826
|
+
* membership), an **`except` / `intersect`** subtree contributes its captured boundary flag
|
|
827
|
+
* (`set-op-membership-nested-except`; a flag-less non-union boundary throws, staying deferred).
|
|
828
|
+
* Shared by {@link fanBranchDataUpdate} and {@link fanBranchDelete} so the two fan paths cannot
|
|
829
|
+
* drift on the gate logic.
|
|
830
|
+
*/
|
|
831
|
+
function accumulateInnerGate(view, branch, gateFlags) {
|
|
832
|
+
const subOp = branch.view.selectAst.compound.op;
|
|
833
|
+
return isUnionLikeSubtree(subOp)
|
|
834
|
+
? gateFlags
|
|
835
|
+
: [...gateFlags, gateFlagForNonUnionSubtree(view, branch)];
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Fan a data-column UPDATE out to one branch — recursing through a nested (subtree) operand
|
|
839
|
+
* to its member leaves, else updating the leaf's member rows (matched via the shared capture).
|
|
840
|
+
*
|
|
841
|
+
* The recursion reuses the SINGLE up-front capture: a subtree's leaves share the outer's data
|
|
842
|
+
* columns (nesting preserves them), so "update the leaf rows whose data tuple ∈ `__vmupd_keys`"
|
|
843
|
+
* is the same frozen-capture correlation rebuilt against each inner branch — no second capture.
|
|
844
|
+
* The positional `dataAssignments` fan unchanged (each re-mapped to the leaf's own column name
|
|
845
|
+
* at that data position via `branch.dataColNames[da.position]`); the value is cloned fresh at
|
|
846
|
+
* each leaf (its refs resolve against that leaf's columns when leg names match — the v1 caveat).
|
|
847
|
+
*/
|
|
848
|
+
function fanBranchDataUpdate(ctx, view, analysis, branch, dataAssignments, stmt, fan, gateFlags = []) {
|
|
849
|
+
if (branch.isNested) {
|
|
850
|
+
const innerGate = accumulateInnerGate(view, branch, gateFlags);
|
|
851
|
+
const baseOps = [];
|
|
852
|
+
for (const inner of analyzeSetOpBranches(view, branch.view, analysis.dataColCount)) {
|
|
853
|
+
baseOps.push(...fanBranchDataUpdate(ctx, view, analysis, inner, dataAssignments, stmt, fan, innerGate));
|
|
854
|
+
}
|
|
855
|
+
return baseOps;
|
|
856
|
+
}
|
|
857
|
+
const assignments = dataAssignments.map(da => ({
|
|
858
|
+
column: branch.dataColNames[da.position],
|
|
859
|
+
value: cloneExpr(da.value),
|
|
860
|
+
}));
|
|
861
|
+
const updateStmt = {
|
|
862
|
+
type: 'update',
|
|
863
|
+
table: { type: 'identifier', name: branch.view.name },
|
|
864
|
+
assignments,
|
|
865
|
+
where: buildMemberExists(analysis, branch, gateFlags),
|
|
866
|
+
contextValues: stmt.contextValues,
|
|
867
|
+
schemaPath: stmt.schemaPath,
|
|
868
|
+
loc: stmt.loc,
|
|
869
|
+
};
|
|
870
|
+
// A multi-source (INNER join) branch routes through the join decomposer + an inner per-branch
|
|
871
|
+
// capture (chained off the outer set-op capture) rather than plain `propagate` (which builds
|
|
872
|
+
// no capture and would collide the inner capture with the outer). § set-op-write-multisource-leg-compose.
|
|
873
|
+
if (branch.isMultiSource) {
|
|
874
|
+
return fanMultiSourceBranch(ctx, branch, updateStmt, fan, (joinAnalysis, name) => decomposeUpdate(ctx, branch.view, joinAnalysis, updateStmt, undefined, name));
|
|
875
|
+
}
|
|
876
|
+
return propagate(ctx, branch.view, { op: 'update', stmt: updateStmt });
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* `set <flag> = true`: insert the captured rows that are **absent** from this branch
|
|
880
|
+
* (`where not k.<flag>`) into it. Composed same-statement data assignments flow into the
|
|
881
|
+
* inserted projection (the new value); every other data column reads the captured row.
|
|
882
|
+
*/
|
|
883
|
+
function buildBranchMembershipInsert(ctx, view, analysis, branch, dataAssignments, stmt, joinLegInserts) {
|
|
884
|
+
if (branch.isNested) {
|
|
885
|
+
// `set <subtreeFlag> = true` would insert the row into a multi-leaf subtree (B∪C) —
|
|
886
|
+
// "which leaf?" has no single deterministic answer (product-coordinate addressing).
|
|
887
|
+
// Deferred to `set-op-membership-nested`. (The `= false` flip routes to the delete
|
|
888
|
+
// fan-out, which IS unambiguous — it touches every member leaf.)
|
|
889
|
+
raiseMutationDiagnostic({
|
|
890
|
+
reason: 'unsupported-set-op',
|
|
891
|
+
column: branch.flag?.name,
|
|
892
|
+
table: view.name,
|
|
893
|
+
message: `cannot write through view '${view.name}': 'set ${branch.flag?.name ?? '<flag>'} = true' inserts into a multi-leaf subtree operand, which has no single deterministic target leaf (product-coordinate addressing) — deferred to set-op-membership-nested`,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
if (!branch.flag) {
|
|
897
|
+
// Unreachable on the flip path (a flip targets a declared flag's side), but guard.
|
|
898
|
+
raiseMutationDiagnostic({
|
|
899
|
+
reason: 'unsupported-set-op',
|
|
900
|
+
table: view.name,
|
|
901
|
+
message: `cannot write through view '${view.name}': the ${branch.side} branch has no membership flag to insert through`,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const assignedByPosition = new Map();
|
|
905
|
+
for (const da of dataAssignments)
|
|
906
|
+
assignedByPosition.set(da.position, da.value);
|
|
907
|
+
const projections = analysis.dataColNames.map((uName, i) => {
|
|
908
|
+
const assigned = assignedByPosition.get(i);
|
|
909
|
+
const expr = assigned !== undefined
|
|
910
|
+
? qualifyDataRefsWithCapture(view, analysis, assigned)
|
|
911
|
+
: { type: 'column', name: uName, table: 'k' };
|
|
912
|
+
return { type: 'column', expr, alias: branch.dataColNames[i] };
|
|
913
|
+
});
|
|
914
|
+
const source = {
|
|
915
|
+
type: 'select',
|
|
916
|
+
columns: projections,
|
|
917
|
+
from: [{ type: 'table', table: { type: 'identifier', name: MS_UPDATE_KEYS_CTE }, alias: 'k' }],
|
|
918
|
+
// Only the captured rows NOT already in this branch — the probe makes a redundant
|
|
919
|
+
// `= true` a clean no-op (and folds the per-operator semantics: `except`'s right
|
|
920
|
+
// flag is always false ⇒ insert all, `intersect`'s flags are always true ⇒ none).
|
|
921
|
+
where: { type: 'unary', operator: 'NOT', expr: { type: 'column', name: branch.flag.name, table: 'k' } },
|
|
922
|
+
};
|
|
923
|
+
const insertStmt = {
|
|
924
|
+
type: 'insert',
|
|
925
|
+
table: { type: 'identifier', name: branch.view.name },
|
|
926
|
+
columns: [...branch.dataColNames],
|
|
927
|
+
source,
|
|
928
|
+
contextValues: stmt.contextValues,
|
|
929
|
+
schemaPath: stmt.schemaPath,
|
|
930
|
+
loc: stmt.loc,
|
|
931
|
+
};
|
|
932
|
+
// A `set <flag> = true` flip into a multi-source (INNER join) branch is built via the
|
|
933
|
+
// plan-level shared-surrogate envelope (`buildMultiSourceInsert`) and spliced as a nested
|
|
934
|
+
// `ViewMutationNode` child — record the descriptor and produce NO base op of our own
|
|
935
|
+
// (`set-op-write-multisource-leg-insert`). The `source` SELECT reads the OUTER `__vmupd_keys`
|
|
936
|
+
// capture (`where not k.<flag>`), so the envelope reuses it verbatim; `buildSetOpMutation`
|
|
937
|
+
// passes the capture-injected context so its `from __vmupd_keys` resolves. A single-source
|
|
938
|
+
// branch lowers through `propagate`.
|
|
939
|
+
if (branch.isMultiSource) {
|
|
940
|
+
joinLegInserts.push({ view: branch.view, stmt: insertStmt });
|
|
941
|
+
return [];
|
|
942
|
+
}
|
|
943
|
+
return propagate(ctx, branch.view, { op: 'insert', stmt: insertStmt });
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Fan a DELETE out to one branch — recursing through a nested (subtree) operand to its member
|
|
947
|
+
* leaves, else deleting the leaf's matching rows (every captured row present there). Serves
|
|
948
|
+
* both the `delete from V` fan-out and the `set <subtreeFlag> = false` subtree drop (a
|
|
949
|
+
* delete fan-out into the subtree's leaves), so it takes either originating statement and
|
|
950
|
+
* reads only its shared `contextValues` / `schemaPath` / `loc`. Reuses the SINGLE up-front
|
|
951
|
+
* capture (the same frozen-data-tuple correlation, rebuilt against each inner branch).
|
|
952
|
+
*/
|
|
953
|
+
function fanBranchDelete(ctx, view, analysis, branch, stmt, fan, gateFlags = []) {
|
|
954
|
+
if (branch.isNested) {
|
|
955
|
+
const innerGate = accumulateInnerGate(view, branch, gateFlags);
|
|
956
|
+
const baseOps = [];
|
|
957
|
+
for (const inner of analyzeSetOpBranches(view, branch.view, analysis.dataColCount)) {
|
|
958
|
+
baseOps.push(...fanBranchDelete(ctx, view, analysis, inner, stmt, fan, innerGate));
|
|
959
|
+
}
|
|
960
|
+
return baseOps;
|
|
961
|
+
}
|
|
962
|
+
const deleteStmt = {
|
|
963
|
+
type: 'delete',
|
|
964
|
+
table: { type: 'identifier', name: branch.view.name },
|
|
965
|
+
where: buildMemberExists(analysis, branch, gateFlags),
|
|
966
|
+
contextValues: stmt.contextValues,
|
|
967
|
+
schemaPath: stmt.schemaPath,
|
|
968
|
+
loc: stmt.loc,
|
|
969
|
+
};
|
|
970
|
+
// A multi-source (INNER join) branch routes through the join decomposer + an inner per-branch
|
|
971
|
+
// capture (chained off the outer set-op capture); a single-source branch lowers via `propagate`.
|
|
972
|
+
if (branch.isMultiSource) {
|
|
973
|
+
return fanMultiSourceBranch(ctx, branch, deleteStmt, fan, (joinAnalysis, name) => decomposeDelete(ctx, branch.view, joinAnalysis, deleteStmt, name));
|
|
974
|
+
}
|
|
975
|
+
return propagate(ctx, branch.view, { op: 'delete', stmt: deleteStmt });
|
|
976
|
+
}
|
|
977
|
+
// --- DELETE (fan-out via the probe) ---------------------------------------
|
|
978
|
+
function buildDelete(ctx, view, analysis, stmt) {
|
|
979
|
+
rejectReturning(view, stmt.returning);
|
|
980
|
+
const capture = buildSetOpCapture(ctx, analysis, stmt.where);
|
|
981
|
+
const fan = makeJoinLegFan(capture);
|
|
982
|
+
const baseOps = [];
|
|
983
|
+
// Delete from every member leaf at every depth — the fan recurses through a subtree
|
|
984
|
+
// operand to its leaves (the full-tuple `exists` correlation restricts each leaf delete to
|
|
985
|
+
// its resident rows, so a non-member leaf matches none). A multi-source (INNER join) branch
|
|
986
|
+
// builds its own inner per-branch capture chained off this outer capture.
|
|
987
|
+
for (const branch of analysis.branches) {
|
|
988
|
+
baseOps.push(...fanBranchDelete(ctx, view, analysis, branch, stmt, fan));
|
|
989
|
+
}
|
|
990
|
+
return { baseOps, capture, nestedCaptures: fan.captures.length > 0 ? fan.captures : undefined };
|
|
991
|
+
}
|
|
992
|
+
// --- INSERT (insert-through, flag-routed) ---------------------------------
|
|
993
|
+
function buildInsertThrough(ctx, view, analysis, stmt) {
|
|
994
|
+
rejectReturning(view, stmt.returning);
|
|
995
|
+
if (stmt.source.type !== 'values') {
|
|
996
|
+
raiseMutationDiagnostic({
|
|
997
|
+
reason: 'unsupported-source',
|
|
998
|
+
table: view.name,
|
|
999
|
+
message: `cannot insert through view '${view.name}': a set-operation insert routes by literal membership flags, so it requires a VALUES source (a SELECT/DML source's per-row routing is deferred)`,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
// Resolve each VALUES position to a data column or a membership flag. An explicit
|
|
1003
|
+
// column list maps by name; an omitted list maps positionally over the view's output
|
|
1004
|
+
// layout (data columns then flags).
|
|
1005
|
+
const layout = resolveInsertLayout(view, analysis, stmt);
|
|
1006
|
+
// The flags route the insert: a true flag activates its branch, a false flag omits it.
|
|
1007
|
+
// The flag value must be a uniform boolean literal across the inserted rows (a per-row
|
|
1008
|
+
// mix is deferred), mirroring the join-existence insert directive.
|
|
1009
|
+
const activeSides = new Set();
|
|
1010
|
+
for (const flag of analysis.flags) {
|
|
1011
|
+
const pos = layout.flagPositions.get(flag.name.toLowerCase());
|
|
1012
|
+
if (pos === undefined) {
|
|
1013
|
+
raiseMutationDiagnostic({
|
|
1014
|
+
reason: 'unsupported-set-op',
|
|
1015
|
+
column: flag.name,
|
|
1016
|
+
table: view.name,
|
|
1017
|
+
message: `cannot insert through view '${view.name}': the membership flag '${flag.name}' must be supplied to route the insert to its branch (a flag-less multi-branch insert is ambiguous)`,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
if (uniformBooleanFlag(view, stmt.source.values, pos, flag.name))
|
|
1021
|
+
activeSides.add(flag.side);
|
|
1022
|
+
}
|
|
1023
|
+
if (activeSides.size === 0) {
|
|
1024
|
+
raiseMutationDiagnostic({
|
|
1025
|
+
reason: 'unsupported-set-op',
|
|
1026
|
+
table: view.name,
|
|
1027
|
+
message: `cannot insert through view '${view.name}': no membership flag is true, so the inserted row would belong to no branch (set at least one 'exists' flag true)`,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
const baseOps = [];
|
|
1031
|
+
// A multi-source (INNER join) branch's VALUES insert is built via the plan-level
|
|
1032
|
+
// shared-surrogate envelope (`buildMultiSourceInsert`) and spliced as a nested
|
|
1033
|
+
// `ViewMutationNode` child rather than routed through `propagate` (which would raise the
|
|
1034
|
+
// internal `unsupported-multisource-insert`) — `set-op-write-multisource-leg-insert`.
|
|
1035
|
+
const joinLegInserts = [];
|
|
1036
|
+
for (const branch of analysis.branches) {
|
|
1037
|
+
if (!activeSides.has(branch.side))
|
|
1038
|
+
continue;
|
|
1039
|
+
if (branch.isNested) {
|
|
1040
|
+
// Routing a VALUES row into a multi-leaf subtree operand has no single deterministic
|
|
1041
|
+
// target leaf (product-coordinate addressing) — deferred to `set-op-membership-nested`.
|
|
1042
|
+
// A leaf-only active side still inserts normally.
|
|
1043
|
+
raiseMutationDiagnostic({
|
|
1044
|
+
reason: 'unsupported-set-op',
|
|
1045
|
+
table: view.name,
|
|
1046
|
+
message: `cannot insert through view '${view.name}': the active routing flag targets a multi-leaf subtree operand, which has no single deterministic target leaf for a VALUES row (product-coordinate addressing) — deferred to set-op-membership-nested`,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
const values = stmt.source.values.map(row => layout.dataPositions.map(p => cloneExpr(row[p])));
|
|
1050
|
+
const source = { type: 'values', values };
|
|
1051
|
+
const insertStmt = {
|
|
1052
|
+
type: 'insert',
|
|
1053
|
+
table: { type: 'identifier', name: branch.view.name },
|
|
1054
|
+
columns: [...branch.dataColNames],
|
|
1055
|
+
source,
|
|
1056
|
+
onConflict: stmt.onConflict,
|
|
1057
|
+
contextValues: stmt.contextValues,
|
|
1058
|
+
schemaPath: stmt.schemaPath,
|
|
1059
|
+
loc: stmt.loc,
|
|
1060
|
+
};
|
|
1061
|
+
// The VALUES source is self-contained (no outer capture); the nested envelope sources
|
|
1062
|
+
// its values straight from it. A single-source branch lowers through `propagate`.
|
|
1063
|
+
if (branch.isMultiSource) {
|
|
1064
|
+
joinLegInserts.push({ view: branch.view, stmt: insertStmt });
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
baseOps.push(...propagate(ctx, branch.view, { op: 'insert', stmt: insertStmt }));
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
// Insert-through reads no existing row, so it needs no capture (self-contained values).
|
|
1071
|
+
return { baseOps, joinLegInserts: joinLegInserts.length > 0 ? joinLegInserts : undefined };
|
|
1072
|
+
}
|
|
1073
|
+
/** Map the insert's column list (explicit or positional) onto data columns + membership flags. */
|
|
1074
|
+
function resolveInsertLayout(view, analysis, stmt) {
|
|
1075
|
+
const flagNames = new Set(analysis.flags.map(f => f.name.toLowerCase()));
|
|
1076
|
+
const dataNames = new Set(analysis.dataColNames.map(n => n.toLowerCase()));
|
|
1077
|
+
const cols = stmt.columns && stmt.columns.length > 0 ? stmt.columns : analysis.viewColNames;
|
|
1078
|
+
const dataByName = new Map(); // data col name → VALUES position
|
|
1079
|
+
const flagPositions = new Map();
|
|
1080
|
+
cols.forEach((rawName, pos) => {
|
|
1081
|
+
const name = rawName.toLowerCase();
|
|
1082
|
+
if (flagNames.has(name)) {
|
|
1083
|
+
flagPositions.set(name, pos);
|
|
1084
|
+
}
|
|
1085
|
+
else if (dataNames.has(name)) {
|
|
1086
|
+
dataByName.set(name, pos);
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
raiseMutationDiagnostic({
|
|
1090
|
+
reason: 'unknown-view-column',
|
|
1091
|
+
column: rawName,
|
|
1092
|
+
table: view.name,
|
|
1093
|
+
message: `cannot insert through view '${view.name}': '${rawName}' is not a data or membership column of the set operation`,
|
|
1094
|
+
suggestion: `view '${view.name}' exposes: ${analysis.viewColNames.join(', ')}.`,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
// Data columns must be supplied in full (the branches need every data column to build
|
|
1099
|
+
// a row); a missing one is left to the branch's own NOT NULL / default handling only
|
|
1100
|
+
// when truly omittable — v1 requires the data columns be supplied for an insert-through.
|
|
1101
|
+
const dataPositions = analysis.dataColNames.map(name => {
|
|
1102
|
+
const pos = dataByName.get(name.toLowerCase());
|
|
1103
|
+
if (pos === undefined) {
|
|
1104
|
+
raiseMutationDiagnostic({
|
|
1105
|
+
reason: 'no-default',
|
|
1106
|
+
column: name,
|
|
1107
|
+
table: view.name,
|
|
1108
|
+
message: `cannot insert through view '${view.name}': data column '${name}' is not supplied; a set-operation insert-through requires every data column`,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
return pos;
|
|
1112
|
+
});
|
|
1113
|
+
return { dataPositions, flagPositions };
|
|
1114
|
+
}
|
|
1115
|
+
// --- helpers --------------------------------------------------------------
|
|
1116
|
+
/**
|
|
1117
|
+
* The correlated `exists (select 1 from __vmupd_keys k where <k matches the branch row>)`
|
|
1118
|
+
* a fan-out / membership-delete routes on. The match is a NULL-safe full-data-tuple
|
|
1119
|
+
* comparison: each data column is `k.c = b.c or (k.c is null and b.c is null)` (set
|
|
1120
|
+
* operations treat `NULL = NULL` as equal; the engine has no `IS NOT DISTINCT FROM`).
|
|
1121
|
+
* The branch columns are qualified with the synthetic branch-view name so {@link propagate}
|
|
1122
|
+
* lowers them to the target row (`__vm_self`); the `k.*` columns resolve to the injected
|
|
1123
|
+
* `__vmupd_keys` relation.
|
|
1124
|
+
*
|
|
1125
|
+
* In the nested fan-out the OUTER `analysis` is threaded unchanged, so `k.*` keeps naming the
|
|
1126
|
+
* one outer capture's data columns while `branch.*` names the inner leaf's — sound because a
|
|
1127
|
+
* subtree preserves the data columns at every depth (`buildBranch`'s arity check guarantees
|
|
1128
|
+
* `branch.dataColNames.length === analysis.dataColCount`).
|
|
1129
|
+
*
|
|
1130
|
+
* **Membership gate** (`set-op-membership-nested-except`). For a **union / union all** subtree
|
|
1131
|
+
* a leaf's rows ⊆ the subtree's, so the frozen capture selects exactly the leaf rows to touch
|
|
1132
|
+
* and no extra conjunct is needed (`gateFlags` empty). For an **`except` / `intersect`** subtree
|
|
1133
|
+
* a leaf can hold rows the subtree EXCLUDES; if an outer operand makes such a row visible it
|
|
1134
|
+
* enters the capture, and a blind fan-out would touch it in the leaves even though it is NOT a
|
|
1135
|
+
* subtree member. To stay sound, each non-union boundary descended contributes its captured
|
|
1136
|
+
* **subtree-membership boundary flag** (the `exists <branch> as <flag>` the OUTER compound
|
|
1137
|
+
* declared for that side, a view output column present in `__vmupd_keys`); `gateFlags` AND-s a
|
|
1138
|
+
* fresh `k.<flag>` per accumulated boundary into the predicate, restricting the fan to genuine
|
|
1139
|
+
* members. Fresh `ColumnExpr` nodes are built per call because the gate is reused across leaves.
|
|
1140
|
+
*/
|
|
1141
|
+
function buildMemberExists(analysis, branch, gateFlags = []) {
|
|
1142
|
+
let pred;
|
|
1143
|
+
for (let i = 0; i < analysis.dataColCount; i++) {
|
|
1144
|
+
const colMatch = nullSafeEqual({ type: 'column', name: analysis.dataColNames[i], table: 'k' }, { type: 'column', name: branch.dataColNames[i], table: branch.view.name });
|
|
1145
|
+
pred = pred ? { type: 'binary', operator: 'AND', left: pred, right: colMatch } : colMatch;
|
|
1146
|
+
}
|
|
1147
|
+
// AND the accumulated subtree-membership boundary flags in. `dataColCount > 0`
|
|
1148
|
+
// (checked in `analyzeSetOpView`) guarantees `pred` is defined here.
|
|
1149
|
+
for (const flag of gateFlags) {
|
|
1150
|
+
const flagRef = { type: 'column', name: flag, table: 'k' };
|
|
1151
|
+
pred = pred ? { type: 'binary', operator: 'AND', left: pred, right: flagRef } : flagRef;
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
type: 'exists',
|
|
1155
|
+
subquery: {
|
|
1156
|
+
type: 'select',
|
|
1157
|
+
columns: [{ type: 'column', expr: { type: 'literal', value: 1 } }],
|
|
1158
|
+
from: [{ type: 'table', table: { type: 'identifier', name: MS_UPDATE_KEYS_CTE }, alias: 'k' }],
|
|
1159
|
+
where: pred,
|
|
1160
|
+
},
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
/** `a = b or (a is null and b is null)` — null-safe equality built from primitives. */
|
|
1164
|
+
function nullSafeEqual(a, b) {
|
|
1165
|
+
const eq = { type: 'binary', operator: '=', left: { ...a }, right: { ...b } };
|
|
1166
|
+
const bothNull = {
|
|
1167
|
+
type: 'binary',
|
|
1168
|
+
operator: 'AND',
|
|
1169
|
+
left: { type: 'unary', operator: 'IS NULL', expr: { ...a } },
|
|
1170
|
+
right: { type: 'unary', operator: 'IS NULL', expr: { ...b } },
|
|
1171
|
+
};
|
|
1172
|
+
return { type: 'binary', operator: 'OR', left: eq, right: bothNull };
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Rewrite a composed data-assignment value (in view / data-column terms) so its column
|
|
1176
|
+
* references read the captured row (`k.<col>`) — the membership-INSERT projection runs
|
|
1177
|
+
* over `__vmupd_keys`, not the branch. A reference to a membership flag, or to a name
|
|
1178
|
+
* that is neither a data column, is rejected (a data value cannot read a routing flag).
|
|
1179
|
+
*/
|
|
1180
|
+
function qualifyDataRefsWithCapture(view, analysis, value) {
|
|
1181
|
+
const dataNames = new Set(analysis.dataColNames.map(n => n.toLowerCase()));
|
|
1182
|
+
return transformExpr(value, (col) => {
|
|
1183
|
+
if (col.table)
|
|
1184
|
+
return undefined; // already qualified (e.g. a correlated outer ref) — leave it
|
|
1185
|
+
if (dataNames.has(col.name.toLowerCase()))
|
|
1186
|
+
return { type: 'column', name: col.name, table: 'k' };
|
|
1187
|
+
raiseMutationDiagnostic({
|
|
1188
|
+
reason: 'unsupported-set-op',
|
|
1189
|
+
column: col.name,
|
|
1190
|
+
table: view.name,
|
|
1191
|
+
message: `cannot write through view '${view.name}': the membership-insert value references '${col.name}', which is not a data column; a data value cannot read a membership flag`,
|
|
1192
|
+
});
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* The uniform boolean directive a membership flag supplies on an insert-through — `true`
|
|
1197
|
+
* activates its branch, `false` omits it. Must be the SAME boolean literal across every
|
|
1198
|
+
* inserted row (a per-row branch on the value is deferred — `set-op-membership-nested`).
|
|
1199
|
+
*/
|
|
1200
|
+
function uniformBooleanFlag(view, rows, position, flagName) {
|
|
1201
|
+
let flag;
|
|
1202
|
+
for (const row of rows) {
|
|
1203
|
+
const cell = row[position];
|
|
1204
|
+
const b = cell ? asBooleanLiteral(cell) : undefined;
|
|
1205
|
+
if (b === undefined) {
|
|
1206
|
+
raiseMutationDiagnostic({
|
|
1207
|
+
reason: 'unsupported-set-op',
|
|
1208
|
+
column: flagName,
|
|
1209
|
+
table: view.name,
|
|
1210
|
+
message: `cannot insert through view '${view.name}': the membership flag '${flagName}' must be a boolean literal (true/false); a non-literal per-row directive is deferred`,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
if (flag === undefined)
|
|
1214
|
+
flag = b;
|
|
1215
|
+
else if (flag !== b) {
|
|
1216
|
+
raiseMutationDiagnostic({
|
|
1217
|
+
reason: 'unsupported-set-op',
|
|
1218
|
+
column: flagName,
|
|
1219
|
+
table: view.name,
|
|
1220
|
+
message: `cannot insert through view '${view.name}': the membership flag '${flagName}' must be uniform across the inserted rows (a per-row mix of true/false is deferred)`,
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return flag ?? false;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* The boolean value of a literal membership assignment (`true`/`false`, or the numeric
|
|
1228
|
+
* `1`/`0` spellings), or `undefined` for any non-literal / non-boolean value (a per-row
|
|
1229
|
+
* branch on the written value is deferred). Mirrors the join-existence write's gate.
|
|
1230
|
+
*/
|
|
1231
|
+
function asBooleanLiteral(expr) {
|
|
1232
|
+
if (expr.type !== 'literal')
|
|
1233
|
+
return undefined;
|
|
1234
|
+
const v = expr.value;
|
|
1235
|
+
if (v === true || v === false)
|
|
1236
|
+
return v;
|
|
1237
|
+
if (v === 1 || v === 1n)
|
|
1238
|
+
return true;
|
|
1239
|
+
if (v === 0 || v === 0n)
|
|
1240
|
+
return false;
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
/** RETURNING through a set-op membership write is not yet recoverable — reject. */
|
|
1244
|
+
function rejectReturning(view, returning) {
|
|
1245
|
+
if (returning && returning.length > 0) {
|
|
1246
|
+
raiseMutationDiagnostic({
|
|
1247
|
+
reason: 'returning-through-view',
|
|
1248
|
+
table: view.name,
|
|
1249
|
+
message: `cannot write through view '${view.name}': RETURNING is not yet supported on a set-operation membership write (the per-branch fan-out yields no single recoverable view row)`,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/** Peel Cast/Collate wrappers to expose an underlying AST literal value, else `undefined`. */
|
|
1254
|
+
function peelToLiteral(expr) {
|
|
1255
|
+
let e = expr;
|
|
1256
|
+
while (e.type === 'cast' || e.type === 'collate')
|
|
1257
|
+
e = e.expr;
|
|
1258
|
+
if (e.type !== 'literal')
|
|
1259
|
+
return undefined;
|
|
1260
|
+
const v = e.value;
|
|
1261
|
+
if (v instanceof Promise)
|
|
1262
|
+
return undefined;
|
|
1263
|
+
return v;
|
|
1264
|
+
}
|
|
1265
|
+
/** Classify one leg result column (the shadow of {@link tryBranchColumnNames}, admitting literals). */
|
|
1266
|
+
function legColumnKind(rc) {
|
|
1267
|
+
if (rc.type === 'all')
|
|
1268
|
+
return null;
|
|
1269
|
+
if (rc.expr.type === 'column')
|
|
1270
|
+
return { kind: 'column', name: rc.alias ?? rc.expr.name };
|
|
1271
|
+
const lit = peelToLiteral(rc.expr);
|
|
1272
|
+
if (lit !== undefined)
|
|
1273
|
+
return { kind: 'literal', value: lit };
|
|
1274
|
+
return null; // a computed (non-literal) projection — not a writable / discriminator shape
|
|
1275
|
+
}
|
|
1276
|
+
/** Strip a leg's own ORDER BY / LIMIT / OFFSET (those belong to the outer compound), keeping `compound`. */
|
|
1277
|
+
function stripLegModifiers(sel) {
|
|
1278
|
+
const { orderBy: _o, limit: _l, offset: _f, ...core } = sel;
|
|
1279
|
+
return core;
|
|
1280
|
+
}
|
|
1281
|
+
/** True iff a leg projects ≥1 literal discriminator (a routing constant). */
|
|
1282
|
+
function hasLiteralDiscriminator(leaf) {
|
|
1283
|
+
return leaf.columns.some(rc => legColumnKind(rc)?.kind === 'literal');
|
|
1284
|
+
}
|
|
1285
|
+
/** True iff a leaf SELECT is a writable flag-less leg: single-source, no compound, ≥1 plain/literal column, all admitted. */
|
|
1286
|
+
function isWritableLeafLeg(leaf) {
|
|
1287
|
+
if (leaf.compound)
|
|
1288
|
+
return false;
|
|
1289
|
+
// A multi-source (join) leg is writable for UPDATE / DELETE when it is an INNER join
|
|
1290
|
+
// (`set-op-write-multisource-leg-compose`, which builds an inner per-branch capture chained
|
|
1291
|
+
// off the outer set-op capture). `isInnerJoinBody` keys only on `joinType`, so a non-equi
|
|
1292
|
+
// (theta) INNER join is admitted here exactly as on the membership and standalone paths. An
|
|
1293
|
+
// OUTER (left/right/full) / cross join leg is deferred: falling to `false` here drops the
|
|
1294
|
+
// WHOLE body out of the flag-less route (via `flaglessShape`'s per-leg walk) → conservative
|
|
1295
|
+
// all-`NO` static surface + the single-source spine's clean `unsupported-set-op` reject.
|
|
1296
|
+
// (Comma joins are unreachable — the reader rejects multiple FROM sources at build time.)
|
|
1297
|
+
if (isJoinBody(leaf) && !isInnerJoinBody(leaf))
|
|
1298
|
+
return false;
|
|
1299
|
+
if (!leaf.columns || leaf.columns.length === 0)
|
|
1300
|
+
return false;
|
|
1301
|
+
return leaf.columns.every(rc => legColumnKind(rc) !== null);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* The flag-less writable shape of a set-op body, or `null` when it is not one. A pure AST
|
|
1305
|
+
* peek (no plan built), the write-side shadow of {@link isSetOpBranchWritable} that ADMITS
|
|
1306
|
+
* literal discriminators (which `tryBranchColumnNames` rejects). Returns `null` for any
|
|
1307
|
+
* existence flag anywhere (mutual exclusion with {@link isSetOpMembershipBody}), a `diff`
|
|
1308
|
+
* body, a non-SELECT operand, a `select *` / computed leg, or a shape v1 does not flatten:
|
|
1309
|
+
* - a **union-like** (`union` / `unionAll`) chain of any depth → N flat legs;
|
|
1310
|
+
* - a **binary** `intersect` / `except` (a single depth-1 compound) → 2 legs;
|
|
1311
|
+
* - anything else (a deep / mixed intersect/except chain) → `null` (kept on the existing reject).
|
|
1312
|
+
*/
|
|
1313
|
+
function flaglessShape(sel) {
|
|
1314
|
+
if (!sel.compound || sel.compound.op === 'diff')
|
|
1315
|
+
return null;
|
|
1316
|
+
if (sel.compound.existence && sel.compound.existence.length > 0)
|
|
1317
|
+
return null;
|
|
1318
|
+
const topOp = sel.compound.op;
|
|
1319
|
+
const legs = [];
|
|
1320
|
+
let cur = sel;
|
|
1321
|
+
for (;;) {
|
|
1322
|
+
const leftLeg = unwrapBranchSelect(leftBranchSelect(cur));
|
|
1323
|
+
if (!isWritableLeafLeg(leftLeg))
|
|
1324
|
+
return null;
|
|
1325
|
+
legs.push(leftLeg);
|
|
1326
|
+
const right = cur.compound.select;
|
|
1327
|
+
if (right.type !== 'select')
|
|
1328
|
+
return null;
|
|
1329
|
+
const rightEff = unwrapBranchSelect(stripLegModifiers(right));
|
|
1330
|
+
if (!rightEff.compound) {
|
|
1331
|
+
if (!isWritableLeafLeg(rightEff))
|
|
1332
|
+
return null;
|
|
1333
|
+
legs.push(rightEff);
|
|
1334
|
+
// A union / union all needs ≥1 literal discriminator to route an insert honestly —
|
|
1335
|
+
// without one every leg is consistent with every row, so the routing is ambiguous
|
|
1336
|
+
// (the flag-less analog of the membership path's "a flag-less multi-branch insert is
|
|
1337
|
+
// ambiguous"). Such a body stays on the existing phase-1 reject. `intersect` / `except`
|
|
1338
|
+
// route by the OPERATOR (every leg / the left operand), so they need no discriminator.
|
|
1339
|
+
if (isUnionLikeSubtree(topOp) && !legs.some(hasLiteralDiscriminator))
|
|
1340
|
+
return null;
|
|
1341
|
+
return { op: topOp, legSelects: legs };
|
|
1342
|
+
}
|
|
1343
|
+
// The chain continues: only a uniform union-like chain may descend past depth 1; an
|
|
1344
|
+
// intersect / except is supported only as a single binary compound (its associativity
|
|
1345
|
+
// matters and is deferred for chains).
|
|
1346
|
+
if (rightEff.compound.op !== topOp || !isUnionLikeSubtree(topOp))
|
|
1347
|
+
return null;
|
|
1348
|
+
if (rightEff.compound.existence && rightEff.compound.existence.length > 0)
|
|
1349
|
+
return null;
|
|
1350
|
+
cur = rightEff;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* True iff `selectAst` is a flag-less set-op body writable through predicate-honest branch
|
|
1355
|
+
* dispatch (`set-op-flagless-predicate-honest-writes`). Mutually exclusive with
|
|
1356
|
+
* {@link isSetOpMembershipBody} (any `exists … as <flag>` takes the membership path); a
|
|
1357
|
+
* non-decomposable shape (outer LIMIT/OFFSET, `select *` / computed leg, deep intersect/except)
|
|
1358
|
+
* returns `false` and keeps rejecting `unsupported-set-op` through the single-source spine.
|
|
1359
|
+
*/
|
|
1360
|
+
export function isSetOpFlaglessWritableBody(selectAst) {
|
|
1361
|
+
if (selectAst.type !== 'select' || !selectAst.compound)
|
|
1362
|
+
return false;
|
|
1363
|
+
if (selectAst.limit || selectAst.offset)
|
|
1364
|
+
return false;
|
|
1365
|
+
return flaglessShape(selectAst) !== null;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* The flag-less view's **discriminator** column names — every data column projected as a
|
|
1369
|
+
* literal in ANY leg (read-only, `no-inverse` on UPDATE). The view's column names come from
|
|
1370
|
+
* the left-most leg (a set op takes its left child's names); a position is a discriminator
|
|
1371
|
+
* iff some leg pins it with a literal projection. Used by the `column_info` static surface
|
|
1372
|
+
* to report the discriminator `is_updatable = NO` (data / plain columns report YES).
|
|
1373
|
+
*/
|
|
1374
|
+
export function flaglessDiscriminatorColumnNames(selectAst) {
|
|
1375
|
+
if (selectAst.type !== 'select')
|
|
1376
|
+
return [];
|
|
1377
|
+
const shape = flaglessShape(selectAst);
|
|
1378
|
+
if (!shape)
|
|
1379
|
+
return [];
|
|
1380
|
+
const first = shape.legSelects[0];
|
|
1381
|
+
const out = [];
|
|
1382
|
+
for (let i = 0; i < first.columns.length; i++) {
|
|
1383
|
+
const anyLiteral = shape.legSelects.some(leg => legColumnKind(leg.columns[i])?.kind === 'literal');
|
|
1384
|
+
if (!anyLiteral)
|
|
1385
|
+
continue;
|
|
1386
|
+
const rc = first.columns[i];
|
|
1387
|
+
const name = rc.type === 'column' ? (rc.alias ?? (rc.expr.type === 'column' ? rc.expr.name : undefined)) : undefined;
|
|
1388
|
+
if (name)
|
|
1389
|
+
out.push(name);
|
|
1390
|
+
}
|
|
1391
|
+
return out;
|
|
1392
|
+
}
|
|
1393
|
+
/** Decompose a flag-less set-op view mutation. Throws a structured diagnostic for unsupported shapes. */
|
|
1394
|
+
export function buildFlaglessSetOpWrite(ctx, view, req) {
|
|
1395
|
+
const { analysis, legs } = analyzeFlaglessSetOpView(ctx, view);
|
|
1396
|
+
switch (req.op) {
|
|
1397
|
+
case 'insert': return buildFlaglessInsert(ctx, view, analysis, legs, req.stmt);
|
|
1398
|
+
case 'update': return buildFlaglessUpdate(ctx, view, analysis, legs, req.stmt);
|
|
1399
|
+
case 'delete': return buildFlaglessDelete(ctx, view, analysis, legs, req.stmt);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function analyzeFlaglessSetOpView(ctx, view) {
|
|
1403
|
+
if (view.selectAst.type !== 'select' || !view.selectAst.compound) {
|
|
1404
|
+
raiseMutationDiagnostic({ reason: 'unsupported-set-op', table: view.name, message: `cannot write through view '${view.name}': not a set-operation body` });
|
|
1405
|
+
}
|
|
1406
|
+
const sel = view.selectAst;
|
|
1407
|
+
const shape = flaglessShape(sel);
|
|
1408
|
+
if (!shape) {
|
|
1409
|
+
raiseMutationDiagnostic({ reason: 'unsupported-set-op', table: view.name, message: `cannot write through view '${view.name}': not a flag-less predicate-honest writable set-operation body` });
|
|
1410
|
+
}
|
|
1411
|
+
// Plan the body ONCE: a flag-less body has no flag columns, so the root attributes ARE
|
|
1412
|
+
// the view's data columns (positionally aligned to every leg's projection).
|
|
1413
|
+
const root = buildSelectStmt(ctx, sel);
|
|
1414
|
+
if (!isRelationalNode(root)) {
|
|
1415
|
+
raiseMutationDiagnostic({ reason: 'no-base-lineage', table: view.name, message: `cannot write through view '${view.name}': the set-operation body did not produce a relation` });
|
|
1416
|
+
}
|
|
1417
|
+
const relRoot = root;
|
|
1418
|
+
const setOpNode = findSetOpNode(relRoot);
|
|
1419
|
+
if (!setOpNode) {
|
|
1420
|
+
raiseMutationDiagnostic({ reason: 'no-base-lineage', table: view.name, message: `cannot write through view '${view.name}': the set-operation body produced no SetOperationNode` });
|
|
1421
|
+
}
|
|
1422
|
+
const dataColCount = setOpNode.dataColumnCount();
|
|
1423
|
+
const attrs = relRoot.getAttributes();
|
|
1424
|
+
const dataColNames = attrs.map(a => a.name);
|
|
1425
|
+
const dataColTypes = attrs.map(a => a.type);
|
|
1426
|
+
// A scope resolving each view data-column name to its producing attribute over the planned
|
|
1427
|
+
// root — reused by `buildSetOpCapture` for the user-predicate / capture projections.
|
|
1428
|
+
const viewColScope = new RegisteredScope(ctx.scope);
|
|
1429
|
+
attrs.forEach((attr, i) => {
|
|
1430
|
+
viewColScope.registerSymbol(attr.name.toLowerCase(), (exp, s) => new ColumnReferenceNode(s, exp, attr.type, attr.id, i));
|
|
1431
|
+
});
|
|
1432
|
+
const legs = shape.legSelects.map((legSel, i) => buildFlaglessLeg(ctx, view, legSel, i, dataColNames));
|
|
1433
|
+
// A `SetOpAnalysis`-shaped carrier so the membership fan helpers / capture builder are
|
|
1434
|
+
// reused verbatim. `branches` is a 2-tuple by type; the flag-less fan iterates `legs`
|
|
1435
|
+
// (which may be > 2) and never reads `branches`, so the first two legs satisfy the type.
|
|
1436
|
+
const branches = [legs[0].branch, legs[1].branch];
|
|
1437
|
+
const analysis = {
|
|
1438
|
+
op: shape.op, root: relRoot, viewColScope,
|
|
1439
|
+
viewColNames: dataColNames, viewColTypes: dataColTypes,
|
|
1440
|
+
dataColCount, dataColNames, surfacedInnerFlagNames: [], flags: [], branches,
|
|
1441
|
+
};
|
|
1442
|
+
return { analysis, legs };
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Build one flag-less leg: a synthetic branch view-like (its columns re-aliased to the view
|
|
1446
|
+
* data-column names so the member-exists correlation and the base ops align positionally),
|
|
1447
|
+
* plus the plan-time routing oracle inputs (the leg's planned σ-forwarded bindings + the
|
|
1448
|
+
* synthesized literal-discriminator bindings).
|
|
1449
|
+
*/
|
|
1450
|
+
function buildFlaglessLeg(ctx, view, legSel, index, dataColNames) {
|
|
1451
|
+
const dataColCount = dataColNames.length;
|
|
1452
|
+
const kinds = legSel.columns.map(legColumnKind);
|
|
1453
|
+
if (kinds.length !== dataColCount || kinds.some(k => k === null)) {
|
|
1454
|
+
raiseMutationDiagnostic({
|
|
1455
|
+
reason: 'unsupported-set-op', table: view.name,
|
|
1456
|
+
message: `cannot write through view '${view.name}': a leg projects ${kinds.length} columns (expected ${dataColCount}) or a non-writable (\`select *\` / computed) column`,
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
// Re-alias every leg column to the view data-column name at its position, so the synthetic
|
|
1460
|
+
// branch view exposes exactly the view's data columns (a plain base column round-trips
|
|
1461
|
+
// through `propagate`; a literal discriminator resolves to its constant). This makes the
|
|
1462
|
+
// member-exists `b.<col>` references and the base ops align regardless of the leg's own
|
|
1463
|
+
// aliases / base-column names.
|
|
1464
|
+
const aliasedColumns = legSel.columns.map((rc, i) => ({
|
|
1465
|
+
type: 'column', expr: rc.expr, alias: dataColNames[i],
|
|
1466
|
+
}));
|
|
1467
|
+
const effectiveSelect = { ...legSel, columns: aliasedColumns };
|
|
1468
|
+
const branchView = { name: `__setop_leg${index}`, schemaName: view.schemaName, selectAst: effectiveSelect };
|
|
1469
|
+
// A flag-less join leg is always an INNER join here: `isWritableLeafLeg` (the recognizer
|
|
1470
|
+
// `flaglessShape` gated on) admits only single-source or INNER-join legs, so an outer / cross
|
|
1471
|
+
// leg never reaches this builder (the body falls out of the flag-less route).
|
|
1472
|
+
const branch = { side: index === 0 ? 'left' : 'right', view: branchView, dataColNames: [...dataColNames], isNested: false, isMultiSource: isJoinBody(legSel) };
|
|
1473
|
+
const plainPositions = kinds.flatMap((k, i) => k.kind === 'column' ? [i] : []);
|
|
1474
|
+
const discriminatorPositions = kinds.flatMap((k, i) => k.kind === 'literal' ? [i] : []);
|
|
1475
|
+
// Plan the leg body for the oracle: its output attributes carry the σ-forwarded constant
|
|
1476
|
+
// bindings / domains (the pre-existing half — a `where color='red'` over a `color`-projecting
|
|
1477
|
+
// leg forwards `∅ → color='red'` to the output column). The synthesized discriminator
|
|
1478
|
+
// bindings (Option B) close the projected-literal gap.
|
|
1479
|
+
const planned = buildSelectStmt(ctx, effectiveSelect);
|
|
1480
|
+
if (!isRelationalNode(planned)) {
|
|
1481
|
+
raiseMutationDiagnostic({ reason: 'no-base-lineage', table: view.name, message: `cannot write through view '${view.name}': a leg did not produce a relation` });
|
|
1482
|
+
}
|
|
1483
|
+
const legRoot = planned;
|
|
1484
|
+
const legAttrs = legRoot.getAttributes();
|
|
1485
|
+
const attrIdToIndex = new Map();
|
|
1486
|
+
legAttrs.forEach((a, i) => attrIdToIndex.set(a.id, i));
|
|
1487
|
+
const scope = new RegisteredScope(ctx.scope);
|
|
1488
|
+
legAttrs.forEach((attr, i) => {
|
|
1489
|
+
scope.registerSymbol(dataColNames[i].toLowerCase(), (exp, s) => new ColumnReferenceNode(s, exp, attr.type, attr.id, i));
|
|
1490
|
+
});
|
|
1491
|
+
const discriminatorBindings = [];
|
|
1492
|
+
kinds.forEach((k, i) => {
|
|
1493
|
+
if (k.kind === 'literal' && k.value !== null) {
|
|
1494
|
+
discriminatorBindings.push({ attrs: [i], value: { kind: 'literal', value: k.value } });
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
const physical = legRoot.physical;
|
|
1498
|
+
const bindings = [...(physical.constantBindings ?? []), ...discriminatorBindings];
|
|
1499
|
+
const domains = physical.domainConstraints ?? [];
|
|
1500
|
+
const sigmaConjuncts = collectFilterConjuncts(legRoot);
|
|
1501
|
+
return {
|
|
1502
|
+
branch, plainPositions, discriminatorPositions, scope, bindings, domains, sigmaConjuncts,
|
|
1503
|
+
attrIndex: (attrId) => attrIdToIndex.get(attrId),
|
|
1504
|
+
getCollation: (col) => legAttrs[col]?.type.collationName,
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Collect every leg-body σ predicate as a flat conjunct list: each `FilterNode.predicate` in
|
|
1509
|
+
* the leg's relational subtree (walked via `getRelations()`) split into its AND-conjuncts. Every
|
|
1510
|
+
* collected conjunct is a fact that must hold for a row resident in this leg, so feeding them all
|
|
1511
|
+
* to the oracle can only ever push the verdict toward `unsat` on a real contradiction (the checker
|
|
1512
|
+
* never emits a false `unsat`). A conjunct referencing a column this leg does not project resolves
|
|
1513
|
+
* to `attrIndex → undefined` and contributes nothing.
|
|
1514
|
+
*/
|
|
1515
|
+
function collectFilterConjuncts(root) {
|
|
1516
|
+
const out = [];
|
|
1517
|
+
const visit = (node) => {
|
|
1518
|
+
if (node instanceof FilterNode)
|
|
1519
|
+
out.push(...splitConjuncts(node.predicate));
|
|
1520
|
+
for (const child of node.getRelations())
|
|
1521
|
+
visit(child);
|
|
1522
|
+
};
|
|
1523
|
+
visit(root);
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* The per-leg branch oracle (`set-op-flagless-predicate-honest-writes`): is a row satisfying
|
|
1528
|
+
* `predicate` possible in this leg? Feeds the mutation predicate (in view data-column terms,
|
|
1529
|
+
* resolved against the leg's planned attributes) as conjuncts, alongside the leg's σ-forwarded
|
|
1530
|
+
* + literal-discriminator bindings, into {@link checkSatisfiability}. `unsat` ⇒ skip the leg;
|
|
1531
|
+
* `sat` / `unknown` ⇒ include it (the checker never emits a false `unsat`). A `predicate` of
|
|
1532
|
+
* `undefined` (no WHERE / no supplied values) is unconstrained ⇒ `sat`.
|
|
1533
|
+
*/
|
|
1534
|
+
function legConsistency(ctx, leg, predicate) {
|
|
1535
|
+
let conjuncts = [];
|
|
1536
|
+
if (predicate) {
|
|
1537
|
+
const node = buildExpression({ ...ctx, scope: leg.scope }, cloneExpr(predicate));
|
|
1538
|
+
conjuncts = splitConjuncts(node);
|
|
1539
|
+
}
|
|
1540
|
+
// Fold the leg's own σ conjuncts in alongside the mutation predicate: a range σ on a
|
|
1541
|
+
// projected column (`where x < 5`) is invisible to the bindings/domains the leg forwards,
|
|
1542
|
+
// so a supplied value that provably violates it (`x = 7`) only contradicts here ⇒ `unsat`
|
|
1543
|
+
// ⇒ skip the leg (no phantom base row). Non-projected-column conjuncts are inert.
|
|
1544
|
+
return checkSatisfiability([...conjuncts, ...leg.sigmaConjuncts], leg.domains, leg.bindings, leg.attrIndex, leg.getCollation);
|
|
1545
|
+
}
|
|
1546
|
+
function resolveFlaglessInsertLayout(view, analysis, stmt) {
|
|
1547
|
+
const cols = stmt.columns && stmt.columns.length > 0 ? stmt.columns : analysis.dataColNames;
|
|
1548
|
+
const map = new Map();
|
|
1549
|
+
cols.forEach((rawName, vi) => {
|
|
1550
|
+
const pos = analysis.dataColNames.findIndex(n => n.toLowerCase() === rawName.toLowerCase());
|
|
1551
|
+
if (pos < 0) {
|
|
1552
|
+
raiseMutationDiagnostic({
|
|
1553
|
+
reason: 'unknown-view-column', column: rawName, table: view.name,
|
|
1554
|
+
message: `cannot insert through view '${view.name}': '${rawName}' is not a column of the set operation`,
|
|
1555
|
+
suggestion: `view '${view.name}' exposes: ${analysis.dataColNames.join(', ')}.`,
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
map.set(pos, vi);
|
|
1559
|
+
});
|
|
1560
|
+
return { valueIndexByDataPos: map };
|
|
1561
|
+
}
|
|
1562
|
+
/** The existence predicate of one VALUES row: `∧ <dataCol> = <suppliedValue>` over the supplied columns. */
|
|
1563
|
+
function rowExistencePredicate(analysis, layout, row) {
|
|
1564
|
+
let pred;
|
|
1565
|
+
for (const [pos, vi] of layout.valueIndexByDataPos) {
|
|
1566
|
+
const eq = {
|
|
1567
|
+
type: 'binary', operator: '=',
|
|
1568
|
+
left: { type: 'column', name: analysis.dataColNames[pos] },
|
|
1569
|
+
right: row[vi],
|
|
1570
|
+
};
|
|
1571
|
+
pred = pred ? { type: 'binary', operator: 'AND', left: pred, right: eq } : eq;
|
|
1572
|
+
}
|
|
1573
|
+
return pred;
|
|
1574
|
+
}
|
|
1575
|
+
/** The legs a flag-less INSERT routes into by operator: `except` inserts the left operand only. */
|
|
1576
|
+
function fanLegsForInsert(op, legs) {
|
|
1577
|
+
return op === 'except' ? [legs[0]] : legs;
|
|
1578
|
+
}
|
|
1579
|
+
function buildFlaglessInsert(ctx, view, analysis, legs, stmt) {
|
|
1580
|
+
rejectReturning(view, stmt.returning);
|
|
1581
|
+
if (stmt.source.type !== 'values') {
|
|
1582
|
+
raiseMutationDiagnostic({
|
|
1583
|
+
reason: 'unsupported-source', table: view.name,
|
|
1584
|
+
message: `cannot insert through view '${view.name}': a flag-less set-operation insert routes by the supplied discriminator values, so it requires a VALUES source (a SELECT/DML source's per-row routing is deferred)`,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
const values = stmt.source.values;
|
|
1588
|
+
const layout = resolveFlaglessInsertLayout(view, analysis, stmt);
|
|
1589
|
+
const baseOps = [];
|
|
1590
|
+
// A multi-source (INNER join) leg's INSERT is built via the plan-level shared-surrogate
|
|
1591
|
+
// envelope (`buildMultiSourceInsert`) and spliced as a nested `ViewMutationNode` child rather
|
|
1592
|
+
// than routed through `propagate` (which would raise the internal `unsupported-multisource-insert`)
|
|
1593
|
+
// — `set-op-write-multisource-leg-insert`. The VALUES source is self-contained.
|
|
1594
|
+
const joinLegInserts = [];
|
|
1595
|
+
for (const leg of fanLegsForInsert(analysis.op, legs)) {
|
|
1596
|
+
// Per-row routing: a row inserts into this leg unless provably inconsistent with it.
|
|
1597
|
+
const rows = values.filter(row => legConsistency(ctx, leg, rowExistencePredicate(analysis, layout, row)) !== 'unsat');
|
|
1598
|
+
if (rows.length === 0)
|
|
1599
|
+
continue;
|
|
1600
|
+
// Only the leg's PLAIN (writable) columns that are supplied flow into the base insert;
|
|
1601
|
+
// the literal discriminators are determined by the leg projection / σ (omitted base
|
|
1602
|
+
// columns are recovered by the leg's `where`-constant FD insert-defaulting).
|
|
1603
|
+
const supplied = leg.plainPositions.filter(pos => layout.valueIndexByDataPos.has(pos));
|
|
1604
|
+
if (supplied.length === 0)
|
|
1605
|
+
continue; // an all-literal / unsupplied leg has no base row to write
|
|
1606
|
+
const colNames = supplied.map(pos => analysis.dataColNames[pos]);
|
|
1607
|
+
const valueRows = rows.map(row => supplied.map(pos => cloneExpr(row[layout.valueIndexByDataPos.get(pos)])));
|
|
1608
|
+
const source = { type: 'values', values: valueRows };
|
|
1609
|
+
const insertStmt = {
|
|
1610
|
+
type: 'insert',
|
|
1611
|
+
table: { type: 'identifier', name: leg.branch.view.name },
|
|
1612
|
+
columns: colNames,
|
|
1613
|
+
source,
|
|
1614
|
+
onConflict: stmt.onConflict,
|
|
1615
|
+
contextValues: stmt.contextValues,
|
|
1616
|
+
schemaPath: stmt.schemaPath,
|
|
1617
|
+
loc: stmt.loc,
|
|
1618
|
+
};
|
|
1619
|
+
if (leg.branch.isMultiSource) {
|
|
1620
|
+
joinLegInserts.push({ view: leg.branch.view, stmt: insertStmt });
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
baseOps.push(...propagate(ctx, leg.branch.view, { op: 'insert', stmt: insertStmt }));
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (baseOps.length === 0 && joinLegInserts.length === 0) {
|
|
1627
|
+
raiseMutationDiagnostic({
|
|
1628
|
+
reason: 'unsupported-set-op', table: view.name,
|
|
1629
|
+
message: `cannot insert through view '${view.name}': the supplied values are consistent with no writable leg (the row would belong to no branch)`,
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
// Insert-through reads no existing row, so it needs no capture (self-contained values).
|
|
1633
|
+
return { baseOps, joinLegInserts: joinLegInserts.length > 0 ? joinLegInserts : undefined };
|
|
1634
|
+
}
|
|
1635
|
+
// --- flag-less DELETE / data-UPDATE (fan to the consistent legs) ----------
|
|
1636
|
+
/** The legs a flag-less DELETE / data-UPDATE fans to: every consistent leg (`except` fans the left only). */
|
|
1637
|
+
function fanLegsForFanOut(ctx, op, legs, where) {
|
|
1638
|
+
const consistent = legs.filter(l => legConsistency(ctx, l, where) !== 'unsat');
|
|
1639
|
+
if (op === 'except')
|
|
1640
|
+
return consistent.includes(legs[0]) ? [legs[0]] : [];
|
|
1641
|
+
return consistent;
|
|
1642
|
+
}
|
|
1643
|
+
function buildFlaglessDelete(ctx, view, analysis, legs, stmt) {
|
|
1644
|
+
rejectReturning(view, stmt.returning);
|
|
1645
|
+
const capture = buildSetOpCapture(ctx, analysis, stmt.where);
|
|
1646
|
+
const fan = makeJoinLegFan(capture);
|
|
1647
|
+
const baseOps = [];
|
|
1648
|
+
for (const leg of fanLegsForFanOut(ctx, analysis.op, legs, stmt.where)) {
|
|
1649
|
+
baseOps.push(...fanBranchDelete(ctx, view, analysis, leg.branch, stmt, fan));
|
|
1650
|
+
}
|
|
1651
|
+
return { baseOps, capture, nestedCaptures: fan.captures.length > 0 ? fan.captures : undefined };
|
|
1652
|
+
}
|
|
1653
|
+
function buildFlaglessUpdate(ctx, view, analysis, legs, stmt) {
|
|
1654
|
+
rejectReturning(view, stmt.returning);
|
|
1655
|
+
// A literal discriminator is read-only (it has no base inverse). Reject a write to one
|
|
1656
|
+
// up front with `no-inverse` (Finding 5) — NOT silently routed.
|
|
1657
|
+
const discriminators = new Set();
|
|
1658
|
+
for (const leg of legs)
|
|
1659
|
+
for (const p of leg.discriminatorPositions)
|
|
1660
|
+
discriminators.add(p);
|
|
1661
|
+
const dataAssignments = [];
|
|
1662
|
+
for (const asg of stmt.assignments) {
|
|
1663
|
+
const position = analysis.dataColNames.findIndex(n => n.toLowerCase() === asg.column.toLowerCase());
|
|
1664
|
+
if (position < 0) {
|
|
1665
|
+
raiseMutationDiagnostic({
|
|
1666
|
+
reason: 'unknown-view-column', column: asg.column, table: view.name,
|
|
1667
|
+
message: `cannot write through view '${view.name}': '${asg.column}' is not a column of the set operation`,
|
|
1668
|
+
suggestion: `view '${view.name}' exposes: ${analysis.dataColNames.join(', ')}.`,
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
if (discriminators.has(position)) {
|
|
1672
|
+
raiseMutationDiagnostic({
|
|
1673
|
+
reason: 'no-inverse', column: asg.column, table: view.name,
|
|
1674
|
+
message: `cannot write through view '${view.name}': '${asg.column}' is a literal discriminator column (it routes rows to a branch and has no base-column inverse) — it is read-only`,
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
dataAssignments.push({ position, value: asg.value });
|
|
1678
|
+
}
|
|
1679
|
+
const capture = buildSetOpCapture(ctx, analysis, stmt.where);
|
|
1680
|
+
const fan = makeJoinLegFan(capture);
|
|
1681
|
+
const baseOps = [];
|
|
1682
|
+
for (const leg of fanLegsForFanOut(ctx, analysis.op, legs, stmt.where)) {
|
|
1683
|
+
baseOps.push(...fanBranchDataUpdate(ctx, view, analysis, leg.branch, dataAssignments, stmt, fan));
|
|
1684
|
+
}
|
|
1685
|
+
return { baseOps, capture, nestedCaptures: fan.captures.length > 0 ? fan.captures : undefined };
|
|
1686
|
+
}
|
|
1687
|
+
//# sourceMappingURL=set-op.js.map
|