@quereus/quereus 4.3.1 → 4.3.2
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 +25 -7
- package/dist/src/common/types.d.ts +9 -59
- 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 +2 -2
- package/dist/src/core/database-assertions.d.ts.map +1 -1
- package/dist/src/core/database-assertions.js +4 -3
- package/dist/src/core/database-assertions.js.map +1 -1
- package/dist/src/core/database-external-changes.d.ts +1 -1
- package/dist/src/core/database-external-changes.js +1 -1
- package/dist/src/core/database-internal.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-analysis.d.ts +199 -0
- package/dist/src/core/database-materialized-views-analysis.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views-analysis.js +502 -0
- package/dist/src/core/database-materialized-views-analysis.js.map +1 -0
- package/dist/src/core/database-materialized-views-apply.d.ts +280 -0
- package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views-apply.js +624 -0
- package/dist/src/core/database-materialized-views-apply.js.map +1 -0
- package/dist/src/core/database-materialized-views-plan-builders.d.ts +203 -0
- package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views-plan-builders.js +1084 -0
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -0
- package/dist/src/core/database-materialized-views-plans.d.ts +417 -0
- package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views-plans.js +13 -0
- package/dist/src/core/database-materialized-views-plans.js.map +1 -0
- package/dist/src/core/database-materialized-views.d.ts +11 -819
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +60 -2069
- package/dist/src/core/database-materialized-views.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts +10 -1
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +16 -4
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database-watchers.d.ts.map +1 -1
- package/dist/src/core/database-watchers.js +2 -1
- package/dist/src/core/database-watchers.js.map +1 -1
- package/dist/src/core/database.d.ts +99 -13
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +250 -33
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/derived-row-validator.d.ts +1 -1
- package/dist/src/core/derived-row-validator.d.ts.map +1 -1
- package/dist/src/core/derived-row-validator.js +5 -3
- package/dist/src/core/derived-row-validator.js.map +1 -1
- package/dist/src/core/statement.d.ts +8 -0
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +98 -18
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/emit/ast-stringify.d.ts.map +1 -1
- package/dist/src/emit/ast-stringify.js +24 -13
- package/dist/src/emit/ast-stringify.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +2 -1
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/json.d.ts.map +1 -1
- package/dist/src/func/builtins/json.js +2 -1
- package/dist/src/func/builtins/json.js.map +1 -1
- package/dist/src/func/builtins/mutation.d.ts.map +1 -1
- package/dist/src/func/builtins/mutation.js +3 -2
- package/dist/src/func/builtins/mutation.js.map +1 -1
- package/dist/src/index.d.ts +16 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +33 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/ast.d.ts +2 -2
- package/dist/src/parser/lexer.d.ts +11 -1
- package/dist/src/parser/lexer.d.ts.map +1 -1
- package/dist/src/parser/lexer.js +40 -34
- package/dist/src/parser/lexer.js.map +1 -1
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +16 -9
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/analysis/authored-inverse.js +1 -1
- package/dist/src/planner/analysis/comparison-collation.d.ts +52 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.js +84 -7
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +64 -17
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/coverage-prover.d.ts +1 -1
- package/dist/src/planner/analysis/coverage-prover.js +1 -1
- package/dist/src/planner/analysis/sat-checker.d.ts +8 -1
- package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
- package/dist/src/planner/analysis/sat-checker.js +117 -22
- package/dist/src/planner/analysis/sat-checker.js.map +1 -1
- package/dist/src/planner/analysis/scalar-invertibility.d.ts +13 -4
- package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -1
- package/dist/src/planner/analysis/scalar-invertibility.js +12 -3
- package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -1
- package/dist/src/planner/analysis/update-lineage.d.ts +1 -1
- package/dist/src/planner/analysis/update-lineage.js +1 -1
- package/dist/src/planner/analysis/update-lineage.js.map +1 -1
- package/dist/src/planner/analysis/view-complement.d.ts +2 -2
- package/dist/src/planner/building/delete.js +2 -2
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/dml-target.d.ts +2 -2
- package/dist/src/planner/building/dml-target.js +2 -2
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +53 -2
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/schema-resolution.d.ts +3 -0
- package/dist/src/planner/building/schema-resolution.d.ts.map +1 -1
- package/dist/src/planner/building/schema-resolution.js +4 -5
- package/dist/src/planner/building/schema-resolution.js.map +1 -1
- package/dist/src/planner/building/select.js +3 -3
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/update.js +2 -2
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/building/view-mutation-builder.js +7 -7
- package/dist/src/planner/building/view-mutation-builder.js.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.d.ts +1 -2
- package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.js +21 -14
- package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
- package/dist/src/planner/cache/reference-graph.d.ts +14 -1
- package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
- package/dist/src/planner/cache/reference-graph.js +26 -26
- package/dist/src/planner/cache/reference-graph.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +7 -3
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/debug.d.ts +3 -2
- package/dist/src/planner/debug.d.ts.map +1 -1
- package/dist/src/planner/debug.js +9 -3
- package/dist/src/planner/debug.js.map +1 -1
- package/dist/src/planner/framework/characteristics.d.ts +46 -19
- package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
- package/dist/src/planner/framework/characteristics.js +50 -134
- package/dist/src/planner/framework/characteristics.js.map +1 -1
- package/dist/src/planner/framework/context.d.ts +8 -44
- package/dist/src/planner/framework/context.d.ts.map +1 -1
- package/dist/src/planner/framework/context.js +0 -91
- package/dist/src/planner/framework/context.js.map +1 -1
- package/dist/src/planner/framework/pass.d.ts +7 -0
- package/dist/src/planner/framework/pass.d.ts.map +1 -1
- package/dist/src/planner/framework/pass.js +84 -0
- package/dist/src/planner/framework/pass.js.map +1 -1
- package/dist/src/planner/framework/registry.d.ts +13 -42
- package/dist/src/planner/framework/registry.d.ts.map +1 -1
- package/dist/src/planner/framework/registry.js +14 -182
- package/dist/src/planner/framework/registry.js.map +1 -1
- package/dist/src/planner/mutation/backward-body.d.ts +2 -2
- package/dist/src/planner/mutation/cte-flatten.js +1 -1
- package/dist/src/planner/mutation/decomposition.d.ts +2 -2
- package/dist/src/planner/mutation/decomposition.js +1 -1
- package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -1
- package/dist/src/planner/mutation/lens-enforcement.js +2 -1
- package/dist/src/planner/mutation/lens-enforcement.js.map +1 -1
- package/dist/src/planner/mutation/multi-source.d.ts +8 -8
- package/dist/src/planner/mutation/multi-source.js +11 -11
- package/dist/src/planner/mutation/multi-source.js.map +1 -1
- package/dist/src/planner/mutation/propagate.d.ts +1 -1
- package/dist/src/planner/mutation/propagate.js +1 -1
- package/dist/src/planner/mutation/scope-transform.d.ts +2 -2
- package/dist/src/planner/mutation/scope-transform.js +3 -3
- package/dist/src/planner/mutation/scope-transform.js.map +1 -1
- package/dist/src/planner/mutation/set-op.d.ts +1 -1
- package/dist/src/planner/mutation/set-op.d.ts.map +1 -1
- package/dist/src/planner/mutation/set-op.js +3 -2
- package/dist/src/planner/mutation/set-op.js.map +1 -1
- package/dist/src/planner/mutation/single-source.d.ts +3 -3
- package/dist/src/planner/mutation/single-source.js +9 -9
- package/dist/src/planner/mutation/single-source.js.map +1 -1
- package/dist/src/planner/nodes/aggregate-function.d.ts +3 -1
- package/dist/src/planner/nodes/aggregate-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-function.js +1 -0
- package/dist/src/planner/nodes/aggregate-function.js.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.d.ts +1 -0
- package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.js +8 -1
- 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 +3 -1
- package/dist/src/planner/nodes/alias-node.js.map +1 -1
- package/dist/src/planner/nodes/asof-scan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/asof-scan-node.js +3 -2
- package/dist/src/planner/nodes/asof-scan-node.js.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.d.ts +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/asserted-keys-node.js +5 -2
- package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/async-gather-node.js +3 -1
- package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
- package/dist/src/planner/nodes/block.js +3 -3
- package/dist/src/planner/nodes/block.js.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.d.ts +2 -0
- package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/bloom-join-node.js +5 -1
- package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
- package/dist/src/planner/nodes/cache-node.d.ts +1 -0
- package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cache-node.js +1 -0
- package/dist/src/planner/nodes/cache-node.js.map +1 -1
- package/dist/src/planner/nodes/cte-node.d.ts +1 -0
- package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cte-node.js +4 -1
- package/dist/src/planner/nodes/cte-node.js.map +1 -1
- package/dist/src/planner/nodes/distinct-node.js +3 -3
- package/dist/src/planner/nodes/distinct-node.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +21 -0
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/nodes/envelope-scan-node.d.ts +1 -1
- package/dist/src/planner/nodes/envelope-scan-node.js +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/fanout-lookup-join-node.js +4 -2
- package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts +10 -2
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +81 -14
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts +12 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.js +28 -1
- package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/internal-recursive-cte-ref-node.d.ts +3 -1
- package/dist/src/planner/nodes/internal-recursive-cte-ref-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/internal-recursive-cte-ref-node.js +1 -0
- package/dist/src/planner/nodes/internal-recursive-cte-ref-node.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts +3 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +5 -5
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js +4 -1
- package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -1
- package/dist/src/planner/nodes/limit-offset.d.ts +1 -0
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +4 -3
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.d.ts +2 -0
- package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.js +6 -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 +3 -1
- package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +68 -5
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js +137 -24
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/project-node.d.ts +3 -2
- package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/project-node.js +1 -0
- package/dist/src/planner/nodes/project-node.js.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.js +8 -2
- package/dist/src/planner/nodes/recursive-cte-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts +5 -1
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +3 -0
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/remote-query-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/remote-query-node.js +3 -1
- package/dist/src/planner/nodes/remote-query-node.js.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/retrieve-node.js +3 -1
- package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +3 -1
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/planner/nodes/sink-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/sink-node.js +3 -1
- package/dist/src/planner/nodes/sink-node.js.map +1 -1
- package/dist/src/planner/nodes/sort.d.ts +2 -1
- package/dist/src/planner/nodes/sort.d.ts.map +1 -1
- package/dist/src/planner/nodes/sort.js +9 -5
- package/dist/src/planner/nodes/sort.js.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts +12 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.js +32 -3
- package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts +2 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +2 -0
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/nodes/view-mutation-node.d.ts +2 -2
- package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/view-mutation-node.js +5 -1
- package/dist/src/planner/nodes/view-mutation-node.js.map +1 -1
- package/dist/src/planner/nodes/window-function.d.ts +3 -1
- package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-function.js +1 -0
- package/dist/src/planner/nodes/window-function.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts +52 -8
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +885 -894
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +235 -202
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +2 -2
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +2 -2
- package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +1 -1
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +1 -1
- package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +1 -1
- package/dist/src/planner/rules/join/rule-join-elimination.d.ts +1 -1
- package/dist/src/planner/rules/join/rule-join-elimination.js +1 -1
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +5 -6
- package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts +1 -1
- package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts +2 -2
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +4 -3
- package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts +19 -0
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -0
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +48 -0
- package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -0
- package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js +12 -1
- package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +22 -20
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.d.ts +39 -0
- package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -0
- package/dist/src/planner/rules/shared/index-style-context.js +26 -0
- package/dist/src/planner/rules/shared/index-style-context.js.map +1 -0
- 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 +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts +1 -1
- package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +1 -1
- package/dist/src/planner/stats/analyze.d.ts.map +1 -1
- package/dist/src/planner/stats/analyze.js +2 -20
- package/dist/src/planner/stats/analyze.js.map +1 -1
- package/dist/src/planner/stats/histogram.d.ts.map +1 -1
- package/dist/src/planner/stats/histogram.js +4 -0
- package/dist/src/planner/stats/histogram.js.map +1 -1
- package/dist/src/planner/util/fd-utils.d.ts +2 -3
- package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
- package/dist/src/planner/util/fd-utils.js +2 -3
- package/dist/src/planner/util/fd-utils.js.map +1 -1
- package/dist/src/planner/validation/plan-validator.d.ts +16 -0
- package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
- package/dist/src/planner/validation/plan-validator.js +36 -0
- package/dist/src/planner/validation/plan-validator.js.map +1 -1
- package/dist/src/runtime/async-util.d.ts +26 -0
- package/dist/src/runtime/async-util.d.ts.map +1 -1
- package/dist/src/runtime/async-util.js +139 -55
- package/dist/src/runtime/async-util.js.map +1 -1
- package/dist/src/runtime/context-helpers.d.ts +32 -4
- package/dist/src/runtime/context-helpers.d.ts.map +1 -1
- package/dist/src/runtime/context-helpers.js +26 -9
- package/dist/src/runtime/context-helpers.js.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.js +2 -1
- package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
- package/dist/src/runtime/emission-context.d.ts +27 -3
- package/dist/src/runtime/emission-context.d.ts.map +1 -1
- package/dist/src/runtime/emission-context.js +38 -7
- package/dist/src/runtime/emission-context.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +7 -1
- 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 +17 -13
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +98 -9
- 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 +13 -10
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/async-gather.d.ts.map +1 -1
- package/dist/src/runtime/emit/async-gather.js +4 -3
- package/dist/src/runtime/emit/async-gather.js.map +1 -1
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +2 -1
- 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 +122 -14
- 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 +2 -1
- 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 +20 -8
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/cache.d.ts.map +1 -1
- package/dist/src/runtime/emit/cache.js +2 -1
- package/dist/src/runtime/emit/cache.js.map +1 -1
- package/dist/src/runtime/emit/case.d.ts.map +1 -1
- package/dist/src/runtime/emit/case.js +66 -45
- package/dist/src/runtime/emit/case.js.map +1 -1
- package/dist/src/runtime/emit/cast.d.ts.map +1 -1
- package/dist/src/runtime/emit/cast.js +2 -1
- package/dist/src/runtime/emit/cast.js.map +1 -1
- package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +13 -7
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/create-assertion.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-assertion.js +2 -1
- package/dist/src/runtime/emit/create-assertion.js.map +1 -1
- package/dist/src/runtime/emit/create-index.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-index.js +12 -1
- package/dist/src/runtime/emit/create-index.js.map +1 -1
- package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-table.js +12 -1
- package/dist/src/runtime/emit/create-table.js.map +1 -1
- package/dist/src/runtime/emit/cte-reference.d.ts.map +1 -1
- package/dist/src/runtime/emit/cte-reference.js +2 -1
- package/dist/src/runtime/emit/cte-reference.js.map +1 -1
- package/dist/src/runtime/emit/cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/cte.js +2 -1
- package/dist/src/runtime/emit/cte.js.map +1 -1
- package/dist/src/runtime/emit/ddl-transaction-policy.d.ts +50 -0
- package/dist/src/runtime/emit/ddl-transaction-policy.d.ts.map +1 -0
- package/dist/src/runtime/emit/ddl-transaction-policy.js +69 -0
- package/dist/src/runtime/emit/ddl-transaction-policy.js.map +1 -0
- package/dist/src/runtime/emit/delete.d.ts.map +1 -1
- package/dist/src/runtime/emit/delete.js +2 -1
- package/dist/src/runtime/emit/delete.js.map +1 -1
- package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
- package/dist/src/runtime/emit/distinct.js +2 -1
- package/dist/src/runtime/emit/distinct.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +70 -31
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-assertion.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-assertion.js +2 -1
- package/dist/src/runtime/emit/drop-assertion.js.map +1 -1
- package/dist/src/runtime/emit/drop-index.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-index.js +17 -1
- package/dist/src/runtime/emit/drop-index.js.map +1 -1
- package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-table.js +11 -1
- package/dist/src/runtime/emit/drop-table.js.map +1 -1
- package/dist/src/runtime/emit/eager-prefetch.d.ts.map +1 -1
- package/dist/src/runtime/emit/eager-prefetch.js +2 -1
- package/dist/src/runtime/emit/eager-prefetch.js.map +1 -1
- package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/envelope-scan.js +2 -1
- package/dist/src/runtime/emit/envelope-scan.js.map +1 -1
- package/dist/src/runtime/emit/fanout-lookup-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/fanout-lookup-join.js +3 -2
- package/dist/src/runtime/emit/fanout-lookup-join.js.map +1 -1
- package/dist/src/runtime/emit/filter.d.ts.map +1 -1
- package/dist/src/runtime/emit/filter.js +8 -3
- package/dist/src/runtime/emit/filter.js.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.js +16 -11
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/insert.d.ts.map +1 -1
- package/dist/src/runtime/emit/insert.js +2 -1
- package/dist/src/runtime/emit/insert.js.map +1 -1
- package/dist/src/runtime/emit/join.d.ts.map +1 -1
- package/dist/src/runtime/emit/join.js +20 -6
- package/dist/src/runtime/emit/join.js.map +1 -1
- package/dist/src/runtime/emit/limit-offset.d.ts.map +1 -1
- package/dist/src/runtime/emit/limit-offset.js +2 -1
- package/dist/src/runtime/emit/limit-offset.js.map +1 -1
- package/dist/src/runtime/emit/literal.d.ts.map +1 -1
- package/dist/src/runtime/emit/literal.js +2 -1
- package/dist/src/runtime/emit/literal.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +22 -0
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +123 -12
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +6 -0
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +14 -4
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/ordinal-slice.d.ts.map +1 -1
- package/dist/src/runtime/emit/ordinal-slice.js +2 -1
- package/dist/src/runtime/emit/ordinal-slice.js.map +1 -1
- package/dist/src/runtime/emit/pragma.d.ts.map +1 -1
- package/dist/src/runtime/emit/pragma.js +2 -1
- package/dist/src/runtime/emit/pragma.js.map +1 -1
- package/dist/src/runtime/emit/project.d.ts.map +1 -1
- package/dist/src/runtime/emit/project.js +7 -2
- package/dist/src/runtime/emit/project.js.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
- package/dist/src/runtime/emit/recursive-cte.js +2 -1
- package/dist/src/runtime/emit/recursive-cte.js.map +1 -1
- package/dist/src/runtime/emit/returning.d.ts.map +1 -1
- package/dist/src/runtime/emit/returning.js +7 -2
- package/dist/src/runtime/emit/returning.js.map +1 -1
- package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
- package/dist/src/runtime/emit/scalar-function.js +2 -1
- package/dist/src/runtime/emit/scalar-function.js.map +1 -1
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +55 -17
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +6 -5
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/runtime/emit/sequencing.d.ts.map +1 -1
- package/dist/src/runtime/emit/sequencing.js +2 -1
- package/dist/src/runtime/emit/sequencing.js.map +1 -1
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +6 -5
- package/dist/src/runtime/emit/set-operation.js.map +1 -1
- package/dist/src/runtime/emit/sink.d.ts.map +1 -1
- package/dist/src/runtime/emit/sink.js +2 -1
- package/dist/src/runtime/emit/sink.js.map +1 -1
- package/dist/src/runtime/emit/sort.d.ts.map +1 -1
- package/dist/src/runtime/emit/sort.js +2 -1
- package/dist/src/runtime/emit/sort.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +42 -23
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/table-valued-function.d.ts.map +1 -1
- package/dist/src/runtime/emit/table-valued-function.js +2 -1
- package/dist/src/runtime/emit/table-valued-function.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js +2 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
- package/dist/src/runtime/emit/transaction.d.ts.map +1 -1
- package/dist/src/runtime/emit/transaction.js +2 -1
- package/dist/src/runtime/emit/transaction.js.map +1 -1
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +2 -1
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/runtime/emit/update.d.ts.map +1 -1
- package/dist/src/runtime/emit/update.js +2 -1
- package/dist/src/runtime/emit/update.js.map +1 -1
- package/dist/src/runtime/emit/values.d.ts.map +1 -1
- package/dist/src/runtime/emit/values.js +2 -1
- package/dist/src/runtime/emit/values.js.map +1 -1
- package/dist/src/runtime/emit/view-mutation.d.ts +1 -1
- package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -1
- package/dist/src/runtime/emit/view-mutation.js +6 -3
- package/dist/src/runtime/emit/view-mutation.js.map +1 -1
- package/dist/src/runtime/emit/window.d.ts.map +1 -1
- package/dist/src/runtime/emit/window.js +26 -9
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/emitters.d.ts +11 -3
- package/dist/src/runtime/emitters.d.ts.map +1 -1
- package/dist/src/runtime/emitters.js +13 -22
- package/dist/src/runtime/emitters.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +39 -13
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +18 -4
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +72 -18
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/scheduler.d.ts +56 -7
- package/dist/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/src/runtime/scheduler.js +223 -200
- package/dist/src/runtime/scheduler.js.map +1 -1
- package/dist/src/runtime/strict-flags.d.ts +22 -0
- package/dist/src/runtime/strict-flags.d.ts.map +1 -0
- package/dist/src/runtime/strict-flags.js +26 -0
- package/dist/src/runtime/strict-flags.js.map +1 -0
- package/dist/src/runtime/strict-fork.d.ts +4 -4
- package/dist/src/runtime/strict-fork.d.ts.map +1 -1
- package/dist/src/runtime/strict-fork.js +175 -31
- package/dist/src/runtime/strict-fork.js.map +1 -1
- package/dist/src/runtime/types.d.ts +66 -4
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js +37 -0
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/runtime/utils.d.ts.map +1 -1
- package/dist/src/runtime/utils.js +8 -17
- package/dist/src/runtime/utils.js.map +1 -1
- package/dist/src/schema/column.d.ts +8 -0
- package/dist/src/schema/column.d.ts.map +1 -1
- package/dist/src/schema/column.js.map +1 -1
- package/dist/src/schema/ddl-generator.js +5 -0
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/lens-compiler.js +2 -2
- package/dist/src/schema/lens-compiler.js.map +1 -1
- package/dist/src/schema/lens-prover.d.ts.map +1 -1
- package/dist/src/schema/lens-prover.js +2 -3
- package/dist/src/schema/lens-prover.js.map +1 -1
- package/dist/src/schema/lens.d.ts +1 -1
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +7 -2
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/rename-rewriter.d.ts +61 -0
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +77 -0
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/table.d.ts +36 -8
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +61 -11
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +28 -5
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +37 -5
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/schema/view.d.ts +1 -1
- package/dist/src/schema/view.js +1 -1
- package/dist/src/types/json-type.d.ts.map +1 -1
- package/dist/src/types/json-type.js +18 -8
- package/dist/src/types/json-type.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +26 -0
- package/dist/src/types/logical-type.d.ts.map +1 -1
- package/dist/src/types/logical-type.js.map +1 -1
- package/dist/src/util/comparison.d.ts +83 -59
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +191 -131
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/json-canonical.d.ts +34 -0
- package/dist/src/util/json-canonical.d.ts.map +1 -0
- package/dist/src/util/json-canonical.js +32 -0
- package/dist/src/util/json-canonical.js.map +1 -0
- package/dist/src/util/key-serializer.d.ts +10 -3
- package/dist/src/util/key-serializer.d.ts.map +1 -1
- package/dist/src/util/key-serializer.js +43 -17
- package/dist/src/util/key-serializer.js.map +1 -1
- package/dist/src/util/key-tuple-codec.d.ts +37 -0
- package/dist/src/util/key-tuple-codec.d.ts.map +1 -0
- package/dist/src/util/key-tuple-codec.js +65 -0
- package/dist/src/util/key-tuple-codec.js.map +1 -0
- package/dist/src/util/latches.d.ts +29 -3
- package/dist/src/util/latches.d.ts.map +1 -1
- package/dist/src/util/latches.js +60 -6
- package/dist/src/util/latches.js.map +1 -1
- package/dist/src/util/patterns.d.ts +22 -8
- package/dist/src/util/patterns.d.ts.map +1 -1
- package/dist/src/util/patterns.js +87 -16
- package/dist/src/util/patterns.js.map +1 -1
- package/dist/src/util/qualified-name.d.ts +10 -0
- package/dist/src/util/qualified-name.d.ts.map +1 -0
- package/dist/src/util/qualified-name.js +15 -0
- package/dist/src/util/qualified-name.js.map +1 -0
- package/dist/src/vtab/backing-host.d.ts +8 -1
- package/dist/src/vtab/backing-host.d.ts.map +1 -1
- package/dist/src/vtab/backing-host.js +7 -0
- package/dist/src/vtab/backing-host.js.map +1 -1
- package/dist/src/vtab/best-access-plan.d.ts +18 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js +15 -0
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/capabilities.d.ts +44 -0
- package/dist/src/vtab/capabilities.d.ts.map +1 -1
- package/dist/src/vtab/filter-info.d.ts +42 -0
- package/dist/src/vtab/filter-info.d.ts.map +1 -1
- package/dist/src/vtab/filter-info.js +137 -1
- package/dist/src/vtab/filter-info.js.map +1 -1
- package/dist/src/vtab/idx-str.d.ts +62 -0
- package/dist/src/vtab/idx-str.d.ts.map +1 -0
- package/dist/src/vtab/idx-str.js +130 -0
- package/dist/src/vtab/idx-str.js.map +1 -0
- package/dist/src/vtab/index-descriptor.d.ts +122 -0
- package/dist/src/vtab/index-descriptor.d.ts.map +1 -0
- package/dist/src/vtab/index-descriptor.js +104 -0
- package/dist/src/vtab/index-descriptor.js.map +1 -0
- package/dist/src/vtab/mapping-advertisement.d.ts +1 -1
- package/dist/src/vtab/memory/index.d.ts +12 -4
- package/dist/src/vtab/memory/index.d.ts.map +1 -1
- package/dist/src/vtab/memory/index.js +17 -9
- package/dist/src/vtab/memory/index.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +85 -31
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +127 -86
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/connection.d.ts +27 -0
- package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/connection.js +36 -1
- package/dist/src/vtab/memory/layer/connection.js.map +1 -1
- package/dist/src/vtab/memory/layer/interface.d.ts +24 -4
- package/dist/src/vtab/memory/layer/interface.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +212 -16
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +754 -170
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts +26 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.js +29 -11
- package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
- package/dist/src/vtab/memory/layer/safe-iterate.d.ts +2 -2
- package/dist/src/vtab/memory/layer/safe-iterate.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/safe-iterate.js +2 -2
- package/dist/src/vtab/memory/layer/safe-iterate.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.d.ts +5 -1
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +38 -22
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.js +13 -21
- package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +169 -7
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +334 -9
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +11 -4
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +35 -16
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts +30 -4
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +63 -15
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.d.ts +9 -1
- package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/predicate.js +35 -14
- package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key-encode.js +12 -6
- package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.d.ts +6 -1
- package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.js +12 -8
- package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
- package/dist/src/vtab/module.d.ts +38 -14
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +41 -2
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js +19 -0
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +5 -4
- package/dist/src/planner/rules/cache/rule-materialization-advisory.d.ts +0 -18
- package/dist/src/planner/rules/cache/rule-materialization-advisory.d.ts.map +0 -1
- package/dist/src/planner/rules/cache/rule-materialization-advisory.js +0 -65
- package/dist/src/planner/rules/cache/rule-materialization-advisory.js.map +0 -1
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialized-view maintenance — **plan builders**. The cost-gated builder that compiles
|
|
3
|
+
* an MV body into a {@link MaintenancePlan} (the bounded-delta arms plus the full-rebuild
|
|
4
|
+
* floor) and the residual-compile / source-stats helpers they share. Extracted from
|
|
5
|
+
* database-materialized-views.ts as free functions over {@link MaterializedViewManagerContext};
|
|
6
|
+
* the sole caller in the manager is `registerMaterializedView` → {@link buildMaintenancePlan}.
|
|
7
|
+
*/
|
|
8
|
+
import { QuereusError } from '../common/errors.js';
|
|
9
|
+
import { StatusCode } from '../common/types.js';
|
|
10
|
+
import { ColumnReferenceNode } from '../planner/nodes/reference.js';
|
|
11
|
+
import { checkDeterministic } from '../planner/validation/determinism-validator.js';
|
|
12
|
+
import { emitPlanNode } from '../runtime/emitters.js';
|
|
13
|
+
import { EmissionContext } from '../runtime/emission-context.js';
|
|
14
|
+
import { Scheduler } from '../runtime/scheduler.js';
|
|
15
|
+
import { PlanNodeType } from '../planner/nodes/plan-node-type.js';
|
|
16
|
+
import { injectKeyFilter } from '../planner/analysis/key-filter.js';
|
|
17
|
+
import { keysOf } from '../planner/util/fd-utils.js';
|
|
18
|
+
import { deriveCoarsenedBackingKey, resolveValuePreservingSourceCol } from '../planner/analysis/coarsened-key.js';
|
|
19
|
+
import { proveOneToOneJoin } from '../planner/analysis/coverage-prover.js';
|
|
20
|
+
import { CapabilityDetectors } from '../planner/framework/characteristics.js';
|
|
21
|
+
import { selectMaintenanceStrategy, isFullRebuildPathological, seqScanCost, filterCost, projectCost, } from '../planner/cost/index.js';
|
|
22
|
+
import { tryResolveBackingHost } from '../runtime/emit/materialized-view-helpers.js';
|
|
23
|
+
import { compilePredicate } from '../vtab/memory/utils/predicate.js';
|
|
24
|
+
import { findNonReplicableFunction, findNonReplicableCollation, findNonDeterministic, normalizeCollation, findAggregate, containsNodeType, containsAnyJoin, countNodeType, countJoins, findTableFunctionCall, collectTableRefs, collectProducingExprs, resolveTransitiveSourceCol, bodyWhereReferencesLookup, bodyWhereIsNonDeterministic, relationalAttributes, rootRelationalNode, cannotMaterialize, nonReplicableDerivationError, nonReplicableCollationDerivationError, isSingleRowEvaluable, compileSourceRowEvaluator, } from './database-materialized-views-analysis.js';
|
|
25
|
+
import { BINARY_COLLATION } from '../util/comparison.js';
|
|
26
|
+
/** Fallback source row estimate when the StatsProvider has no count (mirrors the
|
|
27
|
+
* optimizer's naive default). Only feeds the create-time maintenance cost gate. */
|
|
28
|
+
const DEFAULT_SOURCE_ROWS = 1000;
|
|
29
|
+
/**
|
|
30
|
+
* Snapshot a backing table's physical primary key as the maintenance plans consume it: the
|
|
31
|
+
* declared `(index, desc, collation)` triple plus the collation comparator that name resolves
|
|
32
|
+
* to on this database. Every arm builds its `backingPkDefinition` through here, so the per-row
|
|
33
|
+
* key comparisons in `database-materialized-views-apply.ts` never do a registry lookup — and
|
|
34
|
+
* never silently degrade a custom collation to byte order.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: this freezes the comparator into the plan. Re-registering a collation name with
|
|
37
|
+
* `db.registerCollation(...)` after the plan was built leaves the plan on the old comparator
|
|
38
|
+
* until the MV is re-registered — which any schema change does, but a bare `registerCollation`
|
|
39
|
+
* does not. Same exposure as index comparators, which resolve once at table open; if that ever
|
|
40
|
+
* needs fixing, fix it for both.
|
|
41
|
+
*
|
|
42
|
+
* NOTE: `resolver` throws on an unregistered collation name. Unreachable today — the memory
|
|
43
|
+
* module rejects the backing `create` first — so this never converts a working `create
|
|
44
|
+
* materialized view` into an error. If a backing host is ever added that tolerates an
|
|
45
|
+
* unregistered name on a key column, this becomes the first site to throw, at plan-build time.
|
|
46
|
+
*/
|
|
47
|
+
function resolveBackingPkColumns(backing, resolver) {
|
|
48
|
+
return backing.primaryKeyDefinition.map(d => ({
|
|
49
|
+
index: d.index,
|
|
50
|
+
desc: d.desc,
|
|
51
|
+
collation: d.collation,
|
|
52
|
+
collationFn: d.collation ? resolver(d.collation) : BINARY_COLLATION,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the row-time maintenance plan for an MV — **cost-gated, with a floor, never a
|
|
57
|
+
* shape allowlist**. The builder tries to match a bounded-delta arm by shape
|
|
58
|
+
* ({@link tryBuildBoundedDeltaArm}); a body that matches **none** falls through to the
|
|
59
|
+
* always-correct {@link buildFullRebuildPlan} floor (re-evaluate the whole body, replace
|
|
60
|
+
* the backing transactionally). **No body is rejected for its shape.** Only five
|
|
61
|
+
* create-time rejections remain, all non-shape (docs/invariants.md MV-006):
|
|
62
|
+
* - a **non-deterministic** body without `pragma nondeterministic_schema` — a hard reject
|
|
63
|
+
* in the matched arm (so the arm-specific determinism diagnostic survives) or, for a
|
|
64
|
+
* body matching no arm, in the floor's whole-body determinism check;
|
|
65
|
+
* - a **bag** (no provable unique key) — the floor's `keysOf` reject (a duplicate-producing
|
|
66
|
+
* body usually fails the set contract earlier, at create-fill);
|
|
67
|
+
* - a body with **no relational output**;
|
|
68
|
+
* - a body that **reads no source table** (`select 42`) — the floor's `sourceBases` reject;
|
|
69
|
+
* there is no source to index the plan under and no write that could dirty it;
|
|
70
|
+
* - a **full-rebuild-only body over a source past the size threshold**
|
|
71
|
+
* ({@link isFullRebuildPathological}, the `materialized_view_rebuild_row_threshold` option).
|
|
72
|
+
*
|
|
73
|
+
* The single source may itself be another MV's backing table (an MV-over-MV body):
|
|
74
|
+
* `building/select.ts` rewrites a reference to `mv1` into a `TableReference` against
|
|
75
|
+
* `mv1`'s backing table, so the source base is `mv1`'s backing base and the same checks
|
|
76
|
+
* evaluate against the (keyed `memory`) backing schema unchanged. A write to `mv1` then
|
|
77
|
+
* drives `mv2` via the cascade in {@link maintainRowTime}.
|
|
78
|
+
*
|
|
79
|
+
* Eligibility is a *cost choice* among the body's structurally-sound strategies
|
|
80
|
+
* ({@link selectMaintenanceStrategy}): the bounded-delta arms are preferred by the argmin
|
|
81
|
+
* cost gate, and full-rebuild is selected exactly when no bounded-delta arm is sound (an
|
|
82
|
+
* empty sound set resolves to the floor) — so an existing eligible shape is unaffected.
|
|
83
|
+
*/
|
|
84
|
+
export function buildMaintenancePlan(ctx, mv) {
|
|
85
|
+
const db = ctx;
|
|
86
|
+
// Analyze the MV's own body to compile maintenance; suppress the read-side
|
|
87
|
+
// rewrite so the body stays over its SOURCE table, not re-pointed at this
|
|
88
|
+
// MV's backing (which the maintenance plan is what keeps consistent).
|
|
89
|
+
const analyzed = db.schemaManager.withSuppressedMaterializedViewRewrite(() => {
|
|
90
|
+
const { plan } = ctx._buildPlan([mv.derivation.selectAst]);
|
|
91
|
+
return ctx.optimizer.optimizeForAnalysis(plan, db);
|
|
92
|
+
});
|
|
93
|
+
// Replicable-determinism gate — host-conditional, inert by default. A backing host
|
|
94
|
+
// whose backing replicates across peers (the sync-store) sets
|
|
95
|
+
// `requiresReplicableDerivations` so a platform-dependent UDF or collation cannot
|
|
96
|
+
// diverge peers. Checked here — after `analyzed`, before arm selection — so it applies
|
|
97
|
+
// regardless of which maintenance arm wins, over the SAME analyzed plan the determinism
|
|
98
|
+
// gate walks (nested calls, WHERE / GROUP BY / aggregate-arg / TVF-arg positions all
|
|
99
|
+
// reached). It sits NEXT TO, not in place of, the determinism gate: the two are
|
|
100
|
+
// orthogonal, so this is NOT lifted by `pragma nondeterministic_schema`. A memory/store
|
|
101
|
+
// host leaves the flag undefined ⇒ this block is skipped ⇒ zero behavior change.
|
|
102
|
+
// Idempotent (same body ⇒ same verdict), so it is also desirable on re-register /
|
|
103
|
+
// catalog import: a tampered catalog cannot smuggle a non-replicable body past a
|
|
104
|
+
// demanding host. Two gates of the same shape: a non-replicable FUNCTION (which the
|
|
105
|
+
// body walk's function-bearing nodes carry) and a non-replicable COLLATION (which rides
|
|
106
|
+
// each scalar node's resolved type plus the backing key's declared collations).
|
|
107
|
+
// Resolve the host LENIENTLY: at the create-time gate registration of an
|
|
108
|
+
// `alter table … set maintained` attach, a module that materializes its durable
|
|
109
|
+
// backing late (lamina's `ensureBackingForAttach`, after this gate) has no host
|
|
110
|
+
// yet. The host is used here ONLY for the host-conditional, default-inert
|
|
111
|
+
// `requiresReplicableDerivations` gate — a host that sets it (the synced-store
|
|
112
|
+
// flavor) always exists by plan-build time, so skipping the gate when the host
|
|
113
|
+
// is absent never lets a non-replicable body slip past. The reconcile resolves
|
|
114
|
+
// the host for real, and the maintenance arms re-resolve it per use.
|
|
115
|
+
const host = tryResolveBackingHost(db, mv);
|
|
116
|
+
if (host?.requiresReplicableDerivations) {
|
|
117
|
+
const offendingFn = findNonReplicableFunction(analyzed);
|
|
118
|
+
if (offendingFn)
|
|
119
|
+
throw nonReplicableDerivationError(mv.name, offendingFn);
|
|
120
|
+
const offendingCollation = findNonReplicableCollation(analyzed, mv, db);
|
|
121
|
+
if (offendingCollation)
|
|
122
|
+
throw nonReplicableCollationDerivationError(mv.name, offendingCollation);
|
|
123
|
+
}
|
|
124
|
+
// Try a bounded-delta arm; a shape that fits none falls through to the floor.
|
|
125
|
+
const boundedDelta = tryBuildBoundedDeltaArm(ctx, mv, analyzed);
|
|
126
|
+
const plan = boundedDelta ?? buildFullRebuildPlan(ctx, mv, analyzed);
|
|
127
|
+
// Precompute the NOT-NULL ordering-seeded PK skew guard once, here at plan build —
|
|
128
|
+
// which `alter … drop not null` re-runs (it recompiles the row-time plan live), so the
|
|
129
|
+
// guarded set is recomputed at exactly the moment the skew can appear. Gated to
|
|
130
|
+
// non-empty so the field stays `undefined` (zero per-write cost) for the common case.
|
|
131
|
+
const guarded = computeNullGuardColumns(mv, analyzed);
|
|
132
|
+
plan.nullGuardColumns = guarded.length > 0 ? guarded : undefined;
|
|
133
|
+
return plan;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Backing columns that would silently store a NULL into a declared-NOT-NULL physical-PK
|
|
137
|
+
* column if the source loosened — the reachable "NOT-NULL ordering-seeded PK over a
|
|
138
|
+
* loosened source" skew, precomputed for the row-time guard
|
|
139
|
+
* ({@link MaterializedViewManager.maintainRowTime}). A backing column `i` is guarded when
|
|
140
|
+
* it is (1) declared NOT NULL, (2) a physical-PK member, AND (3) its re-derived body output
|
|
141
|
+
* column `i` is now nullable. Backing column `i` corresponds 1:1 to body output column `i`
|
|
142
|
+
* (`deriveBackingShape` builds the backing column list positionally from the body's output),
|
|
143
|
+
* and `deriveBackingShape`'s own `notNull = type.nullable === false` derivation means the
|
|
144
|
+
* body-nullability term agrees with the backing declaration by construction.
|
|
145
|
+
*
|
|
146
|
+
* The body-nullability term (3) is the real discriminator: the NOT-NULL/physical-PK set
|
|
147
|
+
* alone is non-empty for almost every MV (the logical-key PK column is normally the NOT-NULL
|
|
148
|
+
* source PK), so guarding on (1)+(2) only would scan essentially every maintained write.
|
|
149
|
+
* (3) is true only for the rare loosened-source skew, so the returned set — hence
|
|
150
|
+
* `plan.nullGuardColumns` — is empty (the field stays `undefined`) for the common case. A
|
|
151
|
+
* physical-PK column declared *nullable* (a nullable-source ordering column) fails (1) and is
|
|
152
|
+
* never guarded — it self-consistently stores NULL and must keep working.
|
|
153
|
+
*/
|
|
154
|
+
function computeNullGuardColumns(mv, analyzed) {
|
|
155
|
+
const root = rootRelationalNode(analyzed);
|
|
156
|
+
if (!root)
|
|
157
|
+
return [];
|
|
158
|
+
const bodyColumns = root.getType().columns;
|
|
159
|
+
const pkIndices = new Set(mv.primaryKeyDefinition.map(d => d.index));
|
|
160
|
+
const guarded = [];
|
|
161
|
+
for (let i = 0; i < mv.columns.length; i++) {
|
|
162
|
+
const col = mv.columns[i];
|
|
163
|
+
if (col.notNull !== true)
|
|
164
|
+
continue; // nullable-declared PK column stays permitted
|
|
165
|
+
if (!pkIndices.has(i))
|
|
166
|
+
continue; // physical-PK member only
|
|
167
|
+
const bodyCol = bodyColumns[i];
|
|
168
|
+
if (bodyCol && bodyCol.type.nullable !== false) { // body now yields nullable
|
|
169
|
+
guarded.push({ index: i, name: col.name });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return guarded;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Route the analyzed body to the matching bounded-delta arm, or return `null` when its
|
|
176
|
+
* shape fits **no** bounded-delta arm (the caller then builds the full-rebuild floor).
|
|
177
|
+
* Each arm builder likewise returns `null` on a sub-shape mismatch and falls through
|
|
178
|
+
* here. The arms keep only **determinism** as a hard reject (so their arm-specific
|
|
179
|
+
* determinism diagnostic survives — see the individual builders); every other mismatch
|
|
180
|
+
* is a `null` fall-through. Bag / no-output / size rejects live in the floor.
|
|
181
|
+
*/
|
|
182
|
+
export function tryBuildBoundedDeltaArm(ctx, mv, analyzed) {
|
|
183
|
+
// A body that reads no source table has no bounded-delta arm → floor (which rejects
|
|
184
|
+
// a sourceless body). (A self-join / TVF fan-out surfaces ≥2 refs or a TVF node.)
|
|
185
|
+
const tableRefs = [...collectTableRefs(analyzed).values()];
|
|
186
|
+
if (tableRefs.length === 0)
|
|
187
|
+
return null;
|
|
188
|
+
// Shapes no bounded-delta arm models — a window function reads across the partition,
|
|
189
|
+
// set ops / recursive CTEs / DISTINCT / row caps are out of the bounded-delta model.
|
|
190
|
+
// They are NOT rejected: a deterministic, keyed such body is maintained by the floor.
|
|
191
|
+
if (containsNodeType(analyzed, PlanNodeType.Window))
|
|
192
|
+
return null;
|
|
193
|
+
if (containsNodeType(analyzed, PlanNodeType.Distinct))
|
|
194
|
+
return null;
|
|
195
|
+
if (containsNodeType(analyzed, PlanNodeType.SetOperation))
|
|
196
|
+
return null;
|
|
197
|
+
if (containsNodeType(analyzed, PlanNodeType.RecursiveCTE))
|
|
198
|
+
return null;
|
|
199
|
+
if (mv.derivation.selectAst.type === 'select' && (mv.derivation.selectAst.limit !== undefined || mv.derivation.selectAst.offset !== undefined)) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const tableRef = tableRefs[0];
|
|
203
|
+
const sourceSchema = tableRef.tableSchema;
|
|
204
|
+
const sourceBase = `${sourceSchema.schemaName}.${sourceSchema.name}`.toLowerCase();
|
|
205
|
+
// Single base source `T` joined to ONE lateral table-valued function — a fan-out
|
|
206
|
+
// body (each base row produces N backing rows) → the prefix-delete arm. Routed
|
|
207
|
+
// *before* the generic join branch below, because a lateral fan-out surfaces BOTH a
|
|
208
|
+
// Join and a TableFunctionCall. A multi-base TVF body falls to the floor.
|
|
209
|
+
if (containsNodeType(analyzed, PlanNodeType.TableFunctionCall)) {
|
|
210
|
+
if (tableRefs.length !== 1)
|
|
211
|
+
return null;
|
|
212
|
+
return buildLateralTvfPrefixDeletePlan(ctx, mv, analyzed, tableRef, sourceBase);
|
|
213
|
+
}
|
|
214
|
+
// Any join → the provably-1:1 join-residual arm. A fanning (non-1:1) join, an outer
|
|
215
|
+
// join, a >2-source join, an aggregate over a join, or a partial WHERE returns `null`
|
|
216
|
+
// from the builder → floor. (The lateral-TVF fan-out above is matched first because
|
|
217
|
+
// it also surfaces a join node.)
|
|
218
|
+
if (containsAnyJoin(analyzed)) {
|
|
219
|
+
return buildJoinResidualPlan(ctx, mv, analyzed, tableRefs);
|
|
220
|
+
}
|
|
221
|
+
// A non-join multi-source body (e.g. a WHERE-subquery over a second table) has no
|
|
222
|
+
// bounded-delta arm → floor.
|
|
223
|
+
if (tableRefs.length > 1)
|
|
224
|
+
return null;
|
|
225
|
+
// Single-source aggregate (`group by` over bare columns) → residual-recompute arm.
|
|
226
|
+
// Each changed source row belongs to exactly one group; maintenance recomputes that
|
|
227
|
+
// group's backing row from live state. A scalar aggregate (no GROUP BY) falls to the
|
|
228
|
+
// floor.
|
|
229
|
+
const aggregate = findAggregate(analyzed);
|
|
230
|
+
if (aggregate) {
|
|
231
|
+
return buildAggregateResidualPlan(ctx, mv, analyzed, tableRef, sourceBase, aggregate);
|
|
232
|
+
}
|
|
233
|
+
// The covering-index shape → inverse-projection arm (the default single-source arm).
|
|
234
|
+
return buildInverseProjectionPlan(ctx, mv, analyzed, tableRef, sourceBase);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Build an `'inverse-projection'` plan for the covering-index shape: a single
|
|
238
|
+
* row-preserving source `T` with a primary key, a linear
|
|
239
|
+
* `TableReference → optional Filter → Project → optional Sort` body, a projection that
|
|
240
|
+
* resolves every source PK column (and every backing-key column) to a **passthrough**
|
|
241
|
+
* source column — non-key columns may instead be a **deterministic scalar expression**
|
|
242
|
+
* over the source row — and a partial WHERE evaluable on a single source row. Returns
|
|
243
|
+
* `null` on any **shape** mismatch (the caller falls through to the full-rebuild floor);
|
|
244
|
+
* a **non-deterministic** computed column is the one hard reject (its arm-specific
|
|
245
|
+
* determinism diagnostic must survive rather than fall through to the floor's generic one).
|
|
246
|
+
*/
|
|
247
|
+
export function buildInverseProjectionPlan(ctx, mv, analyzed, tableRef, sourceBase) {
|
|
248
|
+
const db = ctx;
|
|
249
|
+
const sourceSchema = tableRef.tableSchema;
|
|
250
|
+
const sourcePkCols = sourceSchema.primaryKeyDefinition.map(d => d.index);
|
|
251
|
+
if (sourcePkCols.length === 0)
|
|
252
|
+
return null; // source has no PK → floor
|
|
253
|
+
const backing = ctx._findTable(mv.name, mv.schemaName);
|
|
254
|
+
if (!backing) {
|
|
255
|
+
throw new QuereusError(`Internal error: backing table '${mv.name}' for materialized view '${mv.name}' not found`, StatusCode.INTERNAL);
|
|
256
|
+
}
|
|
257
|
+
// Projection classification: each backing output column is either a passthrough
|
|
258
|
+
// source column (a pure permutation entry) or a deterministic scalar expression
|
|
259
|
+
// over the single source row. A passthrough makes maintenance a column copy; an
|
|
260
|
+
// expression column evaluates `project(sourceRow)` via the runtime (still a pure
|
|
261
|
+
// per-row function — O(log n), no body re-execution). PK / backing-key columns
|
|
262
|
+
// must stay passthrough (the backing key and the inverse-projection conflict map
|
|
263
|
+
// depend on it); non-key columns may be computed.
|
|
264
|
+
//
|
|
265
|
+
// "Passthrough" is value-preserving lineage (`resolveValuePreservingSourceCol`):
|
|
266
|
+
// a bare column reference, OR one wrapped in `collate` / a no-op `cast` — those
|
|
267
|
+
// wrappers copy the source VALUE verbatim, so the column-copy maintenance is
|
|
268
|
+
// exact. This is what lets the collation-weakening migration shape (`select b
|
|
269
|
+
// collate nocase as b from t`) register here with its coarsened backing key:
|
|
270
|
+
// the per-row upsert is keyed under the backing PK's (output) collation, so a
|
|
271
|
+
// colliding source row last-write-wins into the shared backing row, and a
|
|
272
|
+
// delete of one colliding sibling removes the shared row (the documented
|
|
273
|
+
// anomaly — docs/materialized-views.md § Coarsened backing keys).
|
|
274
|
+
const sourceAttrToCol = new Map();
|
|
275
|
+
const sourceDescriptor = [];
|
|
276
|
+
tableRef.getAttributes().forEach((a, i) => {
|
|
277
|
+
sourceAttrToCol.set(a.id, i);
|
|
278
|
+
sourceDescriptor[a.id] = i;
|
|
279
|
+
});
|
|
280
|
+
const producingByAttrId = collectProducingExprs(analyzed);
|
|
281
|
+
const rootAttrs = relationalAttributes(analyzed);
|
|
282
|
+
if (!rootAttrs)
|
|
283
|
+
return null; // no relational output → floor (which hard-rejects it)
|
|
284
|
+
const projectors = [];
|
|
285
|
+
for (let outCol = 0; outCol < rootAttrs.length; outCol++) {
|
|
286
|
+
const attr = rootAttrs[outCol];
|
|
287
|
+
const sourceCol = attr ? resolveValuePreservingSourceCol(attr.id, sourceAttrToCol, producingByAttrId) : undefined;
|
|
288
|
+
if (sourceCol !== undefined) {
|
|
289
|
+
projectors.push({ kind: 'passthrough', sourceCol });
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// Computed column: a deterministic scalar over the source row. A
|
|
293
|
+
// non-deterministic producer is a HARD reject (the arm-specific determinism
|
|
294
|
+
// diagnostic must survive — so `random()` fails on *determinism*, not by silently
|
|
295
|
+
// falling to a still-rejected floor); a deterministic-but-unsupported *shape*
|
|
296
|
+
// (no resolvable producer, a subquery / cross-row reference, an async form)
|
|
297
|
+
// returns `null` → the floor.
|
|
298
|
+
const colName = attr?.name ?? `#${outCol}`;
|
|
299
|
+
const producing = attr ? producingByAttrId.get(attr.id) : undefined;
|
|
300
|
+
if (!producing)
|
|
301
|
+
return null;
|
|
302
|
+
const det = checkDeterministic(producing);
|
|
303
|
+
if (!det.valid) {
|
|
304
|
+
throw cannotMaterialize(mv.name, `it projects a non-deterministic expression column '${colName}' (${det.expression}); `
|
|
305
|
+
+ `a row-time backing value must be reproducible from the source row`);
|
|
306
|
+
}
|
|
307
|
+
if (!isSingleRowEvaluable(producing, sourceDescriptor))
|
|
308
|
+
return null;
|
|
309
|
+
let evalFn;
|
|
310
|
+
try {
|
|
311
|
+
evalFn = compileSourceRowEvaluator(db, producing, sourceDescriptor);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return null; // not row-time maintainable as a single-row scalar → floor
|
|
315
|
+
}
|
|
316
|
+
projectors.push({ kind: 'expr', eval: evalFn });
|
|
317
|
+
}
|
|
318
|
+
// Every source PK column must be projected as a passthrough column so the backing
|
|
319
|
+
// key is a deterministic identity of the source row that `lookupCoveringConflicts`
|
|
320
|
+
// can invert. A PK column produced only via an expression (or not at all) breaks
|
|
321
|
+
// that recovery.
|
|
322
|
+
const passthroughSourceCols = new Set(projectors.flatMap(p => p.kind === 'passthrough' ? [p.sourceCol] : []));
|
|
323
|
+
for (const pk of sourcePkCols) {
|
|
324
|
+
if (!passthroughSourceCols.has(pk))
|
|
325
|
+
return null; // PK not passthrough-projected → floor
|
|
326
|
+
}
|
|
327
|
+
const backingPkDefinition = resolveBackingPkColumns(backing, ctx.getCollationResolver());
|
|
328
|
+
// A computed column may never land in the backing primary key: the btree keys on
|
|
329
|
+
// it and `lookupCoveringConflicts` recovers the source PK from it, both of which
|
|
330
|
+
// require a passthrough source-column identity.
|
|
331
|
+
for (const d of backingPkDefinition) {
|
|
332
|
+
if (projectors[d.index]?.kind !== 'passthrough')
|
|
333
|
+
return null; // computed backing-key col → floor
|
|
334
|
+
}
|
|
335
|
+
// Partial WHERE must be evaluable on a single source row (no subqueries /
|
|
336
|
+
// cross-row references). `compilePredicate` throws on unsupported forms; an
|
|
337
|
+
// unsupported WHERE shape falls to the floor.
|
|
338
|
+
//
|
|
339
|
+
// No owning-table name is passed (lenient qualifier handling), deliberately: the
|
|
340
|
+
// view body may reference the source through a FROM alias, not the source table's
|
|
341
|
+
// own name, so validating against `sourceSchema.name` would wrongly reject a legal
|
|
342
|
+
// aliased body — and this site FLOORS to full-rebuild on any throw rather than
|
|
343
|
+
// erroring, turning a false rejection into a silent maintenance regression.
|
|
344
|
+
// NOTE: to make MV WHERE strict, thread the alias set from the view's FROM into
|
|
345
|
+
// this builder (not currently available here) and validate against it.
|
|
346
|
+
let predicate;
|
|
347
|
+
const bodyWhere = mv.derivation.selectAst.type === 'select' ? mv.derivation.selectAst.where : undefined;
|
|
348
|
+
if (bodyWhere) {
|
|
349
|
+
try {
|
|
350
|
+
predicate = compilePredicate(bodyWhere, sourceSchema.columns);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return null; // WHERE not evaluable on a single source row → floor
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// ── Cost gate (incremental-maintenance-cost-gate) ──
|
|
357
|
+
// The covering-index shape's only structurally-sound maintenance strategy is
|
|
358
|
+
// 'inverse-projection' (O(1) per changed row); 'full-rebuild' is the floor for bodies
|
|
359
|
+
// this arm did NOT match (reached via the `null` fall-through above), so it is not a
|
|
360
|
+
// competitor here. Eligibility is thus a cost choice among the sound strategies (argmin
|
|
361
|
+
// maintenanceCost); for this shape it resolves to inverse-projection while recording the
|
|
362
|
+
// choice + the cost inputs the runtime reuses.
|
|
363
|
+
const soundStrategies = ['inverse-projection'];
|
|
364
|
+
const sourceStats = estimateMaintenanceStats(ctx, sourceSchema, projectors.length, predicate !== undefined);
|
|
365
|
+
// Create-time change-cardinality estimate: ~1% of the source per statement (typical OLTP).
|
|
366
|
+
const estimatedChangeCardinality = Math.max(1, sourceStats.tableRows * 0.01);
|
|
367
|
+
const chosenStrategy = selectMaintenanceStrategy(soundStrategies, estimatedChangeCardinality, sourceStats);
|
|
368
|
+
// Defensive: this arm's sound set is exactly ['inverse-projection']. A different choice
|
|
369
|
+
// would mean the set grew without the corresponding apply-arm — fail loud rather than
|
|
370
|
+
// register an unexecutable plan.
|
|
371
|
+
if (chosenStrategy !== 'inverse-projection') {
|
|
372
|
+
throw new QuereusError(`Internal error: cost gate selected unwired strategy '${chosenStrategy}' for materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
kind: 'inverse-projection',
|
|
376
|
+
mv,
|
|
377
|
+
sourceBase,
|
|
378
|
+
backingSchema: mv.schemaName,
|
|
379
|
+
backingTableName: mv.name,
|
|
380
|
+
chosenStrategy,
|
|
381
|
+
sourceStats,
|
|
382
|
+
backingPkDefinition,
|
|
383
|
+
projectors,
|
|
384
|
+
predicate,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Build a `'residual-recompute'` plan for a single-source aggregate body
|
|
389
|
+
* (`select g1,…, agg(…) from T [where P] group by g1,…` over **bare** group columns),
|
|
390
|
+
* or return `null` on a shape mismatch (the caller falls through to the full-rebuild
|
|
391
|
+
* floor). Each changed source row belongs to exactly one group `(g1,…)`; maintaining the
|
|
392
|
+
* MV means recomputing that group's backing row from live state — delete the old slice,
|
|
393
|
+
* run the group-keyed residual, upsert the recomputed slice (zero rows when the group
|
|
394
|
+
* emptied). See {@link ResidualRecomputePlan} and `docs/incremental-maintenance.md`
|
|
395
|
+
* § residual-recompute.
|
|
396
|
+
*
|
|
397
|
+
* A **non-deterministic** group/aggregate expression is the one hard reject (the
|
|
398
|
+
* arm-specific determinism diagnostic must survive); every other mismatch — a scalar
|
|
399
|
+
* aggregate, a computed group key, a backing key that is not the group key — returns
|
|
400
|
+
* `null` → the floor.
|
|
401
|
+
*
|
|
402
|
+
* NOTE: the group binding is derived **directly** from the aggregate node's bare GROUP
|
|
403
|
+
* BY columns, not via `extractBindings`. `analyzeRowSpecific`'s `'group'` classification
|
|
404
|
+
* additionally requires the group key to cover a *source* unique key (so it reports
|
|
405
|
+
* `'global'` for the common `group by <non-key>` body), which is the wrong test here —
|
|
406
|
+
* the backing is keyed by the group key regardless of whether it is a source key.
|
|
407
|
+
*/
|
|
408
|
+
export function buildAggregateResidualPlan(ctx, mv, analyzed, tableRef, sourceBase, aggregate) {
|
|
409
|
+
// A scalar aggregate (no GROUP BY) is one global row keyed by the empty key — no
|
|
410
|
+
// bounded-delta group binding, so it falls to the floor.
|
|
411
|
+
if (aggregate.groupBy.length === 0)
|
|
412
|
+
return null;
|
|
413
|
+
// Map T's output attributes to source column indices. T is a bare
|
|
414
|
+
// `TableReferenceNode`, so output-column index == source-column index.
|
|
415
|
+
const sourceAttrToCol = new Map();
|
|
416
|
+
tableRef.getAttributes().forEach((a, i) => sourceAttrToCol.set(a.id, i));
|
|
417
|
+
const producingByAttrId = collectProducingExprs(analyzed);
|
|
418
|
+
// Transitive provenance: chase output-attr → producing ColumnReference chains
|
|
419
|
+
// (Project-over-Aggregate adds a hop the single-hop `resolveSourceCol` cannot
|
|
420
|
+
// follow) until landing on a T source column, or `undefined`.
|
|
421
|
+
const resolveToSourceCol = (attrId) => resolveTransitiveSourceCol(attrId, sourceAttrToCol, producingByAttrId);
|
|
422
|
+
// Each GROUP BY expression must be a bare source column (a computed group key has
|
|
423
|
+
// no source-column index to bind / key the backing on) → otherwise the floor.
|
|
424
|
+
const groupColumns = [];
|
|
425
|
+
for (const expr of aggregate.groupBy) {
|
|
426
|
+
if (!(expr instanceof ColumnReferenceNode))
|
|
427
|
+
return null;
|
|
428
|
+
const sourceCol = sourceAttrToCol.get(expr.attributeId);
|
|
429
|
+
if (sourceCol === undefined)
|
|
430
|
+
return null;
|
|
431
|
+
groupColumns.push(sourceCol);
|
|
432
|
+
}
|
|
433
|
+
// Determinism: a residual must reproduce exactly what `select <body>` returns, so a
|
|
434
|
+
// volatile group/aggregate expression (random()/now()/volatile UDF) is a HARD reject.
|
|
435
|
+
for (const expr of aggregate.groupBy) {
|
|
436
|
+
const det = checkDeterministic(expr);
|
|
437
|
+
if (!det.valid)
|
|
438
|
+
throw cannotMaterialize(mv.name, `it groups by a non-deterministic expression (${det.expression})`);
|
|
439
|
+
}
|
|
440
|
+
for (const agg of aggregate.aggregates) {
|
|
441
|
+
const det = checkDeterministic(agg.expression);
|
|
442
|
+
if (!det.valid)
|
|
443
|
+
throw cannotMaterialize(mv.name, `it aggregates a non-deterministic expression (${det.expression})`);
|
|
444
|
+
}
|
|
445
|
+
// Backing table + its physical PK. The aggregate's group-key FD
|
|
446
|
+
// (`propagateAggregateFds`) makes the group key the backing key (via `keysOf`).
|
|
447
|
+
const backing = ctx._findTable(mv.name, mv.schemaName);
|
|
448
|
+
if (!backing) {
|
|
449
|
+
throw new QuereusError(`Internal error: backing table '${mv.name}' for materialized view '${mv.name}' not found`, StatusCode.INTERNAL);
|
|
450
|
+
}
|
|
451
|
+
const backingPkDefinition = resolveBackingPkColumns(backing, ctx.getCollationResolver());
|
|
452
|
+
// Map each backing-PK column back to the source group column it projects, so a
|
|
453
|
+
// changed row's old backing-slice delete key can be built. Every backing-PK column
|
|
454
|
+
// MUST resolve to a GROUP BY source column — else the backing key is not the group
|
|
455
|
+
// key and point-keyed delete+upsert would be unsound → fall to the floor.
|
|
456
|
+
const rootAttrs = relationalAttributes(analyzed);
|
|
457
|
+
if (!rootAttrs)
|
|
458
|
+
return null;
|
|
459
|
+
const groupColumnSet = new Set(groupColumns);
|
|
460
|
+
const backingPkSourceCols = [];
|
|
461
|
+
for (const d of backingPkDefinition) {
|
|
462
|
+
const attr = rootAttrs[d.index];
|
|
463
|
+
const sourceCol = attr ? resolveToSourceCol(attr.id) : undefined;
|
|
464
|
+
if (sourceCol === undefined || !groupColumnSet.has(sourceCol))
|
|
465
|
+
return null;
|
|
466
|
+
backingPkSourceCols.push(sourceCol);
|
|
467
|
+
}
|
|
468
|
+
// Compile + cache the group-keyed residual once (the body with `g1 = :gk0 AND …`
|
|
469
|
+
// injected on T). Re-run per affected group key against the live transaction.
|
|
470
|
+
const relKey = `${sourceBase}#${tableRef.id ?? 'unknown'}`;
|
|
471
|
+
const residualScheduler = compileResidual(ctx, analyzed, relKey, groupColumns, 'gk');
|
|
472
|
+
if (!residualScheduler)
|
|
473
|
+
return null; // could not parameterize the residual → floor
|
|
474
|
+
// ── Cost gate ──
|
|
475
|
+
// The residual is the structurally-sound incremental arm for an aggregate body;
|
|
476
|
+
// 'full-rebuild' is the always-correct floor for shapes where the residual is NOT
|
|
477
|
+
// sound, so (as with inverse-projection) it is not a competitor here. We still
|
|
478
|
+
// record the chosen strategy + cost inputs for parity with the substrate.
|
|
479
|
+
const soundStrategies = ['residual-recompute'];
|
|
480
|
+
const hasPredicate = mv.derivation.selectAst.type === 'select' && mv.derivation.selectAst.where !== undefined;
|
|
481
|
+
const sourceStats = estimateMaintenanceStats(ctx, tableRef.tableSchema, backing.columns.length, hasPredicate);
|
|
482
|
+
const estimatedChangeCardinality = Math.max(1, sourceStats.tableRows * 0.01);
|
|
483
|
+
const chosenStrategy = selectMaintenanceStrategy(soundStrategies, estimatedChangeCardinality, sourceStats);
|
|
484
|
+
if (chosenStrategy !== 'residual-recompute') {
|
|
485
|
+
throw new QuereusError(`Internal error: cost gate selected unwired strategy '${chosenStrategy}' for materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
kind: 'residual-recompute',
|
|
489
|
+
mv,
|
|
490
|
+
sourceBase,
|
|
491
|
+
backingSchema: mv.schemaName,
|
|
492
|
+
backingTableName: mv.name,
|
|
493
|
+
chosenStrategy,
|
|
494
|
+
sourceStats,
|
|
495
|
+
binding: { kind: 'group', groupColumns: [...groupColumns] },
|
|
496
|
+
degradeToRebuild: false,
|
|
497
|
+
residualScheduler,
|
|
498
|
+
bindParamPrefix: 'gk',
|
|
499
|
+
bindColumns: groupColumns,
|
|
500
|
+
backingPkDefinition,
|
|
501
|
+
backingPkSourceCols,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Build a `'join-residual'` plan for a provably-1:1 row-preserving **inner/cross join**
|
|
506
|
+
* body (`select … from T join P on T.fk = P.id`), or return `null` on a shape mismatch
|
|
507
|
+
* (the caller falls through to the full-rebuild floor). The driving table `T` is the one
|
|
508
|
+
* whose PK the optimizer surfaced as the backing key (the 1:1 join collapses the composite
|
|
509
|
+
* product key to `T`'s PK); the other base ref is the lookup `P`. See {@link JoinResidualPlan}
|
|
510
|
+
* and `docs/incremental-maintenance.md` § join-residual.
|
|
511
|
+
*
|
|
512
|
+
* Soundness gates (a mismatch on any returns `null` → floor): exactly two base tables; no
|
|
513
|
+
* aggregate over the join; the backing PK is exactly `T`'s PK projected as passthrough
|
|
514
|
+
* columns (so each changed `T` row maps to one backing row and the reverse residual's rows
|
|
515
|
+
* carry the backing key); the join is provably 1:1 on `T` ({@link proveOneToOneJoin} — no
|
|
516
|
+
* row loss via NOT-NULL FK→PK RI, no fan-out via the join-frame `isUnique(T.pk)`); and the
|
|
517
|
+
* join is **inner/cross** (an outer join would make the lookup-side reverse residual unsound
|
|
518
|
+
* — filtering `P` drops the null-extended rows). A **non-deterministic** projection is the
|
|
519
|
+
* one hard reject (its arm-specific determinism diagnostic must survive).
|
|
520
|
+
*
|
|
521
|
+
* **A body WHERE is now accepted** (it is no longer a blanket reject): the predicate is
|
|
522
|
+
* classified by which base table(s) its columns reference (reusing the per-base-ref
|
|
523
|
+
* attribute→source-column maps below). A `T`-only predicate needs nothing extra — the
|
|
524
|
+
* forward residual already carries it and the membership set `{ T : T.fk = P.pk }` cannot
|
|
525
|
+
* move on a `P` write, so the lookup side stays upsert-only. A predicate referencing `P` (or
|
|
526
|
+
* both sides) switches the lookup side to a **delete-capable** reverse residual by building
|
|
527
|
+
* `lookupMembershipResidualScheduler` (the body with the WHERE stripped, keyed on `P`). See
|
|
528
|
+
* {@link JoinResidualPlan}'s "WHERE handling" note and {@link applyLookupResidual}.
|
|
529
|
+
*/
|
|
530
|
+
export function buildJoinResidualPlan(ctx, mv, analyzed, tableRefs) {
|
|
531
|
+
// A >2-source join or an aggregate over the join has no join-residual binding → floor.
|
|
532
|
+
// A body WHERE is no longer rejected here — it is classified (T-only vs P-referencing)
|
|
533
|
+
// below, after `T`/`P` are identified, and routed to the matching lookup-side strategy.
|
|
534
|
+
if (tableRefs.length !== 2)
|
|
535
|
+
return null;
|
|
536
|
+
if (findAggregate(analyzed))
|
|
537
|
+
return null;
|
|
538
|
+
const backing = ctx._findTable(mv.name, mv.schemaName);
|
|
539
|
+
if (!backing) {
|
|
540
|
+
throw new QuereusError(`Internal error: backing table '${mv.name}' for materialized view '${mv.name}' not found`, StatusCode.INTERNAL);
|
|
541
|
+
}
|
|
542
|
+
const backingPkDefinition = resolveBackingPkColumns(backing, ctx.getCollationResolver());
|
|
543
|
+
const rootAttrs = relationalAttributes(analyzed);
|
|
544
|
+
if (!rootAttrs)
|
|
545
|
+
return null;
|
|
546
|
+
const producingByAttrId = collectProducingExprs(analyzed);
|
|
547
|
+
// Per-base-ref attribute → source-column maps. `T` and `P` are bare
|
|
548
|
+
// TableReferenceNodes (output-col index == source-col index).
|
|
549
|
+
const refInfos = tableRefs.map(ref => {
|
|
550
|
+
const attrToCol = new Map();
|
|
551
|
+
ref.getAttributes().forEach((a, i) => attrToCol.set(a.id, i));
|
|
552
|
+
return { ref, attrToCol };
|
|
553
|
+
});
|
|
554
|
+
// Identify the driving table `T` as the one every backing-PK column resolves to, and
|
|
555
|
+
// map each backing-PK column to its `T` source column (the delete key the forward arm
|
|
556
|
+
// builds). A backing-PK column resolving to neither ref — or columns spanning both —
|
|
557
|
+
// means the backing is not keyed on a single source's PK (the join is not provably
|
|
558
|
+
// 1:1, or `keysOf` fell back to all-columns): reject.
|
|
559
|
+
let tIndex;
|
|
560
|
+
const backingPkSourceCols = [];
|
|
561
|
+
for (const d of backingPkDefinition) {
|
|
562
|
+
const attr = rootAttrs[d.index];
|
|
563
|
+
let resolvedRef;
|
|
564
|
+
let resolvedCol;
|
|
565
|
+
for (let i = 0; i < refInfos.length; i++) {
|
|
566
|
+
const sc = attr ? resolveTransitiveSourceCol(attr.id, refInfos[i].attrToCol, producingByAttrId) : undefined;
|
|
567
|
+
if (sc !== undefined) {
|
|
568
|
+
resolvedRef = i;
|
|
569
|
+
resolvedCol = sc;
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// A backing-PK column resolving to neither ref, or columns spanning both, means the
|
|
574
|
+
// backing is not keyed on a single source's PK (not provably 1:1) → fall to the floor.
|
|
575
|
+
if (resolvedRef === undefined)
|
|
576
|
+
return null;
|
|
577
|
+
if (tIndex === undefined)
|
|
578
|
+
tIndex = resolvedRef;
|
|
579
|
+
else if (tIndex !== resolvedRef)
|
|
580
|
+
return null;
|
|
581
|
+
backingPkSourceCols.push(resolvedCol);
|
|
582
|
+
}
|
|
583
|
+
if (tIndex === undefined)
|
|
584
|
+
return null;
|
|
585
|
+
const tRef = refInfos[tIndex].ref;
|
|
586
|
+
const pRef = refInfos[tIndex === 0 ? 1 : 0].ref;
|
|
587
|
+
const tSchema = tRef.tableSchema;
|
|
588
|
+
const pSchema = pRef.tableSchema;
|
|
589
|
+
const sourceBase = `${tSchema.schemaName}.${tSchema.name}`.toLowerCase();
|
|
590
|
+
const lookupBase = `${pSchema.schemaName}.${pSchema.name}`.toLowerCase();
|
|
591
|
+
if (sourceBase === lookupBase)
|
|
592
|
+
return null; // self-join → floor
|
|
593
|
+
// The backing key must be EXACTLY the driving source's PK (each `T` row → one backing
|
|
594
|
+
// row). `keysOf` surfaced `T.pk` for the 1:1 join; verify it resolved to a real PK key
|
|
595
|
+
// (not the all-columns fallback) by set-equality with `T`'s declared PK.
|
|
596
|
+
const tPkCols = tSchema.primaryKeyDefinition.map(d => d.index);
|
|
597
|
+
if (tPkCols.length === 0)
|
|
598
|
+
return null;
|
|
599
|
+
const backingPkSet = new Set(backingPkSourceCols);
|
|
600
|
+
if (backingPkSet.size !== tPkCols.length || !tPkCols.every(c => backingPkSet.has(c)))
|
|
601
|
+
return null;
|
|
602
|
+
// Prove the join is 1:1 on `T` (no row loss + no fan-out), reusing the coverage
|
|
603
|
+
// prover's shared join predicates over the analyzed body. A fanning / non-1:1 join
|
|
604
|
+
// falls to the floor.
|
|
605
|
+
const root = rootRelationalNode(analyzed);
|
|
606
|
+
if (!root)
|
|
607
|
+
return null;
|
|
608
|
+
const proof = proveOneToOneJoin(root, tSchema);
|
|
609
|
+
if (!proof.ok)
|
|
610
|
+
return null;
|
|
611
|
+
// Restrict to inner/cross: the lookup-side reverse residual filters `P`, which would
|
|
612
|
+
// drop an outer join's null-extended rows (unsound). An outer 1:1 join falls to the floor.
|
|
613
|
+
const topJoin = proof.topJoin;
|
|
614
|
+
const joinType = topJoin && CapabilityDetectors.isJoin(topJoin) ? topJoin.getJoinType() : undefined;
|
|
615
|
+
if (joinType !== 'inner' && joinType !== 'cross')
|
|
616
|
+
return null;
|
|
617
|
+
// Determinism: the residual must reproduce exactly what `select <body>` returns, so a
|
|
618
|
+
// volatile projection (random()/now()/volatile UDF) is a HARD reject.
|
|
619
|
+
for (const attr of rootAttrs) {
|
|
620
|
+
const producing = attr ? producingByAttrId.get(attr.id) : undefined;
|
|
621
|
+
if (!producing)
|
|
622
|
+
continue; // a bare passthrough column has no producing expr to check
|
|
623
|
+
const det = checkDeterministic(producing);
|
|
624
|
+
if (!det.valid) {
|
|
625
|
+
throw cannotMaterialize(mv.name, `it projects a non-deterministic expression (${det.expression}); a row-time backing value must be reproducible from the source rows`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Forward (`T`) residual: the body with `T.pk = :pk0 AND …` injected on `T`. Recomputes
|
|
629
|
+
// the one joined row for a changed `T` row (delegated to `applyForwardResidual`).
|
|
630
|
+
const tRelKey = `${sourceBase}#${tRef.id ?? 'unknown'}`;
|
|
631
|
+
const forwardResidual = compileResidual(ctx, analyzed, tRelKey, tPkCols, 'pk');
|
|
632
|
+
if (!forwardResidual)
|
|
633
|
+
return null;
|
|
634
|
+
// Reverse (`P`) **in-scope** residual: the body — WHERE retained — with `P.pk = :pk0 AND …`
|
|
635
|
+
// injected on `P`. Drives lookup-side maintenance — finds every currently in-scope joined
|
|
636
|
+
// row referencing a changed `P` row.
|
|
637
|
+
const pPkCols = pSchema.primaryKeyDefinition.map(d => d.index);
|
|
638
|
+
if (pPkCols.length === 0)
|
|
639
|
+
return null;
|
|
640
|
+
const pRelKey = `${lookupBase}#${pRef.id ?? 'unknown'}`;
|
|
641
|
+
const reverseResidual = compileResidual(ctx, analyzed, pRelKey, pPkCols, 'pk');
|
|
642
|
+
if (!reverseResidual)
|
|
643
|
+
return null;
|
|
644
|
+
// Classify the body WHERE by which base table(s) its columns reference: a predicate that
|
|
645
|
+
// references `P` (or both sides) can flip a row's WHERE membership on a `P` write, so the
|
|
646
|
+
// lookup side must become delete-capable; a `T`-only predicate cannot move membership, so
|
|
647
|
+
// the upsert-only reverse residual above stays sound. The membership residual is the body
|
|
648
|
+
// with the WHERE **stripped** and the key filter on `P` — it returns every currently
|
|
649
|
+
// referencing `T` row (regardless of scope) so its backing key can be deleted before the
|
|
650
|
+
// in-scope survivors are re-upserted. Absent for a no-WHERE / `T`-only-WHERE body.
|
|
651
|
+
const hasWhere = mv.derivation.selectAst.type === 'select' && mv.derivation.selectAst.where !== undefined;
|
|
652
|
+
// A volatile WHERE would make every residual (which embeds it) irreproducible → fall to
|
|
653
|
+
// the floor's pragma-gated whole-body determinism reject, not an unsound bounded-delta arm.
|
|
654
|
+
if (hasWhere && bodyWhereIsNonDeterministic(analyzed))
|
|
655
|
+
return null;
|
|
656
|
+
const whereReferencesLookup = hasWhere
|
|
657
|
+
&& bodyWhereReferencesLookup(analyzed, refInfos[tIndex].attrToCol, producingByAttrId);
|
|
658
|
+
let lookupMembershipResidual;
|
|
659
|
+
if (whereReferencesLookup) {
|
|
660
|
+
const membership = compileLookupMembershipResidual(ctx, mv, lookupBase, pPkCols);
|
|
661
|
+
if (!membership)
|
|
662
|
+
return null; // could not strip + re-key the membership residual → floor
|
|
663
|
+
lookupMembershipResidual = membership;
|
|
664
|
+
}
|
|
665
|
+
// ── Cost gate (parity with the other residual arms) ──
|
|
666
|
+
const soundStrategies = ['residual-recompute'];
|
|
667
|
+
const sourceStats = estimateMaintenanceStats(ctx, tSchema, backing.columns.length, hasWhere);
|
|
668
|
+
const estimatedChangeCardinality = Math.max(1, sourceStats.tableRows * 0.01);
|
|
669
|
+
const chosenStrategy = selectMaintenanceStrategy(soundStrategies, estimatedChangeCardinality, sourceStats);
|
|
670
|
+
if (chosenStrategy !== 'residual-recompute') {
|
|
671
|
+
throw new QuereusError(`Internal error: cost gate selected unwired strategy '${chosenStrategy}' for materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
kind: 'join-residual',
|
|
675
|
+
mv,
|
|
676
|
+
sourceBase,
|
|
677
|
+
backingSchema: mv.schemaName,
|
|
678
|
+
backingTableName: mv.name,
|
|
679
|
+
chosenStrategy,
|
|
680
|
+
sourceStats,
|
|
681
|
+
binding: { kind: 'row', keyColumns: [...tPkCols] },
|
|
682
|
+
degradeToRebuild: false,
|
|
683
|
+
residualScheduler: forwardResidual,
|
|
684
|
+
bindParamPrefix: 'pk',
|
|
685
|
+
bindColumns: tPkCols,
|
|
686
|
+
backingPkDefinition,
|
|
687
|
+
backingPkSourceCols,
|
|
688
|
+
lookupBase,
|
|
689
|
+
lookupResidualScheduler: reverseResidual,
|
|
690
|
+
lookupMembershipResidualScheduler: lookupMembershipResidual,
|
|
691
|
+
lookupBindColumns: pPkCols,
|
|
692
|
+
lookupBindParamPrefix: 'pk',
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Compile the **lookup membership** residual for the join-residual arm's delete-capable
|
|
697
|
+
* lookup side: the MV body with its top-level WHERE **stripped** (membership only) and a
|
|
698
|
+
* key-equality filter injected on the lookup `P`, keyed `pk0…`. The WHERE is stripped at the
|
|
699
|
+
* AST level (a shallow clone dropping `where`) and the body re-built + re-analyzed, so only
|
|
700
|
+
* the WHERE is removed — the join, its `ON` condition, and any projection sub-expressions are
|
|
701
|
+
* preserved. Re-analysis assigns fresh node ids, so `P`'s reference is re-located by base name
|
|
702
|
+
* to compute the injection target. Returns `null` if the lookup ref or the key-filter
|
|
703
|
+
* injection could not be resolved (the caller then falls to the full-rebuild floor).
|
|
704
|
+
*
|
|
705
|
+
* Run per affected `P` key, this residual returns **every** `T` row currently joined to `P`
|
|
706
|
+
* via the join's `ON` condition — irrespective of the WHERE — so {@link applyLookupResidual}
|
|
707
|
+
* can delete each one's `T.pk` backing key before the in-scope residual re-upserts the
|
|
708
|
+
* survivors (the membership set the WHERE-bearing reverse residual would otherwise never
|
|
709
|
+
* shrink).
|
|
710
|
+
*/
|
|
711
|
+
export function compileLookupMembershipResidual(ctx, mv, lookupBase, pPkCols) {
|
|
712
|
+
const db = ctx;
|
|
713
|
+
const strippedAst = { ...mv.derivation.selectAst, where: undefined };
|
|
714
|
+
const stripped = db.schemaManager.withSuppressedMaterializedViewRewrite(() => {
|
|
715
|
+
const { plan } = ctx._buildPlan([strippedAst]);
|
|
716
|
+
return ctx.optimizer.optimizeForAnalysis(plan, db);
|
|
717
|
+
});
|
|
718
|
+
// Re-locate `P` in the WHERE-stripped plan by base name (fresh node ids) to build the
|
|
719
|
+
// injection target key the way `compileResidual`'s callers do.
|
|
720
|
+
let pRelKey;
|
|
721
|
+
for (const [relKey, ref] of collectTableRefs(stripped)) {
|
|
722
|
+
if (`${ref.tableSchema.schemaName}.${ref.tableSchema.name}`.toLowerCase() === lookupBase) {
|
|
723
|
+
pRelKey = relKey;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (pRelKey === undefined)
|
|
728
|
+
return null;
|
|
729
|
+
return compileResidual(ctx, stripped, pRelKey, pPkCols, 'pk');
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Build a `'full-rebuild'` plan — the always-correct floor — for an MV whose body matches
|
|
733
|
+
* no bounded-delta arm, or throw with a non-shape diagnostic. This is the fall-through
|
|
734
|
+
* builder {@link tryBuildBoundedDeltaArm} routes to on a `null` (no bounded-delta arm fits).
|
|
735
|
+
* See {@link FullRebuildPlan} and `docs/mv-maintenance.md` § Full-rebuild floor /
|
|
736
|
+
* § Primary key inference.
|
|
737
|
+
*
|
|
738
|
+
* Create-time rejections (none shape-based — the floor accepts general bodies):
|
|
739
|
+
* - **bag** body with no provable unique key (`keysOf` over the optimized body root is
|
|
740
|
+
* empty) — there is no row identity to materialize on. `keysOf` already gates its
|
|
741
|
+
* all-columns fallback on `isSet`, so a non-empty result is a real key (a true column
|
|
742
|
+
* key OR the all-columns key of a provable set) and an empty result is exactly a bag.
|
|
743
|
+
* (A duplicate-producing body usually fails the set contract earlier, at create-fill.)
|
|
744
|
+
* - **non-deterministic** body (any `random()`/`now()`/volatile UDF anywhere in the plan)
|
|
745
|
+
* without `pragma nondeterministic_schema` — no maintenance could keep it equal to its
|
|
746
|
+
* plain view (mirrors the per-arm determinism rejects and the DDL determinism gate);
|
|
747
|
+
* - body with **no relational output** (degenerate);
|
|
748
|
+
* - **size**: full-rebuild is the only sound strategy *and* the **largest** participating
|
|
749
|
+
* source exceeds the `materialized_view_rebuild_row_threshold` option
|
|
750
|
+
* ({@link isFullRebuildPathological}) — every DML write would re-scan that source.
|
|
751
|
+
* `0` disables the size reject (accept any size).
|
|
752
|
+
*/
|
|
753
|
+
export function buildFullRebuildPlan(ctx, mv, analyzed) {
|
|
754
|
+
const db = ctx;
|
|
755
|
+
// Optimize the whole body ONCE — read-side MV rewrite suppressed so it reads its
|
|
756
|
+
// sources, not the backing it populates — then derive the body's key + determinism
|
|
757
|
+
// from, and compile its scheduler from, the SAME optimized plan.
|
|
758
|
+
const optimized = db.schemaManager.withSuppressedMaterializedViewRewrite(() => ctx.optimizer.optimize(analyzed, db));
|
|
759
|
+
const root = rootRelationalNode(optimized);
|
|
760
|
+
if (!root)
|
|
761
|
+
throw cannotMaterialize(mv.name, 'its body produced no relational output');
|
|
762
|
+
// Backing key = the body's provable unique key. A bag (no provable key — a key-dropping
|
|
763
|
+
// projection, a `union all` of overlapping inputs, …) has no row identity to key a
|
|
764
|
+
// materialization on, so it must be a set. An all-columns pseudo-key is admitted only
|
|
765
|
+
// when the body is provably a set (`keysOf` gates it on `isSet`); a bag with an
|
|
766
|
+
// all-columns "key" still resolves to empty here and rejects, else duplicates would
|
|
767
|
+
// collide on the backing key.
|
|
768
|
+
//
|
|
769
|
+
// One carve-out: a keyless body whose source key survives through value-preserving
|
|
770
|
+
// passthrough lineage (the parallel-migration collation-weakening shape) is keyed on
|
|
771
|
+
// the COARSENED lineage key instead of rejected — the same `deriveCoarsenedBackingKey`
|
|
772
|
+
// derivation `deriveBackingShape` keyed the backing with, over the same fully-optimized
|
|
773
|
+
// body, so the two agree by construction. Colliding rows then last-write-win under the
|
|
774
|
+
// floor's collation-keyed `replace-all` diff (docs/materialized-views.md § Coarsened
|
|
775
|
+
// backing keys); the create emitter owns the key-coarsening warning.
|
|
776
|
+
if (keysOf(root).length === 0 && deriveCoarsenedBackingKey(root) === undefined) {
|
|
777
|
+
throw cannotMaterialize(mv.name, 'its body has no provable unique key — it is a bag (e.g. a key-dropping '
|
|
778
|
+
+ 'projection or a `union all` of overlapping inputs), so it must be a set');
|
|
779
|
+
}
|
|
780
|
+
// Whole-body determinism: a non-deterministic body can never be kept equal to its
|
|
781
|
+
// plain view by any maintenance, so it is a hard reject unless the schema-determinism
|
|
782
|
+
// gate is lifted. Mirrors the per-arm determinism rejects (and the DDL gate).
|
|
783
|
+
if (!db.options.getBooleanOption('nondeterministic_schema')) {
|
|
784
|
+
const nonDet = findNonDeterministic(analyzed);
|
|
785
|
+
if (nonDet) {
|
|
786
|
+
throw cannotMaterialize(mv.name, `its body is non-deterministic (${nonDet}); a materialized view body must be `
|
|
787
|
+
+ 'reproducible to stay equal to its plain view (set `pragma nondeterministic_schema` to override)');
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// Every source the body reads (set-op legs, every join source, …) so a write to any of
|
|
791
|
+
// them triggers a rebuild. Collected from the (pre-physical) analyzed plan, where every
|
|
792
|
+
// source is a bare `TableReferenceNode` — the optimized plan may have wrapped them in
|
|
793
|
+
// physical access nodes.
|
|
794
|
+
const tableRefs = [...collectTableRefs(analyzed).values()];
|
|
795
|
+
const sourceBases = [...new Set(tableRefs.map(ref => `${ref.tableSchema.schemaName}.${ref.tableSchema.name}`.toLowerCase()))];
|
|
796
|
+
if (sourceBases.length === 0)
|
|
797
|
+
throw cannotMaterialize(mv.name, 'its body reads no source table');
|
|
798
|
+
const backing = ctx._findTable(mv.name, mv.schemaName);
|
|
799
|
+
if (!backing) {
|
|
800
|
+
throw new QuereusError(`Internal error: backing table '${mv.name}' for materialized view '${mv.name}' not found`, StatusCode.INTERNAL);
|
|
801
|
+
}
|
|
802
|
+
// ── Cost gate + size reject ──
|
|
803
|
+
// Full-rebuild is the floor: an EMPTY sound set resolves to it (`selectMaintenanceStrategy`).
|
|
804
|
+
// Cost the rebuild against the LARGEST participating source — every write re-evaluates the
|
|
805
|
+
// whole body, so the largest source it scans governs whether the per-write rebuild is
|
|
806
|
+
// pathological (e.g. a tiny driving table joined to a huge lookup gates on the lookup).
|
|
807
|
+
// Re-resolve each source's CURRENT schema (the analyzed plan node may carry a pre-`analyze`
|
|
808
|
+
// snapshot whose `statistics` predates the latest counts) so the size gate reflects the
|
|
809
|
+
// live source size at create time.
|
|
810
|
+
const statsProvider = ctx.optimizer.getStats();
|
|
811
|
+
let largestSchema = tableRefs[0].tableSchema;
|
|
812
|
+
let largestRows = -1;
|
|
813
|
+
for (const ref of tableRefs) {
|
|
814
|
+
const live = liveSourceSchema(ctx, ref);
|
|
815
|
+
const rows = statsProvider.tableRows(live) ?? DEFAULT_SOURCE_ROWS;
|
|
816
|
+
if (rows > largestRows) {
|
|
817
|
+
largestRows = rows;
|
|
818
|
+
largestSchema = live;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const sourceStats = estimateMaintenanceStats(ctx, largestSchema, backing.columns.length, /*hasPredicate*/ false);
|
|
822
|
+
// Size reject: full-rebuild is the only sound strategy here, so a source past the
|
|
823
|
+
// configurable threshold makes every write pathological. `0` disables the reject.
|
|
824
|
+
const rebuildThreshold = db.options.getNumberOption('materialized_view_rebuild_row_threshold');
|
|
825
|
+
if (isFullRebuildPathological(sourceStats, rebuildThreshold)) {
|
|
826
|
+
const largestBase = `${largestSchema.schemaName}.${largestSchema.name}`.toLowerCase();
|
|
827
|
+
throw cannotMaterialize(mv.name, `its only sound maintenance strategy is a full body rebuild, but its largest source '${largestBase}' has `
|
|
828
|
+
+ `~${sourceStats.tableRows} rows, over the materialized_view_rebuild_row_threshold (${rebuildThreshold}) — `
|
|
829
|
+
+ `every write would re-scan it. Raise or disable the threshold `
|
|
830
|
+
+ `(\`pragma materialized_view_rebuild_row_threshold = 0\`)`);
|
|
831
|
+
}
|
|
832
|
+
// Compile the whole optimized body once into a reusable scheduler (no key filter).
|
|
833
|
+
const bodyScheduler = new Scheduler(emitPlanNode(optimized, new EmissionContext(db)));
|
|
834
|
+
const chosenStrategy = selectMaintenanceStrategy([], Math.max(1, sourceStats.tableRows * 0.01), sourceStats);
|
|
835
|
+
if (chosenStrategy !== 'full-rebuild') {
|
|
836
|
+
throw new QuereusError(`Internal error: cost gate selected '${chosenStrategy}' for the full-rebuild floor of materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
kind: 'full-rebuild',
|
|
840
|
+
mv,
|
|
841
|
+
sourceBase: sourceBases[0],
|
|
842
|
+
backingSchema: mv.schemaName,
|
|
843
|
+
backingTableName: mv.name,
|
|
844
|
+
chosenStrategy,
|
|
845
|
+
sourceStats,
|
|
846
|
+
bodyScheduler,
|
|
847
|
+
sourceBases,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Compile the key-filtered residual for a binding into a reusable {@link Scheduler}:
|
|
852
|
+
* the analyzed body with a key-equality filter injected on `T`'s `TableReferenceNode`
|
|
853
|
+
* (parameterized `${paramPrefix}0…`), then optimized + emitted. Mirrors the assertion
|
|
854
|
+
* evaluator's residual compilation (`database-assertions.ts`) so the two cannot drift.
|
|
855
|
+
* Returns `null` if `injectKeyFilter` could not target `T` (the arm builder then falls
|
|
856
|
+
* through to the full-rebuild floor).
|
|
857
|
+
*/
|
|
858
|
+
export function compileResidual(ctx, analyzed, relKey, bindColumns, paramPrefix) {
|
|
859
|
+
const db = ctx;
|
|
860
|
+
const rewritten = injectKeyFilter(analyzed, relKey, bindColumns, paramPrefix);
|
|
861
|
+
if (rewritten === analyzed)
|
|
862
|
+
return null; // could not parameterize the residual → floor
|
|
863
|
+
// Suppress the read-side rewrite: the residual is the MV's own body (+ a key
|
|
864
|
+
// filter) compiled to maintain its backing, so it must stay over the source.
|
|
865
|
+
const optimized = db.schemaManager.withSuppressedMaterializedViewRewrite(() => ctx.optimizer.optimize(rewritten, db));
|
|
866
|
+
const instruction = emitPlanNode(optimized, new EmissionContext(db));
|
|
867
|
+
return new Scheduler(instruction);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Build a `'prefix-delete'` plan for a single-source lateral-TVF fan-out body
|
|
871
|
+
* (`select T.pk…, …, f.* from T cross join lateral tvf(<args over T>) f`), or return
|
|
872
|
+
* `null` on a shape mismatch (the caller falls through to the full-rebuild floor). The
|
|
873
|
+
* backing PK is the composite product key `(T.pk ∪ tvf-key)` that `keysOf` advertises
|
|
874
|
+
* through the lateral join; the base PK is its leading prefix. See {@link PrefixDeletePlan}
|
|
875
|
+
* and `docs/incremental-maintenance.md` § prefix-delete.
|
|
876
|
+
*
|
|
877
|
+
* Soundness gates (a mismatch on any returns `null` → floor): exactly one lateral TVF and
|
|
878
|
+
* one join (no nested/multi TVF, no aggregate over the fan-out); the TVF advertises a
|
|
879
|
+
* per-call key; the base PK projected and forming the **leading prefix** of the backing PK
|
|
880
|
+
* with a non-empty TVF-key tail (so each base row's fan-out rows are individually
|
|
881
|
+
* addressable and a by-prefix delete selects exactly one base row's slice). An `order by`
|
|
882
|
+
* over the fan-out that reorders the composite key so the base PK no longer leads is a
|
|
883
|
+
* `null` fall-through (the floor maintains it wholesale). The body's WHERE, if any, is part
|
|
884
|
+
* of the residual (so an out-of-scope base row fans out to zero rows), exactly as in the
|
|
885
|
+
* aggregate arm. A **non-deterministic** TVF / argument is the one hard reject (its
|
|
886
|
+
* arm-specific determinism diagnostic must survive).
|
|
887
|
+
*/
|
|
888
|
+
export function buildLateralTvfPrefixDeletePlan(ctx, mv, analyzed, tableRef, sourceBase) {
|
|
889
|
+
// Exactly one lateral TVF and one join. A second base table is already excluded by
|
|
890
|
+
// the single-source check upstream; this falls to the floor for a second TVF / chained
|
|
891
|
+
// lateral join (`t join lateral tvf1 join lateral tvf2`).
|
|
892
|
+
if (countNodeType(analyzed, PlanNodeType.TableFunctionCall) !== 1)
|
|
893
|
+
return null;
|
|
894
|
+
if (countJoins(analyzed) !== 1)
|
|
895
|
+
return null;
|
|
896
|
+
// An aggregate over the fan-out is a different shape → floor (the TVF route is taken
|
|
897
|
+
// before the aggregate route, so an `... group by` over a lateral TVF lands here).
|
|
898
|
+
if (findAggregate(analyzed))
|
|
899
|
+
return null;
|
|
900
|
+
// Determinism: a residual must reproduce exactly what `select <body>` returns, so a
|
|
901
|
+
// volatile TVF (or a volatile argument expression) is a HARD reject.
|
|
902
|
+
const tvf = findTableFunctionCall(analyzed);
|
|
903
|
+
if (!tvf) {
|
|
904
|
+
// Unreachable — countNodeType(...) === 1 above guarantees one exists.
|
|
905
|
+
throw new QuereusError(`Internal error: lateral TVF node not found for materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
906
|
+
}
|
|
907
|
+
if (tvf.physical.deterministic === false) {
|
|
908
|
+
throw cannotMaterialize(mv.name, `it fans out through a non-deterministic table-valued function '${tvf.functionName}' (a row-time fan-out must be reproducible from the base row)`);
|
|
909
|
+
}
|
|
910
|
+
for (const operand of tvf.operands) {
|
|
911
|
+
const det = checkDeterministic(operand);
|
|
912
|
+
if (!det.valid)
|
|
913
|
+
throw cannotMaterialize(mv.name, `it passes a non-deterministic argument (${det.expression}) to the lateral table-valued function`);
|
|
914
|
+
}
|
|
915
|
+
// The lateral TVF must advertise a per-call key, so the composite product key is a
|
|
916
|
+
// real column key `(base PK ∪ TVF key)` rather than the all-columns / `isSet`
|
|
917
|
+
// fallback. Without one the fan-out rows are not individually addressable by a proper
|
|
918
|
+
// key — the by-prefix delete + keyed upsert would be unsound — so fall to the floor.
|
|
919
|
+
// `getType().keys` carries the validated advertisement (an out-of-range key
|
|
920
|
+
// advertisement is dropped, leaving this empty), so it is the authoritative
|
|
921
|
+
// "did the TVF advertise a usable key" signal.
|
|
922
|
+
if (tvf.getType().keys.length === 0)
|
|
923
|
+
return null;
|
|
924
|
+
// Base T's PK source columns.
|
|
925
|
+
const sourceSchema = tableRef.tableSchema;
|
|
926
|
+
const sourcePkCols = sourceSchema.primaryKeyDefinition.map(d => d.index);
|
|
927
|
+
if (sourcePkCols.length === 0)
|
|
928
|
+
return null;
|
|
929
|
+
// Backing table + its physical PK (the composite product key).
|
|
930
|
+
const backing = ctx._findTable(mv.name, mv.schemaName);
|
|
931
|
+
if (!backing) {
|
|
932
|
+
throw new QuereusError(`Internal error: backing table '${mv.name}' for materialized view '${mv.name}' not found`, StatusCode.INTERNAL);
|
|
933
|
+
}
|
|
934
|
+
const backingPkDefinition = resolveBackingPkColumns(backing, ctx.getCollationResolver());
|
|
935
|
+
// Map each output attribute to a base-T source column (or `undefined` for a TVF
|
|
936
|
+
// output column). T's attributes pass through the join unchanged, so a base-PK
|
|
937
|
+
// output column resolves to a T column while a TVF-key output column does not.
|
|
938
|
+
const sourceAttrToCol = new Map();
|
|
939
|
+
tableRef.getAttributes().forEach((a, i) => sourceAttrToCol.set(a.id, i));
|
|
940
|
+
const producingByAttrId = collectProducingExprs(analyzed);
|
|
941
|
+
const rootAttrs = relationalAttributes(analyzed);
|
|
942
|
+
if (!rootAttrs)
|
|
943
|
+
return null;
|
|
944
|
+
// Prefix soundness: the LEADING `basePrefixLen` backing-PK columns must each
|
|
945
|
+
// project (transitively) a distinct base-T PK column, their set must equal the base
|
|
946
|
+
// PK, and there must be a non-empty TVF-key tail. So the base PK is the leading
|
|
947
|
+
// prefix of the composite product key and the by-prefix delete selects exactly one
|
|
948
|
+
// base row's fan-out. A composite key that did not form this way falls to the floor.
|
|
949
|
+
const basePrefixLen = sourcePkCols.length;
|
|
950
|
+
if (backingPkDefinition.length <= basePrefixLen)
|
|
951
|
+
return null;
|
|
952
|
+
const basePkSet = new Set(sourcePkCols);
|
|
953
|
+
const leadingSourceCols = new Set();
|
|
954
|
+
const backingPrefixSourceCols = [];
|
|
955
|
+
for (let i = 0; i < basePrefixLen; i++) {
|
|
956
|
+
const d = backingPkDefinition[i];
|
|
957
|
+
const attr = rootAttrs[d.index];
|
|
958
|
+
const sc = attr ? resolveTransitiveSourceCol(attr.id, sourceAttrToCol, producingByAttrId) : undefined;
|
|
959
|
+
if (sc === undefined || !basePkSet.has(sc))
|
|
960
|
+
return null; // base PK not the leading prefix → floor
|
|
961
|
+
// Soundness precondition for the binary prefix scan (the property
|
|
962
|
+
// `prefix-delete-noncase-collation-regression-test` locks in): the backing base-PK
|
|
963
|
+
// column MUST inherit the source PK column's collation. The btree orders this prefix
|
|
964
|
+
// by `d.collation`, but the keyed diff's existing-slice read (`scanEffective` with
|
|
965
|
+
// the base prefix, in `applyPrefixDelete`) early-terminates the prefix scan on a
|
|
966
|
+
// BINARY compare (scan-layer.ts) — sound ONLY because source-PK uniqueness under that
|
|
967
|
+
// collation collapses each collation class to a single binary value, so a base row's
|
|
968
|
+
// fan-out rows are binary-homogeneous and contiguous. A backing collation MORE
|
|
969
|
+
// permissive than the source's would let collation-equal/binary-different base rows
|
|
970
|
+
// interleave and break that. The backing column derives its collation from the body
|
|
971
|
+
// relation's type (deriveBackingShape), so a mismatch is an internal derivation bug —
|
|
972
|
+
// fail loud rather than register an unsound plan.
|
|
973
|
+
const backingColl = normalizeCollation(d.collation);
|
|
974
|
+
const sourceColl = normalizeCollation(sourceSchema.columns[sc]?.collation);
|
|
975
|
+
if (backingColl !== sourceColl) {
|
|
976
|
+
throw new QuereusError(`Internal error: materialized view '${mv.name}' backing base-PK column `
|
|
977
|
+
+ `'${backing.columns[d.index]?.name ?? d.index}' has collation '${backingColl}' but its source `
|
|
978
|
+
+ `primary-key column '${sourceSchema.columns[sc]?.name ?? sc}' has collation '${sourceColl}'; `
|
|
979
|
+
+ `the prefix-delete arm's binary prefix scan requires the backing base-PK column to inherit the `
|
|
980
|
+
+ `source PK collation (see scan-layer.ts early-termination)`, StatusCode.INTERNAL);
|
|
981
|
+
}
|
|
982
|
+
leadingSourceCols.add(sc);
|
|
983
|
+
backingPrefixSourceCols.push(sc);
|
|
984
|
+
}
|
|
985
|
+
if (leadingSourceCols.size !== basePkSet.size)
|
|
986
|
+
return null; // prefix does not cover the base PK → floor
|
|
987
|
+
// The TVF-key tail must NOT re-use a base-PK column — else the fan-out rows would
|
|
988
|
+
// not be distinguished and the "key" would be base-only (defensive: the product key
|
|
989
|
+
// places the TVF key, a distinct relation's columns, in the tail). Otherwise → floor.
|
|
990
|
+
for (let i = basePrefixLen; i < backingPkDefinition.length; i++) {
|
|
991
|
+
const d = backingPkDefinition[i];
|
|
992
|
+
const attr = rootAttrs[d.index];
|
|
993
|
+
const sc = attr ? resolveTransitiveSourceCol(attr.id, sourceAttrToCol, producingByAttrId) : undefined;
|
|
994
|
+
if (sc !== undefined && basePkSet.has(sc))
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
// Compile + cache the base-PK-keyed residual once (the body with `T.pk = :pk0 AND …`
|
|
998
|
+
// injected on T). Re-run per affected base key against the live transaction; it
|
|
999
|
+
// re-runs the lateral join + TVF for that single base row, fanning out to N rows.
|
|
1000
|
+
const relKey = `${sourceBase}#${tableRef.id ?? 'unknown'}`;
|
|
1001
|
+
const residualScheduler = compileResidual(ctx, analyzed, relKey, sourcePkCols, 'pk');
|
|
1002
|
+
if (!residualScheduler)
|
|
1003
|
+
return null; // could not parameterize the residual → floor
|
|
1004
|
+
// ── Cost gate ──
|
|
1005
|
+
// The fan-out residual shares the residual-recompute cost shape (a key-filtered
|
|
1006
|
+
// re-execution of the body); the fan-out factor (rows per base key) is not known at
|
|
1007
|
+
// create, so we cost it as a residual and record the choice for substrate parity.
|
|
1008
|
+
// The synchronous reject-at-create / degrade-to-rebuild machinery stays dormant, as
|
|
1009
|
+
// it does for the other arms (a TVF whose fan-out is pathological is not detectable
|
|
1010
|
+
// without fan-out stats — deferred with the fanning-keyed-join follow-up).
|
|
1011
|
+
const soundStrategies = ['residual-recompute'];
|
|
1012
|
+
const hasPredicate = mv.derivation.selectAst.type === 'select' && mv.derivation.selectAst.where !== undefined;
|
|
1013
|
+
const sourceStats = estimateMaintenanceStats(ctx, sourceSchema, backing.columns.length, hasPredicate);
|
|
1014
|
+
const estimatedChangeCardinality = Math.max(1, sourceStats.tableRows * 0.01);
|
|
1015
|
+
const chosenStrategy = selectMaintenanceStrategy(soundStrategies, estimatedChangeCardinality, sourceStats);
|
|
1016
|
+
if (chosenStrategy !== 'residual-recompute') {
|
|
1017
|
+
throw new QuereusError(`Internal error: cost gate selected unwired strategy '${chosenStrategy}' for materialized view '${mv.name}'`, StatusCode.INTERNAL);
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
kind: 'prefix-delete',
|
|
1021
|
+
mv,
|
|
1022
|
+
sourceBase,
|
|
1023
|
+
backingSchema: mv.schemaName,
|
|
1024
|
+
backingTableName: mv.name,
|
|
1025
|
+
chosenStrategy,
|
|
1026
|
+
sourceStats,
|
|
1027
|
+
binding: { kind: 'row', keyColumns: [...sourcePkCols] },
|
|
1028
|
+
degradeToRebuild: false,
|
|
1029
|
+
residualScheduler,
|
|
1030
|
+
bindParamPrefix: 'pk',
|
|
1031
|
+
bindColumns: sourcePkCols,
|
|
1032
|
+
backingPkDefinition,
|
|
1033
|
+
basePrefixLength: basePrefixLen,
|
|
1034
|
+
backingPrefixSourceCols,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Assemble {@link MaintenanceSourceStats} for the cost gate from the optimizer's
|
|
1039
|
+
* StatsProvider and tuning. `tableRows` / `distinctGroupsEstimate` come from the
|
|
1040
|
+
* provider (heuristic defaults when absent); `forwardBodyCost` is estimated from the
|
|
1041
|
+
* forward cost helpers (a scan + optional filter + projection of the source — the
|
|
1042
|
+
* covering-index body shape); `fallbackRatio` carries the detection kernel's
|
|
1043
|
+
* `deltaPerRowFallbackRatio` for the no-stats residual path.
|
|
1044
|
+
*/
|
|
1045
|
+
/**
|
|
1046
|
+
* The CURRENT `TableSchema` of a source `TableReferenceNode`, re-resolved through the
|
|
1047
|
+
* schema manager. A plan node captures the schema as of plan-build; a later `analyze`
|
|
1048
|
+
* replaces the catalog entry with one carrying fresh `statistics`, so the stale captured
|
|
1049
|
+
* schema would report pre-`analyze` row counts. Re-resolving keeps the floor's size gate
|
|
1050
|
+
* on the live source size. Falls back to the node's captured schema if the name no longer
|
|
1051
|
+
* resolves (it always should — the body planned).
|
|
1052
|
+
*/
|
|
1053
|
+
export function liveSourceSchema(ctx, ref) {
|
|
1054
|
+
const captured = ref.tableSchema;
|
|
1055
|
+
return ctx._findTable(captured.name, captured.schemaName) ?? captured;
|
|
1056
|
+
}
|
|
1057
|
+
export function estimateMaintenanceStats(ctx, sourceSchema, projectionCount, hasPredicate) {
|
|
1058
|
+
const optimizer = ctx.optimizer;
|
|
1059
|
+
const statsProvider = optimizer.getStats();
|
|
1060
|
+
const tableRows = statsProvider.tableRows(sourceSchema) ?? DEFAULT_SOURCE_ROWS;
|
|
1061
|
+
const forwardBodyCost = seqScanCost(tableRows)
|
|
1062
|
+
+ (hasPredicate ? filterCost(tableRows) : 0)
|
|
1063
|
+
+ projectCost(tableRows, projectionCount);
|
|
1064
|
+
const stats = {
|
|
1065
|
+
tableRows,
|
|
1066
|
+
forwardBodyCost,
|
|
1067
|
+
fallbackRatio: optimizer.tuning.deltaPerRowFallbackRatio,
|
|
1068
|
+
};
|
|
1069
|
+
// `distinctValues` is an optional, per-column StatsProvider method. For the
|
|
1070
|
+
// covering-index shape the source PK is the grouping key; a single-column PK
|
|
1071
|
+
// yields a usable distinct-groups estimate (which only feeds the never-chosen-here
|
|
1072
|
+
// residual cost). Multi-column PKs leave it unset → residual takes the no-stats path.
|
|
1073
|
+
const pkDef = sourceSchema.primaryKeyDefinition;
|
|
1074
|
+
if (pkDef.length === 1 && statsProvider.distinctValues) {
|
|
1075
|
+
const pkColName = sourceSchema.columns[pkDef[0].index]?.name;
|
|
1076
|
+
if (pkColName !== undefined) {
|
|
1077
|
+
const distinct = statsProvider.distinctValues(sourceSchema, pkColName);
|
|
1078
|
+
if (distinct !== undefined)
|
|
1079
|
+
stats.distinctGroupsEstimate = distinct;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return stats;
|
|
1083
|
+
}
|
|
1084
|
+
//# sourceMappingURL=database-materialized-views-plan-builders.js.map
|