@quereus/quereus 4.3.1 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +3 -3
- package/dist/src/core/database-external-changes.d.ts.map +1 -1
- package/dist/src/core/database-external-changes.js +13 -10
- package/dist/src/core/database-external-changes.js.map +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 +422 -0
- package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -0
- package/dist/src/core/database-materialized-views-apply.js +929 -0
- package/dist/src/core/database-materialized-views-apply.js.map +1 -0
- package/dist/src/core/database-materialized-views-plan-builders.d.ts +267 -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 +1423 -0
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -0
- package/dist/src/core/database-materialized-views-plans.d.ts +641 -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 +108 -860
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +212 -2165
- 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 +270 -50
- 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/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +127 -21
- package/dist/src/func/builtins/aggregate.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/func/registration.d.ts +6 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +16 -0
- package/dist/src/func/registration.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/equi-correlation.d.ts +26 -0
- package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
- package/dist/src/planner/analysis/equi-correlation.js +62 -0
- package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +54 -18
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +86 -42
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +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/scalar-subqueries.d.ts +28 -0
- package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
- package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
- package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
- package/dist/src/planner/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.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +18 -4
- 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/building/with.d.ts.map +1 -1
- package/dist/src/planner/building/with.js +5 -8
- package/dist/src/planner/building/with.js.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.d.ts +34 -2
- package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
- package/dist/src/planner/cache/materialization-advisory.js +185 -42
- package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
- package/dist/src/planner/cache/reference-graph.d.ts +14 -5
- package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
- package/dist/src/planner/cache/reference-graph.js +31 -39
- package/dist/src/planner/cache/reference-graph.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +30 -5
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +26 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/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 +19 -2
- package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cache-node.js +43 -3
- package/dist/src/planner/nodes/cache-node.js.map +1 -1
- package/dist/src/planner/nodes/cte-node.d.ts +16 -1
- package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cte-node.js +16 -3
- 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 +30 -1
- package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/recursive-cte-node.js +38 -5
- 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 +1014 -895
- 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-cte-optimization.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
- package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +7 -0
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +14 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.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.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +55 -56
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
- package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-cse.js +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +29 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +66 -0
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
- 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-fanout-lookup-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
- package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
- package/dist/src/planner/rules/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/rules/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +623 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
- package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
- package/dist/src/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/cache/shared-cache.d.ts +7 -0
- package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
- package/dist/src/runtime/cache/shared-cache.js +34 -1
- package/dist/src/runtime/cache/shared-cache.js.map +1 -1
- package/dist/src/runtime/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 +2 -3
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +23 -28
- 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 +7 -3
- package/dist/src/runtime/emit/cache.d.ts.map +1 -1
- package/dist/src/runtime/emit/cache.js +25 -7
- 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 +45 -21
- 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 +138 -74
- 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 +128 -16
- 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 +61 -2
- 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 +32 -4
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +88 -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 +102 -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/function.d.ts +79 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/lens-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 +59 -24
- 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
|
@@ -3,13 +3,13 @@ import { PlanNodeType } from './nodes/plan-node-type.js';
|
|
|
3
3
|
import { DEFAULT_TUNING } from './optimizer-tuning.js';
|
|
4
4
|
// Re-export for convenience
|
|
5
5
|
export { DEFAULT_TUNING };
|
|
6
|
-
import {
|
|
7
|
-
import { tracePhaseStart, tracePhaseEnd, traceNodeStart, traceNodeEnd } from './framework/trace.js';
|
|
6
|
+
import { tracePhaseStart, tracePhaseEnd } from './framework/trace.js';
|
|
8
7
|
import { CatalogStatsProvider } from './stats/catalog-stats.js';
|
|
9
8
|
import { createOptContext } from './framework/context.js';
|
|
10
9
|
import { PassManager, PassId } from './framework/pass.js';
|
|
10
|
+
import { quereusError } from '../common/errors.js';
|
|
11
|
+
import { StatusCode } from '../common/types.js';
|
|
11
12
|
// Phase 2 rules
|
|
12
|
-
import { ruleMaterializationAdvisory } from './rules/cache/rule-materialization-advisory.js';
|
|
13
13
|
import { ruleMaterializedViewRewrite } from './rules/cache/rule-materialized-view-rewrite.js';
|
|
14
14
|
// Phase 1.5 rules
|
|
15
15
|
import { ruleSelectAccessPath } from './rules/access/rule-select-access-path.js';
|
|
@@ -23,6 +23,7 @@ import { ruleAggregatePredicatePushdown } from './rules/predicate/rule-aggregate
|
|
|
23
23
|
import { ruleFilterMerge } from './rules/predicate/rule-filter-merge.js';
|
|
24
24
|
import { rulePredicateInferenceEquivalence } from './rules/predicate/rule-predicate-inference-equivalence.js';
|
|
25
25
|
import { ruleSargableRangeRewrite } from './rules/predicate/rule-sargable-range-rewrite.js';
|
|
26
|
+
import { ruleFilterSelectivity } from './rules/predicate/rule-filter-selectivity.js';
|
|
26
27
|
import { ruleJoinKeyInference } from './rules/join/rule-join-key-inference.js';
|
|
27
28
|
import { ruleJoinGreedyCommute } from './rules/join/rule-join-greedy-commute.js';
|
|
28
29
|
import { ruleJoinElimination, ruleJoinEliminationUnderAggregate } from './rules/join/rule-join-elimination.js';
|
|
@@ -47,8 +48,11 @@ import { ruleMonotonicWindow } from './rules/window/rule-monotonic-window.js';
|
|
|
47
48
|
// Constraint rules removed - now handled in builders for correctness
|
|
48
49
|
import { ruleCteOptimization } from './rules/cache/rule-cte-optimization.js';
|
|
49
50
|
import { ruleMutatingSubqueryCache } from './rules/cache/rule-mutating-subquery-cache.js';
|
|
51
|
+
import { ruleNestedLoopRightCache } from './rules/cache/rule-nested-loop-right-cache.js';
|
|
50
52
|
import { ruleInSubqueryCache } from './rules/cache/rule-in-subquery-cache.js';
|
|
51
|
-
import {
|
|
53
|
+
import { ruleScalarSubqueryCache } from './rules/cache/rule-scalar-subquery-cache.js';
|
|
54
|
+
import { ruleSubqueryDecorrelation, ruleExistsInSelectDecorrelation } from './rules/subquery/rule-subquery-decorrelation.js';
|
|
55
|
+
import { ruleScalarAggDecorrelation, ruleScalarAggDecorrelationAggregate, ruleScalarAggDecorrelationFilter, ruleScalarAggDecorrelationSort } from './rules/subquery/rule-scalar-agg-decorrelation.js';
|
|
52
56
|
import { ruleAntiJoinFkEmpty } from './rules/subquery/rule-anti-join-fk-empty.js';
|
|
53
57
|
import { ruleSemiJoinFkTrivial } from './rules/subquery/rule-semi-join-fk-trivial.js';
|
|
54
58
|
import { ruleFilterFoldEmpty, ruleProjectFoldEmpty, ruleSortFoldEmpty, ruleLimitOffsetFoldEmpty, ruleDistinctFoldEmpty, ruleJoinFoldEmpty, } from './rules/predicate/rule-empty-relation-folding.js';
|
|
@@ -59,6 +63,1008 @@ import { ruleScalarCSE } from './rules/cache/rule-scalar-cse.js';
|
|
|
59
63
|
// Phase 3 rules
|
|
60
64
|
import { validatePhysicalTree } from './validation/plan-validator.js';
|
|
61
65
|
const log = createLogger('optimizer');
|
|
66
|
+
/** The eight relational node types `grow-retrieve` fans across (order preserved). */
|
|
67
|
+
const GROW_RETRIEVE_NODE_TYPES = [
|
|
68
|
+
PlanNodeType.Filter,
|
|
69
|
+
PlanNodeType.Project,
|
|
70
|
+
PlanNodeType.Sort,
|
|
71
|
+
PlanNodeType.LimitOffset,
|
|
72
|
+
PlanNodeType.Aggregate,
|
|
73
|
+
PlanNodeType.Distinct,
|
|
74
|
+
PlanNodeType.Join,
|
|
75
|
+
PlanNodeType.Window,
|
|
76
|
+
];
|
|
77
|
+
/** Physical access leaves `monotonic-range-access` annotates (order preserved). */
|
|
78
|
+
const RANGE_ACCESS_LEAF_TYPES = [
|
|
79
|
+
PlanNodeType.IndexScan,
|
|
80
|
+
PlanNodeType.IndexSeek,
|
|
81
|
+
PlanNodeType.SeqScan,
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* The optimizer's rule manifest — the single source of truth for which rule runs
|
|
85
|
+
* in which pass and in what order. Array order = registration order = execution
|
|
86
|
+
* order (rule application iterates `pass.rules` in push order; nothing sorts by a
|
|
87
|
+
* numeric priority — that field is gone, see ticket `planner-remove-priority`).
|
|
88
|
+
*
|
|
89
|
+
* Entries interleave passes exactly as the historical imperative registration did;
|
|
90
|
+
* `registerRulesToPasses` walks this array top-to-bottom and appends each handle to
|
|
91
|
+
* its pass, so the per-pass order is a stable subsequence of this array. In
|
|
92
|
+
* particular `lateral-top1-asof` (Structural) sits AFTER the three Physical entries
|
|
93
|
+
* here because that was its historical registration position, making it the last
|
|
94
|
+
* rule in the Structural pass.
|
|
95
|
+
*/
|
|
96
|
+
const RULE_MANIFEST = [
|
|
97
|
+
// ── Structural pass (top-down) ──────────────────────────────────────────
|
|
98
|
+
// Materialized-view query rewrite (read side). Registered FIRST in the
|
|
99
|
+
// Structural pass so it fires on the pristine `Project(Filter?(Retrieve(
|
|
100
|
+
// TableReference)))` — before grow-retrieve / predicate-pushdown reposition
|
|
101
|
+
// the Filter and before the Physical pass absorbs a predicate into a range
|
|
102
|
+
// scan — where the matcher can read the fragment's WHERE off the live plan.
|
|
103
|
+
// Pass rules fire in REGISTRATION order (= manifest array order), so placement
|
|
104
|
+
// here is what guarantees first-fire. Logical→logical: the substituted
|
|
105
|
+
// maintained-table TableReference then flows through normal physical access
|
|
106
|
+
// selection, so `query_plan()` shows an ordinary scan of the MV's own table.
|
|
107
|
+
{
|
|
108
|
+
pass: PassId.Structural,
|
|
109
|
+
id: 'materialized-view-rewrite',
|
|
110
|
+
nodeType: PlanNodeType.Project,
|
|
111
|
+
phase: 'rewrite',
|
|
112
|
+
fn: ruleMaterializedViewRewrite,
|
|
113
|
+
// Replaces a read-only scan-project-filter fragment with a provably
|
|
114
|
+
// row-equivalent backing scan; the dropped base-scan subtree is pure (the
|
|
115
|
+
// matcher admits only recognized predicates, no subqueries) and the
|
|
116
|
+
// replacement re-emits the fragment's identical output attribute ids.
|
|
117
|
+
sideEffectMode: 'safe',
|
|
118
|
+
},
|
|
119
|
+
// Aggregate arm of the same rewrite (`mv-query-rewrite-aggregate-rollup`):
|
|
120
|
+
// recognizes a logical `Aggregate(Filter?(scan))` answered from a grouped MV
|
|
121
|
+
// (exact-key direct scan or superset-key rollup re-aggregation). Registered as
|
|
122
|
+
// a SECOND handle because pass rules fire only on their `nodeType` and are
|
|
123
|
+
// deduped by id — so the aggregate arm needs the `Aggregate` node type and a
|
|
124
|
+
// distinct id. It honors the canonical `materialized-view-rewrite` disable
|
|
125
|
+
// switch internally (see the rule), so existing rule-disable controls turn off
|
|
126
|
+
// both arms. Registered immediately after the Project arm so it likewise fires
|
|
127
|
+
// on the pristine fragment, before grow-retrieve / predicate-pushdown.
|
|
128
|
+
{
|
|
129
|
+
pass: PassId.Structural,
|
|
130
|
+
id: 'materialized-view-rewrite-aggregate',
|
|
131
|
+
nodeType: PlanNodeType.Aggregate,
|
|
132
|
+
phase: 'rewrite',
|
|
133
|
+
fn: ruleMaterializedViewRewrite,
|
|
134
|
+
sideEffectMode: 'safe',
|
|
135
|
+
},
|
|
136
|
+
// grow-retrieve for ALL relational node types (fan-out; ids
|
|
137
|
+
// `grow-retrieve-<nodeType>`). Slides operators down into a Retrieve boundary;
|
|
138
|
+
// the rule itself decides whether growth is possible.
|
|
139
|
+
{
|
|
140
|
+
pass: PassId.Structural,
|
|
141
|
+
id: 'grow-retrieve',
|
|
142
|
+
nodeType: GROW_RETRIEVE_NODE_TYPES,
|
|
143
|
+
phase: 'rewrite',
|
|
144
|
+
fn: ruleGrowRetrieve,
|
|
145
|
+
// Slides operators down into a Retrieve boundary, whose pipeline is
|
|
146
|
+
// always a read by construction (RetrieveNode is the vtab read entry).
|
|
147
|
+
sideEffectMode: 'safe',
|
|
148
|
+
},
|
|
149
|
+
// Join key inference (structural/characteristic).
|
|
150
|
+
{
|
|
151
|
+
pass: PassId.Structural,
|
|
152
|
+
id: 'join-key-inference',
|
|
153
|
+
nodeType: PlanNodeType.Join,
|
|
154
|
+
phase: 'rewrite',
|
|
155
|
+
fn: ruleJoinKeyInference,
|
|
156
|
+
// Diagnostic-only: never returns a transformed node.
|
|
157
|
+
sideEffectMode: 'safe',
|
|
158
|
+
},
|
|
159
|
+
// Greedy join commute: place smaller input on the left to improve
|
|
160
|
+
// nested-loop-like costs.
|
|
161
|
+
{
|
|
162
|
+
pass: PassId.Structural,
|
|
163
|
+
id: 'join-greedy-commute',
|
|
164
|
+
nodeType: PlanNodeType.Join,
|
|
165
|
+
phase: 'rewrite',
|
|
166
|
+
fn: ruleJoinGreedyCommute,
|
|
167
|
+
// Swaps left/right of an inner join — would reorder side-effect
|
|
168
|
+
// execution. The rule refuses when either side carries a write.
|
|
169
|
+
sideEffectMode: 'aware',
|
|
170
|
+
},
|
|
171
|
+
// DISTINCT elimination: remove redundant DISTINCT when source already has
|
|
172
|
+
// unique keys.
|
|
173
|
+
{
|
|
174
|
+
pass: PassId.Structural,
|
|
175
|
+
id: 'distinct-elimination',
|
|
176
|
+
nodeType: PlanNodeType.Distinct,
|
|
177
|
+
phase: 'rewrite',
|
|
178
|
+
fn: ruleDistinctElimination,
|
|
179
|
+
// Unwraps DISTINCT around its source; source survives verbatim and any
|
|
180
|
+
// writes inside it still execute the same number of times.
|
|
181
|
+
sideEffectMode: 'safe',
|
|
182
|
+
},
|
|
183
|
+
// Projection pruning: remove unused inner projections in Project-on-Project.
|
|
184
|
+
{
|
|
185
|
+
pass: PassId.Structural,
|
|
186
|
+
id: 'projection-pruning',
|
|
187
|
+
nodeType: PlanNodeType.Project,
|
|
188
|
+
phase: 'rewrite',
|
|
189
|
+
fn: ruleProjectionPruning,
|
|
190
|
+
// Drops unreferenced inner projections — refuses to drop any whose
|
|
191
|
+
// scalar expression carries a side effect.
|
|
192
|
+
sideEffectMode: 'aware',
|
|
193
|
+
},
|
|
194
|
+
// Lens auxiliary-access routing: route an outer-query predicate over an
|
|
195
|
+
// inlined lens view through an advertised auxiliary structure (nd-tree /
|
|
196
|
+
// vector / full-text) — an auxiliary seek ⋈ logical-key semi-join — instead
|
|
197
|
+
// of a residual filter over the full decomposition scan. Registered BEFORE
|
|
198
|
+
// predicate-pushdown so the matched predicate is still directly above the
|
|
199
|
+
// LensAuxiliaryAccess marker when this fires; within the top-down Structural
|
|
200
|
+
// pass, rules run in registration order, so placing this block ahead of
|
|
201
|
+
// pushdown is what guarantees first-fire on the Filter. No-ops on any Filter
|
|
202
|
+
// whose subtree has no marker (every non-lens / non-routable-lens view), so
|
|
203
|
+
// ordinary queries are untouched.
|
|
204
|
+
{
|
|
205
|
+
pass: PassId.Structural,
|
|
206
|
+
id: 'lens-auxiliary-access',
|
|
207
|
+
nodeType: PlanNodeType.Filter,
|
|
208
|
+
phase: 'rewrite',
|
|
209
|
+
fn: ruleLensAuxiliaryAccess,
|
|
210
|
+
// Replaces a Filter over the lens marker with a semi-join against an
|
|
211
|
+
// auxiliary scan; the logical body (left) survives verbatim and the
|
|
212
|
+
// auxiliary scan it adds is a fresh read-only table reference.
|
|
213
|
+
sideEffectMode: 'safe',
|
|
214
|
+
},
|
|
215
|
+
// Sargable range rewrite: turn `f(col) = c` (for monotone-lossy `f` with a
|
|
216
|
+
// bucketBounds-aware logical type) into `col >= L AND col < U` so the
|
|
217
|
+
// subsequent pushdown wave can carry the bare `col op literal` shape into
|
|
218
|
+
// Retrieve / access-path selection. Runs before aggregate-predicate-pushdown
|
|
219
|
+
// and predicate-pushdown so the rewritten conjuncts ride the same pushdown pass.
|
|
220
|
+
{
|
|
221
|
+
pass: PassId.Structural,
|
|
222
|
+
id: 'sargable-range-rewrite',
|
|
223
|
+
nodeType: PlanNodeType.Filter,
|
|
224
|
+
phase: 'rewrite',
|
|
225
|
+
fn: ruleSargableRangeRewrite,
|
|
226
|
+
// Rewrites a single scalar conjunct shape in place; no subtree moved.
|
|
227
|
+
sideEffectMode: 'safe',
|
|
228
|
+
},
|
|
229
|
+
// Aggregate-aware predicate pushdown: splits a Filter above an aggregate so
|
|
230
|
+
// conjuncts on GROUP-BY-determined columns land below the aggregate. Runs
|
|
231
|
+
// before the cross-node predicate-pushdown so anything we push below the
|
|
232
|
+
// aggregate can propagate further via that rule.
|
|
233
|
+
{
|
|
234
|
+
pass: PassId.Structural,
|
|
235
|
+
id: 'aggregate-predicate-pushdown',
|
|
236
|
+
nodeType: PlanNodeType.Filter,
|
|
237
|
+
phase: 'rewrite',
|
|
238
|
+
fn: ruleAggregatePredicatePushdown,
|
|
239
|
+
// Moves Filter conjuncts below an Aggregate, changing which rows reach
|
|
240
|
+
// the source subtree. Refuses when source has side effects.
|
|
241
|
+
sideEffectMode: 'aware',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
pass: PassId.Structural,
|
|
245
|
+
id: 'predicate-pushdown',
|
|
246
|
+
nodeType: PlanNodeType.Filter,
|
|
247
|
+
phase: 'rewrite',
|
|
248
|
+
fn: rulePredicatePushdown,
|
|
249
|
+
// Slides Filter past Sort/Distinct/Project/Alias/Retrieve, changing
|
|
250
|
+
// which rows reach the layer below — refuses when the immediate child
|
|
251
|
+
// subtree carries a write.
|
|
252
|
+
sideEffectMode: 'aware',
|
|
253
|
+
},
|
|
254
|
+
// Filter merge: combine adjacent Filter nodes into one AND-combined Filter.
|
|
255
|
+
{
|
|
256
|
+
pass: PassId.Structural,
|
|
257
|
+
id: 'filter-merge',
|
|
258
|
+
nodeType: PlanNodeType.Filter,
|
|
259
|
+
phase: 'rewrite',
|
|
260
|
+
fn: ruleFilterMerge,
|
|
261
|
+
// Merges two adjacent Filters into AND; the source subtree is untouched
|
|
262
|
+
// and only the order of predicate-clause evaluation changes (predicates
|
|
263
|
+
// are pure today; the audit gate that DML-in-expression-position needs
|
|
264
|
+
// is on rules that move or drop SUBTREES, not predicate ASTs).
|
|
265
|
+
sideEffectMode: 'safe',
|
|
266
|
+
},
|
|
267
|
+
// Scalar CSE: deduplicate common scalar expressions across Project + Filter +
|
|
268
|
+
// Sort chains.
|
|
269
|
+
{
|
|
270
|
+
pass: PassId.Structural,
|
|
271
|
+
id: 'scalar-cse',
|
|
272
|
+
nodeType: PlanNodeType.Project,
|
|
273
|
+
phase: 'rewrite',
|
|
274
|
+
fn: ruleScalarCSE,
|
|
275
|
+
// Deduplicates scalar expressions — would silently collapse N copies of
|
|
276
|
+
// a side-effect-bearing scalar into 1. The rule's collector skips any
|
|
277
|
+
// non-deterministic or side-effect-bearing expression.
|
|
278
|
+
sideEffectMode: 'aware',
|
|
279
|
+
},
|
|
280
|
+
// EC-driven predicate inference: materialize inferred equality predicates from
|
|
281
|
+
// the cross of predicate-derived constant bindings and the source's
|
|
282
|
+
// equivalence classes. Runs after predicate-pushdown and filter-merge so the
|
|
283
|
+
// predicate is already consolidated and pushdown won't immediately reabsorb the
|
|
284
|
+
// inferred conjuncts on this iteration; the Structural pass's fixed-point loop
|
|
285
|
+
// then re-runs pushdown on subsequent iterations so the new conjuncts can be
|
|
286
|
+
// carried to branch-level Retrieve pipelines.
|
|
287
|
+
{
|
|
288
|
+
pass: PassId.Structural,
|
|
289
|
+
id: 'predicate-inference-equivalence',
|
|
290
|
+
nodeType: PlanNodeType.Filter,
|
|
291
|
+
phase: 'rewrite',
|
|
292
|
+
fn: rulePredicateInferenceEquivalence,
|
|
293
|
+
// Materializes inferred equality conjuncts and optionally injects
|
|
294
|
+
// branch filters above an inner/cross join's children — would change
|
|
295
|
+
// which rows reach a side-effect-bearing branch. Refuses branch
|
|
296
|
+
// injection when the target branch has side effects.
|
|
297
|
+
sideEffectMode: 'aware',
|
|
298
|
+
},
|
|
299
|
+
// GROUP BY FD simplification: drop GROUP BY columns determined by other GROUP
|
|
300
|
+
// BY columns under the aggregate's output FDs + ECs. Picker MIN() aggregates
|
|
301
|
+
// re-emit the dropped columns so output attribute IDs survive. Runs after
|
|
302
|
+
// aggregate-predicate-pushdown so filter-derived ECs are already on the
|
|
303
|
+
// aggregate's source, and before ruleAggregatePhysical (Physical pass) so the
|
|
304
|
+
// smaller GROUP BY feeds the stream/hash decision.
|
|
305
|
+
{
|
|
306
|
+
pass: PassId.Structural,
|
|
307
|
+
id: 'groupby-fd-simplification',
|
|
308
|
+
nodeType: PlanNodeType.Aggregate,
|
|
309
|
+
phase: 'rewrite',
|
|
310
|
+
fn: ruleGroupByFdSimplification,
|
|
311
|
+
// Drops bare-column GROUP BY entries (re-emitting them as picker
|
|
312
|
+
// aggregates). The dropped expressions are pure ColumnReferenceNodes
|
|
313
|
+
// by construction, so no side-effect-bearing scalar can be lost.
|
|
314
|
+
sideEffectMode: 'safe',
|
|
315
|
+
},
|
|
316
|
+
// Join existence-flag pruning (demand-gated): drop an `exists … as` match flag
|
|
317
|
+
// from a JoinNode when no ancestor demands its output attribute id, so
|
|
318
|
+
// `hasExistenceColumns` flips false on the last drop and the five flag-guarded
|
|
319
|
+
// join rules re-enable. Registered AFTER projection-pruning / predicate-pushdown
|
|
320
|
+
// / scalar-cse so the demand set is settled, and BEFORE fanout-lookup-join and
|
|
321
|
+
// join-elimination so the freshly-pruned Project threads through them in the
|
|
322
|
+
// same applyRules loop. The PostOptimization join rules (join-physical-selection,
|
|
323
|
+
// monotonic-merge-join) and the Structural Join-typed lateral-top1-asof (visited
|
|
324
|
+
// top-down after this ancestor Project) see the flag-free join automatically.
|
|
325
|
+
{
|
|
326
|
+
pass: PassId.Structural,
|
|
327
|
+
id: 'join-existence-pruning',
|
|
328
|
+
nodeType: PlanNodeType.Project,
|
|
329
|
+
phase: 'rewrite',
|
|
330
|
+
fn: ruleJoinExistencePruning,
|
|
331
|
+
// Drops only a derived, read-only `{true,false}` boolean column; both
|
|
332
|
+
// join sides survive verbatim, so no write can be skipped or reordered.
|
|
333
|
+
sideEffectMode: 'safe',
|
|
334
|
+
},
|
|
335
|
+
// Aggregate variant of existence-flag pruning: drop an `exists … as` match flag
|
|
336
|
+
// from a JoinNode reachable through a pass-through chain under an AggregateNode
|
|
337
|
+
// (the Project entrypoint never sees this shape). Registered alongside the
|
|
338
|
+
// Project entrypoint and BEFORE join-elimination-aggregate, so a freshly-pruned
|
|
339
|
+
// Aggregate threads into that rule in the same applyRules loop — the aggregate-
|
|
340
|
+
// side analogue of why join-existence-pruning runs before join-elimination.
|
|
341
|
+
{
|
|
342
|
+
pass: PassId.Structural,
|
|
343
|
+
id: 'join-existence-pruning-aggregate',
|
|
344
|
+
nodeType: PlanNodeType.Aggregate,
|
|
345
|
+
phase: 'rewrite',
|
|
346
|
+
fn: ruleJoinExistencePruningUnderAggregate,
|
|
347
|
+
// Same as the Project entrypoint: drops only a derived, read-only
|
|
348
|
+
// `{true,false}` boolean column; both join sides survive verbatim and
|
|
349
|
+
// the Aggregate is reconstructed with identical groupBy / aggregates /
|
|
350
|
+
// output attrs (a pure source swap), so no write can be skipped.
|
|
351
|
+
sideEffectMode: 'safe',
|
|
352
|
+
},
|
|
353
|
+
// Semi/anti-join existence-flag recovery (demand-SHAPE gated): the complement
|
|
354
|
+
// of `join-existence-pruning`. When the sole `exists … as` flag on a
|
|
355
|
+
// `left join` is demanded ONLY as a top-level boolean probe
|
|
356
|
+
// (`where flag` ⇒ semi, `where not flag` ⇒ anti), rewrite the JoinNode to the
|
|
357
|
+
// equivalent semi/anti join — the same shape `subquery-decorrelation` emits —
|
|
358
|
+
// re-opening physical join selection and the IND-folding cascade. Registered
|
|
359
|
+
// AFTER `join-existence-pruning` (so an undemanded sibling flag is dropped
|
|
360
|
+
// first, maximizing the sole-spec precondition) and BEFORE `fanout-lookup-join`
|
|
361
|
+
// / `join-elimination` and the IND folders `anti-join-fk-empty` /
|
|
362
|
+
// `semi-join-fk-trivial` (Join, registered below) so the recovered semi/anti
|
|
363
|
+
// threads into them in the same applyRules loop — exactly why
|
|
364
|
+
// `subquery-decorrelation` precedes those folders. Pass rules fire in
|
|
365
|
+
// REGISTRATION order, so this placement (before fanout-lookup-join /
|
|
366
|
+
// join-elimination / the IND folders) is what realizes the ordering.
|
|
367
|
+
{
|
|
368
|
+
pass: PassId.Structural,
|
|
369
|
+
id: 'semijoin-existence-recovery',
|
|
370
|
+
nodeType: PlanNodeType.Project,
|
|
371
|
+
phase: 'rewrite',
|
|
372
|
+
fn: ruleSemijoinExistenceRecovery,
|
|
373
|
+
// Recovers a semi/anti join, which short-circuits the right side's scan at
|
|
374
|
+
// the first match — changing R's execution count. Refuses when R carries a
|
|
375
|
+
// write (mirrors subquery-decorrelation's impure-inner refusal).
|
|
376
|
+
sideEffectMode: 'aware',
|
|
377
|
+
},
|
|
378
|
+
// Inner-join existence-flag recovery (demand-SHAPE gated): the fallback
|
|
379
|
+
// complement of `semijoin-existence-recovery`. When the sole `exists … as`
|
|
380
|
+
// flag on a `left join` is a POSITIVE top-level probe (`where flag`) AND
|
|
381
|
+
// EITHER ≥1 right-side column is demanded above the join OR R fans out
|
|
382
|
+
// (non-unique on the join column, where a semi join would unsoundly collapse
|
|
383
|
+
// duplicates), rewrite the JoinNode to a plain `inner join` (drop the flag,
|
|
384
|
+
// keep both sides) — re-opening physical join selection, non-nullable right
|
|
385
|
+
// typing, and the FK/IND cascade the live flag pinned shut. The two recovery
|
|
386
|
+
// rules consult the SAME `rightMatchesAtMostOne` and so are provably disjoint
|
|
387
|
+
// on the positive-probe space INDEPENDENT of registration order (semi fires
|
|
388
|
+
// iff !rightColDemanded && unique-R; inner iff rightColDemanded || !unique-R).
|
|
389
|
+
// Registered (in registration order) BEFORE `fanout-lookup-join` /
|
|
390
|
+
// `join-elimination` / the Join-typed IND folders so the recovered inner join
|
|
391
|
+
// threads into them in the same applyRules loop.
|
|
392
|
+
{
|
|
393
|
+
pass: PassId.Structural,
|
|
394
|
+
id: 'inner-join-existence-recovery',
|
|
395
|
+
nodeType: PlanNodeType.Project,
|
|
396
|
+
phase: 'rewrite',
|
|
397
|
+
fn: ruleInnerJoinExistenceRecovery,
|
|
398
|
+
// Logically scans R the same number of times as the flag-bearing left
|
|
399
|
+
// join, but dropping the flag re-enables join-physical-selection, which can
|
|
400
|
+
// pick a hash join that scans R once total — changing an impure R's
|
|
401
|
+
// execution count. Refuses when R carries a write (mirrors the sibling).
|
|
402
|
+
sideEffectMode: 'aware',
|
|
403
|
+
},
|
|
404
|
+
// Aggregate counterpart of `semijoin-existence-recovery`: the same probe-only
|
|
405
|
+
// flag recovery anchored on an `AggregateNode` for the bare `count(*) … where
|
|
406
|
+
// flag` / `group by` shape that plans with NO enclosing Project (the probe
|
|
407
|
+
// Filter + flag-bearing join sit under the Aggregate, so the Project entrypoint
|
|
408
|
+
// never fires). Registered (in registration order) AFTER
|
|
409
|
+
// `join-existence-pruning-aggregate` (so an undemanded sibling flag is dropped
|
|
410
|
+
// first, maximizing the sole-spec precondition) and BEFORE the Join-typed IND
|
|
411
|
+
// folders `anti-join-fk-empty` / `semi-join-fk-trivial` and
|
|
412
|
+
// `join-elimination-aggregate`, so the recovered semi/anti threads into them in
|
|
413
|
+
// the same applyRules loop — the aggregate analogue of the Project rule's
|
|
414
|
+
// placement. No nodeType collision with the Project `semijoin-existence-
|
|
415
|
+
// recovery` (Project vs Aggregate). Unlike the Project anchor it has NO inner
|
|
416
|
+
// fallback: a right-col-demanded / fan-out positive probe stays `left`.
|
|
417
|
+
{
|
|
418
|
+
pass: PassId.Structural,
|
|
419
|
+
id: 'semijoin-existence-recovery-aggregate',
|
|
420
|
+
nodeType: PlanNodeType.Aggregate,
|
|
421
|
+
phase: 'rewrite',
|
|
422
|
+
fn: ruleSemijoinExistenceRecoveryUnderAggregate,
|
|
423
|
+
// Recovers a semi/anti join under an Aggregate — short-circuits R's scan at
|
|
424
|
+
// the first match (semi), changing R's execution count. Same impure-R refusal
|
|
425
|
+
// as the Project entrypoint.
|
|
426
|
+
sideEffectMode: 'aware',
|
|
427
|
+
},
|
|
428
|
+
// Fan-out lookup join (FK→PK): cluster N LEFT/INNER nested-loop joins from a
|
|
429
|
+
// common outer into one parallel `FanOutLookupJoinNode` when the cost gate
|
|
430
|
+
// (per-branch latency × (N - cap) > N × branchSetupCost) approves. Runs *before*
|
|
431
|
+
// `join-elimination` so the rule sees the full branch set; elimination would
|
|
432
|
+
// otherwise steal any single branch whose non-preserved side isn't referenced
|
|
433
|
+
// upstream. The rule's cost gate is inert when `expectedLatencyMs === 0`, so
|
|
434
|
+
// memory-vtab chains never transform (single golden-plan sweep verified — see
|
|
435
|
+
// test/optimizer/parallel-fanout.spec.ts).
|
|
436
|
+
{
|
|
437
|
+
pass: PassId.Structural,
|
|
438
|
+
id: 'fanout-lookup-join',
|
|
439
|
+
nodeType: PlanNodeType.Project,
|
|
440
|
+
phase: 'rewrite',
|
|
441
|
+
fn: ruleFanOutLookupJoin,
|
|
442
|
+
// Clusters per-outer-row branches into a parallel fan-out — drives
|
|
443
|
+
// branches concurrently. Refuses to cluster a branch whose subtree
|
|
444
|
+
// carries a write.
|
|
445
|
+
sideEffectMode: 'aware',
|
|
446
|
+
},
|
|
447
|
+
// Join elimination (FK→PK): drop LEFT/INNER joins whose non-preserved side is
|
|
448
|
+
// never referenced above the join and is at-most-one-matching per a declared
|
|
449
|
+
// FK→PK relationship. Runs after predicate-pushdown so any pushed-up filter that
|
|
450
|
+
// *uses* the eliminable side has had a chance to land below the join (and
|
|
451
|
+
// thereby protect itself from elimination).
|
|
452
|
+
{
|
|
453
|
+
pass: PassId.Structural,
|
|
454
|
+
id: 'join-elimination',
|
|
455
|
+
nodeType: PlanNodeType.Project,
|
|
456
|
+
phase: 'rewrite',
|
|
457
|
+
fn: ruleJoinElimination,
|
|
458
|
+
// Drops the non-preserved side of a join — refuses to drop a subtree
|
|
459
|
+
// that carries a write.
|
|
460
|
+
sideEffectMode: 'aware',
|
|
461
|
+
},
|
|
462
|
+
// Subquery decorrelation: transform correlated EXISTS/IN into semi/anti joins.
|
|
463
|
+
// Runs after predicate-pushdown so inner predicates are already pushed.
|
|
464
|
+
{
|
|
465
|
+
pass: PassId.Structural,
|
|
466
|
+
id: 'subquery-decorrelation',
|
|
467
|
+
nodeType: PlanNodeType.Filter,
|
|
468
|
+
phase: 'rewrite',
|
|
469
|
+
fn: ruleSubqueryDecorrelation,
|
|
470
|
+
// Transforms EXISTS(correlated) / IN(correlated) into semi/anti
|
|
471
|
+
// joins, changing how many times the inner subquery's subtree is
|
|
472
|
+
// executed — refuses when the inner subtree carries a write.
|
|
473
|
+
sideEffectMode: 'aware',
|
|
474
|
+
},
|
|
475
|
+
// Filter match site for scalar-aggregate decorrelation: a correlated
|
|
476
|
+
// scalar-aggregate subquery used in a WHERE (or HAVING) comparison —
|
|
477
|
+
// `where o.total > (select avg(c.amount) from c where c.fk = o.k)` — is
|
|
478
|
+
// rewritten to `Filter[pred'](LeftJoin(outer, groupedAgg))`, sharing the
|
|
479
|
+
// per-subquery rewrite (`decorrelateOne`) with the Project- and Aggregate-site
|
|
480
|
+
// siblings. HAVING is the same shape (a FilterNode over an AggregateNode), so
|
|
481
|
+
// one anchor covers both. Registered AFTER `subquery-decorrelation` (both are
|
|
482
|
+
// Filter-typed; pass rules fire in registration order) so EXISTS/IN semi/anti
|
|
483
|
+
// joins materialize first and this rule then decorrelates any scalar-agg
|
|
484
|
+
// comparison over the already-rewritten source. The two Filter rules target
|
|
485
|
+
// disjoint subquery node types (ExistsNode/InNode conjuncts vs ScalarSubqueryNode
|
|
486
|
+
// anywhere in the tree), so there is no match collision.
|
|
487
|
+
{
|
|
488
|
+
pass: PassId.Structural,
|
|
489
|
+
id: 'scalar-agg-decorrelation-filter',
|
|
490
|
+
nodeType: PlanNodeType.Filter,
|
|
491
|
+
phase: 'rewrite',
|
|
492
|
+
fn: ruleScalarAggDecorrelationFilter,
|
|
493
|
+
// Changes the inner subquery subtree's execution count (per outer row →
|
|
494
|
+
// once) — refuses when the inner subtree carries a write.
|
|
495
|
+
sideEffectMode: 'aware',
|
|
496
|
+
},
|
|
497
|
+
// Scalar-aggregate subquery decorrelation: transform a correlated
|
|
498
|
+
// scalar-aggregate subquery in a SELECT projection into a grouped LEFT JOIN
|
|
499
|
+
// (inner table scanned once, hash-aggregated by correlation key). Registered
|
|
500
|
+
// AFTER `fanout-lookup-join` (which is Project-typed too and, on
|
|
501
|
+
// remote-latency plans, consumes the same subquery shape first — it is inert
|
|
502
|
+
// locally) and adjacent to `subquery-decorrelation`, its WHERE-clause
|
|
503
|
+
// sibling. Unconditional (no cost gate), matching the EXISTS/IN precedent;
|
|
504
|
+
// the tiny-outer/huge-inner tradeoff is tracked in
|
|
505
|
+
// `backlog/feat-decorrelation-cost-model`.
|
|
506
|
+
{
|
|
507
|
+
pass: PassId.Structural,
|
|
508
|
+
id: 'scalar-agg-decorrelation',
|
|
509
|
+
nodeType: PlanNodeType.Project,
|
|
510
|
+
phase: 'rewrite',
|
|
511
|
+
fn: ruleScalarAggDecorrelation,
|
|
512
|
+
// Changes the inner subquery subtree's execution count (per outer row →
|
|
513
|
+
// once) — refuses when the inner subtree carries a write.
|
|
514
|
+
sideEffectMode: 'aware',
|
|
515
|
+
},
|
|
516
|
+
// Aggregate-argument match site for the same rewrite: a scalar-aggregate
|
|
517
|
+
// subquery nested inside an aggregate argument (the shape a two-level
|
|
518
|
+
// nested subquery takes after the Project-site rewrite fires on its
|
|
519
|
+
// enclosing level) is decorrelated into a grouped LEFT JOIN placed BELOW
|
|
520
|
+
// the enclosing aggregate. The structural pass is top-down with rules
|
|
521
|
+
// firing before descent, so multi-level nesting converges level by level
|
|
522
|
+
// within a single pass.
|
|
523
|
+
{
|
|
524
|
+
pass: PassId.Structural,
|
|
525
|
+
id: 'scalar-agg-decorrelation-aggregate',
|
|
526
|
+
nodeType: PlanNodeType.Aggregate,
|
|
527
|
+
phase: 'rewrite',
|
|
528
|
+
fn: ruleScalarAggDecorrelationAggregate,
|
|
529
|
+
// Same execution-count change as the Project-site entry — refuses when
|
|
530
|
+
// the inner subtree carries a write.
|
|
531
|
+
sideEffectMode: 'aware',
|
|
532
|
+
},
|
|
533
|
+
// Sort (ORDER BY) match site for the same rewrite: a correlated
|
|
534
|
+
// scalar-aggregate subquery in a sort-key expression —
|
|
535
|
+
// `order by (select count(*) from c where c.fk = o.k)` — is rewritten to a
|
|
536
|
+
// grouped LEFT JOIN placed BELOW the Sort (so the key can read the value
|
|
537
|
+
// column), then capped with a bare pass-through Project that restores the
|
|
538
|
+
// Sort's original output shape (a SortNode publishes its source's attributes
|
|
539
|
+
// verbatim, so the join's appended columns would otherwise leak upward). Sort
|
|
540
|
+
// is not otherwise a decorrelation anchor, so there is no registration-order
|
|
541
|
+
// coupling with the Filter/Project/Aggregate sites — placed adjacent for
|
|
542
|
+
// locality.
|
|
543
|
+
{
|
|
544
|
+
pass: PassId.Structural,
|
|
545
|
+
id: 'scalar-agg-decorrelation-sort',
|
|
546
|
+
nodeType: PlanNodeType.Sort,
|
|
547
|
+
phase: 'rewrite',
|
|
548
|
+
fn: ruleScalarAggDecorrelationSort,
|
|
549
|
+
// Changes the inner subquery subtree's execution count (per outer row →
|
|
550
|
+
// once) — refuses when the inner subtree carries a write.
|
|
551
|
+
sideEffectMode: 'aware',
|
|
552
|
+
},
|
|
553
|
+
// SELECT-list match site for EXISTS/IN decorrelation: a correlated EXISTS /
|
|
554
|
+
// NOT EXISTS / IN in a ProjectNode's expressions becomes a LEFT join carrying
|
|
555
|
+
// an `exists right as` match flag, with the subquery node replaced by a flag
|
|
556
|
+
// column reference (every outer row survives — a semi/anti join cannot express
|
|
557
|
+
// this). Registered adjacent to its decorrelation siblings; ordering relative
|
|
558
|
+
// to the earlier-registered Project-typed flag rules (join-existence-pruning,
|
|
559
|
+
// the recovery rules) is not load-bearing — the per-node applyRules fixpoint
|
|
560
|
+
// re-offers every rule whenever a transform mints a new node, so they see the
|
|
561
|
+
// flag-bearing Project produced here in the same loop.
|
|
562
|
+
{
|
|
563
|
+
pass: PassId.Structural,
|
|
564
|
+
id: 'exists-in-select-decorrelation',
|
|
565
|
+
nodeType: PlanNodeType.Project,
|
|
566
|
+
phase: 'rewrite',
|
|
567
|
+
fn: ruleExistsInSelectDecorrelation,
|
|
568
|
+
// Changes the inner subquery subtree's execution count (per outer row →
|
|
569
|
+
// once) — refuses when the inner subtree carries a write.
|
|
570
|
+
sideEffectMode: 'aware',
|
|
571
|
+
},
|
|
572
|
+
// IND-driven existence folding (runs after decorrelation has materialized
|
|
573
|
+
// EXISTS / NOT EXISTS as semi/anti joins):
|
|
574
|
+
// - Anti-join over a covering non-null FK → Filter(L, false)
|
|
575
|
+
// - Semi-join over a covering FK → drop join (or Filter L on IS NOT NULL
|
|
576
|
+
// when the FK is nullable)
|
|
577
|
+
// Both rules read `lookupCoveringFK` from `util/ind-utils.ts`.
|
|
578
|
+
{
|
|
579
|
+
pass: PassId.Structural,
|
|
580
|
+
id: 'anti-join-fk-empty',
|
|
581
|
+
nodeType: PlanNodeType.Join,
|
|
582
|
+
phase: 'rewrite',
|
|
583
|
+
fn: ruleAntiJoinFkEmpty,
|
|
584
|
+
// Folds an anti-join to EmptyRelation, dropping both sides. Refuses
|
|
585
|
+
// when either side carries a write.
|
|
586
|
+
sideEffectMode: 'aware',
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
pass: PassId.Structural,
|
|
590
|
+
id: 'semi-join-fk-trivial',
|
|
591
|
+
nodeType: PlanNodeType.Join,
|
|
592
|
+
phase: 'rewrite',
|
|
593
|
+
fn: ruleSemiJoinFkTrivial,
|
|
594
|
+
// Drops the R side of a semi-join (replacing with a NOT NULL filter on
|
|
595
|
+
// L). Refuses when R carries a write.
|
|
596
|
+
sideEffectMode: 'aware',
|
|
597
|
+
},
|
|
598
|
+
// Aggregate variant of join-elimination: when an Aggregate sits over an
|
|
599
|
+
// FK-covered left/right/inner join and only references the FK side (or
|
|
600
|
+
// `count(*)`), drop the join. Shares chain-walking + FK-PK alignment with
|
|
601
|
+
// ruleJoinElimination via the same module.
|
|
602
|
+
{
|
|
603
|
+
pass: PassId.Structural,
|
|
604
|
+
id: 'join-elimination-aggregate',
|
|
605
|
+
nodeType: PlanNodeType.Aggregate,
|
|
606
|
+
phase: 'rewrite',
|
|
607
|
+
fn: ruleJoinEliminationUnderAggregate,
|
|
608
|
+
// Drops the non-preserved side of a left/right/inner join sitting under
|
|
609
|
+
// an Aggregate — same guard as ruleJoinElimination.
|
|
610
|
+
sideEffectMode: 'aware',
|
|
611
|
+
},
|
|
612
|
+
// ORDER BY FD pruning: drop trailing ORDER BY keys functionally determined by
|
|
613
|
+
// the leading bare-column keys (under the source's FDs + ECs). Reduces
|
|
614
|
+
// multi-key sorts to single-key sorts when a leading key (e.g. a primary key)
|
|
615
|
+
// determines the rest, which in turn lets `monotonic-limit-pushdown`
|
|
616
|
+
// (PostOptimization) fire. Structural runs before PostOptimization, so the
|
|
617
|
+
// ordering is automatic. Independent of `subquery-decorrelation`; the relative
|
|
618
|
+
// ordering across these Structural rules is not load-bearing for this rule.
|
|
619
|
+
{
|
|
620
|
+
pass: PassId.Structural,
|
|
621
|
+
id: 'orderby-fd-pruning',
|
|
622
|
+
nodeType: PlanNodeType.Sort,
|
|
623
|
+
phase: 'rewrite',
|
|
624
|
+
fn: ruleOrderByFdPruning,
|
|
625
|
+
// Drops trailing ORDER BY keys (or the whole Sort) — the keys are
|
|
626
|
+
// either bare ColumnReferenceNodes (pure) or kept opaque. The Sort's
|
|
627
|
+
// source is preserved verbatim. Whole-Sort elimination is also safe:
|
|
628
|
+
// it returns `node.source`, so every subtree below survives intact.
|
|
629
|
+
sideEffectMode: 'safe',
|
|
630
|
+
},
|
|
631
|
+
// Predicate-contradiction folding (after the IND rules): detect when (filter
|
|
632
|
+
// predicate ∧ source domainConstraints ∧ literal constantBindings) is provably
|
|
633
|
+
// unsatisfiable, and emit EmptyRelationNode carrying the Filter's own schema.
|
|
634
|
+
// Runs alongside the empty-relation folding rules so its output cascades up the
|
|
635
|
+
// same pass.
|
|
636
|
+
//
|
|
637
|
+
// Inner-join `on`-clause contradiction is intentionally NOT registered here.
|
|
638
|
+
// The filter rule already covers WHERE clauses pushed onto the lowest Filter by
|
|
639
|
+
// `predicate-pushdown`; the join-on variant is tracked as follow-up work — it
|
|
640
|
+
// requires deciding how to preserve the join's post-rewrite output schema for
|
|
641
|
+
// parent operators that reference the right side's attribute IDs.
|
|
642
|
+
{
|
|
643
|
+
pass: PassId.Structural,
|
|
644
|
+
id: 'filter-contradiction',
|
|
645
|
+
nodeType: PlanNodeType.Filter,
|
|
646
|
+
phase: 'rewrite',
|
|
647
|
+
fn: ruleFilterContradiction,
|
|
648
|
+
// Replaces the Filter (and its source) with EmptyRelation — refuses
|
|
649
|
+
// when the source subtree carries a write.
|
|
650
|
+
sideEffectMode: 'aware',
|
|
651
|
+
},
|
|
652
|
+
// Empty-relation folding (after the IND rules): recognize provably-empty
|
|
653
|
+
// subtrees (Filter on lit-false, or any host with an EmptyRelation source under
|
|
654
|
+
// appropriate join semantics) and replace them with EmptyRelationNode carrying
|
|
655
|
+
// the host's attribute IDs. Cascades to a fixed point via the Structural pass
|
|
656
|
+
// loop.
|
|
657
|
+
{
|
|
658
|
+
pass: PassId.Structural,
|
|
659
|
+
id: 'fold-filter-empty',
|
|
660
|
+
nodeType: PlanNodeType.Filter,
|
|
661
|
+
phase: 'rewrite',
|
|
662
|
+
fn: ruleFilterFoldEmpty,
|
|
663
|
+
// `Filter(x, lit-false)` drops `x` — refuses when `x` has side effects.
|
|
664
|
+
sideEffectMode: 'aware',
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
pass: PassId.Structural,
|
|
668
|
+
id: 'fold-project-empty',
|
|
669
|
+
nodeType: PlanNodeType.Project,
|
|
670
|
+
phase: 'rewrite',
|
|
671
|
+
fn: ruleProjectFoldEmpty,
|
|
672
|
+
// Fires only when source is already an EmptyRelation (a pure marker
|
|
673
|
+
// with no children); side-effect-bearing subtree cannot reach this
|
|
674
|
+
// fold without itself first being folded.
|
|
675
|
+
sideEffectMode: 'safe',
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
pass: PassId.Structural,
|
|
679
|
+
id: 'fold-sort-empty',
|
|
680
|
+
nodeType: PlanNodeType.Sort,
|
|
681
|
+
phase: 'rewrite',
|
|
682
|
+
fn: ruleSortFoldEmpty,
|
|
683
|
+
// Source is EmptyRelation; see fold-project-empty.
|
|
684
|
+
sideEffectMode: 'safe',
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
pass: PassId.Structural,
|
|
688
|
+
id: 'fold-limit-empty',
|
|
689
|
+
nodeType: PlanNodeType.LimitOffset,
|
|
690
|
+
phase: 'rewrite',
|
|
691
|
+
fn: ruleLimitOffsetFoldEmpty,
|
|
692
|
+
// Source is EmptyRelation; see fold-project-empty.
|
|
693
|
+
sideEffectMode: 'safe',
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
pass: PassId.Structural,
|
|
697
|
+
id: 'fold-distinct-empty',
|
|
698
|
+
nodeType: PlanNodeType.Distinct,
|
|
699
|
+
phase: 'rewrite',
|
|
700
|
+
fn: ruleDistinctFoldEmpty,
|
|
701
|
+
// Source is EmptyRelation; see fold-project-empty.
|
|
702
|
+
sideEffectMode: 'safe',
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
pass: PassId.Structural,
|
|
706
|
+
id: 'fold-join-empty',
|
|
707
|
+
nodeType: PlanNodeType.Join,
|
|
708
|
+
phase: 'rewrite',
|
|
709
|
+
fn: ruleJoinFoldEmpty,
|
|
710
|
+
// Folds an inner/cross/semi/anti join with an empty side to Empty,
|
|
711
|
+
// dropping the *other* side — refuses when the dropped side carries
|
|
712
|
+
// a write.
|
|
713
|
+
sideEffectMode: 'aware',
|
|
714
|
+
},
|
|
715
|
+
// ── Physical pass (bottom-up) ───────────────────────────────────────────
|
|
716
|
+
{
|
|
717
|
+
pass: PassId.Physical,
|
|
718
|
+
id: 'select-access-path',
|
|
719
|
+
nodeType: PlanNodeType.Retrieve,
|
|
720
|
+
phase: 'impl',
|
|
721
|
+
fn: ruleSelectAccessPath,
|
|
722
|
+
// Replaces a logical Retrieve with a physical access node over the
|
|
723
|
+
// same TableReference — read-only by construction.
|
|
724
|
+
sideEffectMode: 'safe',
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
pass: PassId.Physical,
|
|
728
|
+
id: 'filter-selectivity',
|
|
729
|
+
nodeType: PlanNodeType.Filter,
|
|
730
|
+
phase: 'impl',
|
|
731
|
+
fn: ruleFilterSelectivity,
|
|
732
|
+
// Annotation-only: reads stats and rebuilds the identical Filter (same
|
|
733
|
+
// scope, source, predicate, same output attribute ids) with only an added
|
|
734
|
+
// row estimate — no side-effect reordering.
|
|
735
|
+
sideEffectMode: 'safe',
|
|
736
|
+
},
|
|
737
|
+
// QuickPick join enumeration (optional via tuning).
|
|
738
|
+
{
|
|
739
|
+
pass: PassId.Physical,
|
|
740
|
+
id: 'quickpick-join-enumeration',
|
|
741
|
+
nodeType: PlanNodeType.Join,
|
|
742
|
+
phase: 'impl',
|
|
743
|
+
fn: ruleQuickPickJoinEnumeration,
|
|
744
|
+
// Reorders inner-join trees by cost — would change side-effect
|
|
745
|
+
// execution order. Refuses when any leaf relation has side effects.
|
|
746
|
+
sideEffectMode: 'aware',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
pass: PassId.Physical,
|
|
750
|
+
id: 'aggregate-physical',
|
|
751
|
+
nodeType: PlanNodeType.Aggregate,
|
|
752
|
+
phase: 'impl',
|
|
753
|
+
fn: ruleAggregatePhysical,
|
|
754
|
+
// Selects Stream vs Hash aggregate; the source is preserved verbatim
|
|
755
|
+
// (or wrapped in a Sort, which executes its source once).
|
|
756
|
+
sideEffectMode: 'safe',
|
|
757
|
+
},
|
|
758
|
+
// Recognize lateral-top-1 asof. This is a STRUCTURAL rule but is listed here —
|
|
759
|
+
// after the Physical entries above — because that is its historical
|
|
760
|
+
// registration position, which makes it the LAST rule in the Structural pass.
|
|
761
|
+
// It runs (in the Structural pass) before predicate-pushdown so the lateral's
|
|
762
|
+
// Filter still carries the asof predicate intact — predicate-pushdown would
|
|
763
|
+
// otherwise consume it into the inner Retrieve pipeline.
|
|
764
|
+
{
|
|
765
|
+
pass: PassId.Structural,
|
|
766
|
+
id: 'lateral-top1-asof',
|
|
767
|
+
nodeType: PlanNodeType.Join,
|
|
768
|
+
phase: 'rewrite',
|
|
769
|
+
fn: ruleLateralTop1Asof,
|
|
770
|
+
// Recognizes a very narrow shape (Project/Limit/Sort/Filter chain
|
|
771
|
+
// over a vtab leaf that advertises asofRight) — leaf must be a
|
|
772
|
+
// physical TableReference, so all participating subtrees are
|
|
773
|
+
// read-only by construction.
|
|
774
|
+
sideEffectMode: 'safe',
|
|
775
|
+
},
|
|
776
|
+
// ── Post-optimization pass (bottom-up) ──────────────────────────────────
|
|
777
|
+
// Physical join selection runs here (after Physical pass) so QuickPick can see
|
|
778
|
+
// the full logical join tree before any physical conversion happens.
|
|
779
|
+
// Monotonic-aware merge-join recognition runs first so it can recognise cases
|
|
780
|
+
// where both sides advertise MonotonicOn but `physical.ordering` does not match
|
|
781
|
+
// positionally — once it converts a Join into a MergeJoin, the ordering-based
|
|
782
|
+
// rule no-ops on it.
|
|
783
|
+
{
|
|
784
|
+
pass: PassId.PostOptimization,
|
|
785
|
+
id: 'monotonic-merge-join',
|
|
786
|
+
nodeType: PlanNodeType.Join,
|
|
787
|
+
phase: 'impl',
|
|
788
|
+
fn: ruleMonotonicMergeJoin,
|
|
789
|
+
// Replaces a logical Join with a MergeJoin; both children survive
|
|
790
|
+
// in their original positions (no swap).
|
|
791
|
+
sideEffectMode: 'safe',
|
|
792
|
+
},
|
|
793
|
+
// Monotonic streaming-window recognition. Runs after monotonic-merge-join so
|
|
794
|
+
// child joins have already become MergeJoins and propagate their `monotonicOn`;
|
|
795
|
+
// runs before monotonic-limit-pushdown but does not interact with it (different
|
|
796
|
+
// node type).
|
|
797
|
+
{
|
|
798
|
+
pass: PassId.PostOptimization,
|
|
799
|
+
id: 'monotonic-window',
|
|
800
|
+
nodeType: PlanNodeType.Window,
|
|
801
|
+
phase: 'impl',
|
|
802
|
+
fn: ruleMonotonicWindow,
|
|
803
|
+
// Tags the WindowNode with a streaming config in place; source and
|
|
804
|
+
// functions are preserved verbatim.
|
|
805
|
+
sideEffectMode: 'safe',
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
pass: PassId.PostOptimization,
|
|
809
|
+
id: 'join-physical-selection',
|
|
810
|
+
nodeType: PlanNodeType.Join,
|
|
811
|
+
phase: 'impl',
|
|
812
|
+
fn: ruleJoinPhysicalSelection,
|
|
813
|
+
// May swap build/probe sides of an INNER hash join — would reorder
|
|
814
|
+
// side-effect execution. Refuses when either side has side effects.
|
|
815
|
+
sideEffectMode: 'aware',
|
|
816
|
+
},
|
|
817
|
+
// Monotonic LIMIT/OFFSET pushdown: replace LimitOffset[/Sort]/access-leaf with
|
|
818
|
+
// OrdinalSlice when the leaf advertises supportsOrdinalSeek. Runs in
|
|
819
|
+
// PostOptimization so the leaf already carries its physical capabilities.
|
|
820
|
+
{
|
|
821
|
+
pass: PassId.PostOptimization,
|
|
822
|
+
id: 'monotonic-limit-pushdown',
|
|
823
|
+
nodeType: PlanNodeType.LimitOffset,
|
|
824
|
+
phase: 'impl',
|
|
825
|
+
fn: ruleMonotonicLimitPushdown,
|
|
826
|
+
// Slides LIMIT/OFFSET into a physical access leaf via OrdinalSlice;
|
|
827
|
+
// only fires when the chain peels to a SeqScan/IndexScan/IndexSeek
|
|
828
|
+
// (all read-only by construction).
|
|
829
|
+
sideEffectMode: 'safe',
|
|
830
|
+
},
|
|
831
|
+
// Monotonic range-scan recognition (fan-out; ids
|
|
832
|
+
// `monotonic-range-access-<nodeType>`). Runs on physical leaves to annotate
|
|
833
|
+
// `rangeBoundedOn` when a handled range/equality bounds the monotonic column.
|
|
834
|
+
// Runs after the limit pushdown so that an OrdinalSlice rewrite has already
|
|
835
|
+
// replaced any leaf it would have annotated; ordering vs. join-physical-selection
|
|
836
|
+
// is not load-bearing — `rangeBoundedOn` is a pure annotation today and the
|
|
837
|
+
// defensive drop only matters for downstream rules that check
|
|
838
|
+
// `physical.monotonicOn` (asof/merge-join/limit-pushdown), which run later in the
|
|
839
|
+
// same pass or have already run.
|
|
840
|
+
{
|
|
841
|
+
pass: PassId.PostOptimization,
|
|
842
|
+
id: 'monotonic-range-access',
|
|
843
|
+
nodeType: RANGE_ACCESS_LEAF_TYPES,
|
|
844
|
+
phase: 'rewrite',
|
|
845
|
+
fn: ruleMonotonicRangeAccess,
|
|
846
|
+
// Pure annotation of a physical access leaf (read-only).
|
|
847
|
+
sideEffectMode: 'safe',
|
|
848
|
+
},
|
|
849
|
+
// Filter arm of the same rule (defensive escalation): drop `monotonicOn` from a
|
|
850
|
+
// leaf when an unhandled range predicate sits in a directly-overhead Filter.
|
|
851
|
+
// Registered as a distinct handle with the explicit id `monotonic-range-access-
|
|
852
|
+
// filter` (note: lowercase `filter`, unlike the fan-out ids above).
|
|
853
|
+
{
|
|
854
|
+
pass: PassId.PostOptimization,
|
|
855
|
+
id: 'monotonic-range-access-filter',
|
|
856
|
+
nodeType: PlanNodeType.Filter,
|
|
857
|
+
phase: 'rewrite',
|
|
858
|
+
fn: ruleMonotonicRangeAccess,
|
|
859
|
+
// Defensive escalation: drops a leaf's monotonicOn advertisement;
|
|
860
|
+
// the leaf and Filter source tree survive verbatim.
|
|
861
|
+
sideEffectMode: 'safe',
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
pass: PassId.PostOptimization,
|
|
865
|
+
id: 'mutating-subquery-cache',
|
|
866
|
+
nodeType: PlanNodeType.Join,
|
|
867
|
+
phase: 'rewrite',
|
|
868
|
+
fn: ruleMutatingSubqueryCache,
|
|
869
|
+
// Specifically *targets* side-effect-bearing right sides and wraps
|
|
870
|
+
// them in a run-once CacheNode — the canonical aware rule.
|
|
871
|
+
sideEffectMode: 'aware',
|
|
872
|
+
},
|
|
873
|
+
// Cache the pure right side of a surviving nested-loop JoinNode. Runs
|
|
874
|
+
// immediately after mutating-subquery-cache so that a side-effect-bearing
|
|
875
|
+
// right side is already wrapped (and this rule's already-cached gate skips
|
|
876
|
+
// it) — the two rules partition the space: mutating handles impure right
|
|
877
|
+
// sides, this one handles pure ones. By PostOptimization every equi-join is
|
|
878
|
+
// already a hash/merge, so any logical JoinNode reaching here is a nested
|
|
879
|
+
// loop whose left-driven types re-open the right pipeline per left row.
|
|
880
|
+
{
|
|
881
|
+
pass: PassId.PostOptimization,
|
|
882
|
+
id: 'nested-loop-right-cache',
|
|
883
|
+
nodeType: PlanNodeType.Join,
|
|
884
|
+
phase: 'rewrite',
|
|
885
|
+
fn: ruleNestedLoopRightCache,
|
|
886
|
+
// Only fires on side-effect-free right sides (purity gate), but declares
|
|
887
|
+
// 'aware' to match the sibling cache rules and stay correct if the gate
|
|
888
|
+
// is ever relaxed.
|
|
889
|
+
sideEffectMode: 'aware',
|
|
890
|
+
},
|
|
891
|
+
// AsofScan strategy selection (hash → merge). Runs after the leaves' physical
|
|
892
|
+
// ordering / monotonicOn are finalized (monotonic-range-access) so the
|
|
893
|
+
// predicate-driven check can read them off.
|
|
894
|
+
{
|
|
895
|
+
pass: PassId.PostOptimization,
|
|
896
|
+
id: 'asof-strategy-select',
|
|
897
|
+
nodeType: PlanNodeType.AsofScan,
|
|
898
|
+
phase: 'impl',
|
|
899
|
+
fn: ruleAsofStrategySelect,
|
|
900
|
+
// Flips a strategy field on an existing AsofScan; children survive.
|
|
901
|
+
sideEffectMode: 'safe',
|
|
902
|
+
},
|
|
903
|
+
// Async-gather UNION ALL fold: collapse a chain of SetOperationNode(unionAll)
|
|
904
|
+
// into one N-ary AsyncGatherNode(unionAll) when every flattened child clears
|
|
905
|
+
// `concurrencySafe` AND the slowest child meets `tuning.parallel.gatherThresholdMs`.
|
|
906
|
+
// Runs after `asof-strategy-select` — by which point physical-pass selection has
|
|
907
|
+
// finalized `expectedLatencyMs` / `concurrencySafe` on the leaves — and before
|
|
908
|
+
// `materialization-advisory` so any cache the advisory introduces sits *inside*
|
|
909
|
+
// each gather branch (preserving the parallel-drive overlap of high-latency I/O
|
|
910
|
+
// with branch-local compute). The cost gate is inert on memory-vtab plans
|
|
911
|
+
// (expectedLatencyMs=0), so the local-only golden-plan sweep is unaffected.
|
|
912
|
+
{
|
|
913
|
+
pass: PassId.PostOptimization,
|
|
914
|
+
id: 'async-gather-union-all',
|
|
915
|
+
nodeType: PlanNodeType.SetOperation,
|
|
916
|
+
phase: 'rewrite',
|
|
917
|
+
fn: ruleAsyncGatherUnionAll,
|
|
918
|
+
// Drives N branches concurrently — would interleave writes from
|
|
919
|
+
// side-effect-bearing branches in non-deterministic order. Refuses
|
|
920
|
+
// when any branch carries a write.
|
|
921
|
+
sideEffectMode: 'aware',
|
|
922
|
+
},
|
|
923
|
+
// Async-gather ZIP BY KEY fold: collapse a `Project` over a chain of binary
|
|
924
|
+
// full-outer `JoinNode`s sharing a common key set into one N-ary
|
|
925
|
+
// AsyncGatherNode(zipByKey). Same gates and placement rationale as
|
|
926
|
+
// `async-gather-union-all` (concurrencySafe + gatherThresholdMs + uncorrelated
|
|
927
|
+
// branches; inert on memory-vtab plans where expectedLatencyMs=0). Matches
|
|
928
|
+
// `Project` rather than `SetOperation`; the full-outer chain underneath has no
|
|
929
|
+
// other physical lowering, so it survives untouched to this pass.
|
|
930
|
+
{
|
|
931
|
+
pass: PassId.PostOptimization,
|
|
932
|
+
id: 'async-gather-zip-by-key',
|
|
933
|
+
nodeType: PlanNodeType.Project,
|
|
934
|
+
phase: 'rewrite',
|
|
935
|
+
fn: ruleAsyncGatherZipByKey,
|
|
936
|
+
// Concurrent N-ary zip by key — same concern as union-all gather.
|
|
937
|
+
sideEffectMode: 'aware',
|
|
938
|
+
},
|
|
939
|
+
// Eager-prefetch probe wrap: when a physical hash join's build (right) side is
|
|
940
|
+
// high-latency, wrap the probe (left) side in an `EagerPrefetchNode` so the
|
|
941
|
+
// buffered pump pipelines probe reads with the parent emit's per-row work. Gated
|
|
942
|
+
// on `right.physical.expectedLatencyMs >= prefetchProbeThresholdMs`, which is 0
|
|
943
|
+
// on memory-vtab leaves — so the rule is inert on local-only plans (the
|
|
944
|
+
// golden-plan sweep is unaffected). Runs after `mutating-subquery-cache` and
|
|
945
|
+
// `asof-strategy-select` — by which point leaf physical properties incl.
|
|
946
|
+
// `expectedLatencyMs` are finalized — and before `cte-optimization` and
|
|
947
|
+
// `materialization-advisory`, so the advisory sees the prefetch-wrapped tree and
|
|
948
|
+
// does not re-wrap the probe in a Cache.
|
|
949
|
+
{
|
|
950
|
+
pass: PassId.PostOptimization,
|
|
951
|
+
id: 'eager-prefetch-probe',
|
|
952
|
+
nodeType: PlanNodeType.HashJoin,
|
|
953
|
+
phase: 'rewrite',
|
|
954
|
+
fn: ruleEagerPrefetchProbe,
|
|
955
|
+
// Wraps the probe side in a concurrent prefetch pump — iterates the
|
|
956
|
+
// probe subtree concurrently with the build side, which would
|
|
957
|
+
// interleave writes. Refuses when either side has side effects.
|
|
958
|
+
sideEffectMode: 'aware',
|
|
959
|
+
},
|
|
960
|
+
// Fan-out batched-outer recognition: flip an already-formed
|
|
961
|
+
// `FanOutLookupJoinNode` from serial to batched outer mode when the per-row
|
|
962
|
+
// branch count under-saturates the global in-flight budget, the slowest branch
|
|
963
|
+
// is high-latency, and the outer cardinality is large enough for cross-row
|
|
964
|
+
// pipelining to pay off. Runs after physical selection (so leaf expectedLatencyMs
|
|
965
|
+
// / estimatedRows / concurrencySafe are final) and before `materialization-
|
|
966
|
+
// advisory` so the EagerPrefetch the rule wraps the outer in is already in place
|
|
967
|
+
// when the advisory walks the tree. Inert on memory-vtab plans (expectedLatencyMs
|
|
968
|
+
// = 0 AND estimatedRows = 0), so the golden-plan sweep is unaffected.
|
|
969
|
+
{
|
|
970
|
+
pass: PassId.PostOptimization,
|
|
971
|
+
id: 'fanout-batched-outer',
|
|
972
|
+
nodeType: PlanNodeType.FanOutLookupJoin,
|
|
973
|
+
phase: 'rewrite',
|
|
974
|
+
fn: ruleFanOutBatchedOuter,
|
|
975
|
+
// Flips fan-out outer pump to batched (concurrent) — interleaves
|
|
976
|
+
// outer iteration with branch lookups. Refuses on side-effect outer.
|
|
977
|
+
sideEffectMode: 'aware',
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
pass: PassId.PostOptimization,
|
|
981
|
+
id: 'cte-optimization',
|
|
982
|
+
nodeType: PlanNodeType.CTE,
|
|
983
|
+
phase: 'rewrite',
|
|
984
|
+
fn: ruleCteOptimization,
|
|
985
|
+
// Wraps a CTE source in CacheNode. CacheNode materializes on first
|
|
986
|
+
// read and replays on subsequent reads — a run-once fence over the
|
|
987
|
+
// source, so a side-effect-bearing CTE that was previously rerun
|
|
988
|
+
// per reference would now run once. That is sound but order-changing,
|
|
989
|
+
// so the rule is aware of side effects.
|
|
990
|
+
sideEffectMode: 'aware',
|
|
991
|
+
},
|
|
992
|
+
// IN-subquery caching: wrap uncorrelated IN subquery sources in CacheNode.
|
|
993
|
+
{
|
|
994
|
+
pass: PassId.PostOptimization,
|
|
995
|
+
id: 'in-subquery-cache',
|
|
996
|
+
nodeType: PlanNodeType.In,
|
|
997
|
+
phase: 'rewrite',
|
|
998
|
+
fn: ruleInSubqueryCache,
|
|
999
|
+
// Already gates on `isFunctional(source)` (deterministic + read-only).
|
|
1000
|
+
sideEffectMode: 'aware',
|
|
1001
|
+
},
|
|
1002
|
+
// Scalar-subquery caching: wrap uncorrelated scalar subquery inners in CacheNode.
|
|
1003
|
+
{
|
|
1004
|
+
pass: PassId.PostOptimization,
|
|
1005
|
+
id: 'scalar-subquery-cache',
|
|
1006
|
+
nodeType: PlanNodeType.ScalarSubquery,
|
|
1007
|
+
phase: 'rewrite',
|
|
1008
|
+
fn: ruleScalarSubqueryCache,
|
|
1009
|
+
// Gates on isFunctional(inner) (deterministic + read-only).
|
|
1010
|
+
sideEffectMode: 'aware',
|
|
1011
|
+
},
|
|
1012
|
+
// NOTE: The materialization advisory no longer registers per-node-type rules.
|
|
1013
|
+
// It runs once over the whole plan as a dedicated custom-execute pass
|
|
1014
|
+
// (`PassId.Materialization`, order 35 — after PostOptimization so it observes
|
|
1015
|
+
// the CacheNodes injected by `cte-optimization` / `in-subquery-cache`). See
|
|
1016
|
+
// `createMaterializationPass` in framework/pass.ts for the single-walk rationale
|
|
1017
|
+
// and the side-effect-soundness argument.
|
|
1018
|
+
];
|
|
1019
|
+
/**
|
|
1020
|
+
* Register every entry of `manifest` with its pass on `passManager`, in manifest
|
|
1021
|
+
* array order. That order is the execution contract: pass rules fire in
|
|
1022
|
+
* registration order, so appending in manifest order reproduces the intended
|
|
1023
|
+
* per-pass order.
|
|
1024
|
+
*
|
|
1025
|
+
* A fan-out entry (array `nodeType`) mints one handle per node type with id
|
|
1026
|
+
* `${entry.id}-${nodeType}`; a scalar entry mints one handle with `entry.id`.
|
|
1027
|
+
*
|
|
1028
|
+
* Structural well-formedness is asserted here (once, at construction): the target
|
|
1029
|
+
* pass must exist, and no two handles may share an id WITHIN a pass (the same id in
|
|
1030
|
+
* two DIFFERENT passes is allowed — ids are scoped per pass). The duplicate check
|
|
1031
|
+
* hard-fails with `quereusError(INTERNAL)` rather than silently skipping (as
|
|
1032
|
+
* `PassManager.addRuleToPass` would) — a duplicate id is an author bug.
|
|
1033
|
+
*
|
|
1034
|
+
* Exported (not just the private method) so the guarantees above are unit-testable
|
|
1035
|
+
* against a synthetic manifest; see `test/optimizer/rule-manifest.spec.ts`.
|
|
1036
|
+
*/
|
|
1037
|
+
export function registerManifest(manifest, passManager) {
|
|
1038
|
+
const seenIdsByPass = new Map();
|
|
1039
|
+
for (const entry of manifest) {
|
|
1040
|
+
if (!passManager.getPass(entry.pass)) {
|
|
1041
|
+
quereusError(`Rule manifest entry '${entry.id}' targets unregistered pass '${entry.pass}'`, StatusCode.INTERNAL);
|
|
1042
|
+
}
|
|
1043
|
+
let seenIds = seenIdsByPass.get(entry.pass);
|
|
1044
|
+
if (!seenIds) {
|
|
1045
|
+
seenIds = new Set();
|
|
1046
|
+
seenIdsByPass.set(entry.pass, seenIds);
|
|
1047
|
+
}
|
|
1048
|
+
const nodeTypes = Array.isArray(entry.nodeType) ? entry.nodeType : [entry.nodeType];
|
|
1049
|
+
const isFanOut = Array.isArray(entry.nodeType);
|
|
1050
|
+
for (const nodeType of nodeTypes) {
|
|
1051
|
+
const id = isFanOut ? `${entry.id}-${nodeType}` : entry.id;
|
|
1052
|
+
if (seenIds.has(id)) {
|
|
1053
|
+
quereusError(`Duplicate optimizer rule id '${id}' in pass '${entry.pass}'`, StatusCode.INTERNAL);
|
|
1054
|
+
}
|
|
1055
|
+
seenIds.add(id);
|
|
1056
|
+
// addRuleToPass also runs validateSideEffectMode on the handle.
|
|
1057
|
+
passManager.addRuleToPass(entry.pass, {
|
|
1058
|
+
id,
|
|
1059
|
+
nodeType,
|
|
1060
|
+
phase: entry.phase,
|
|
1061
|
+
fn: entry.fn,
|
|
1062
|
+
sideEffectMode: entry.sideEffectMode,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
log('Registered %d rule manifest entries to optimization passes', manifest.length);
|
|
1067
|
+
}
|
|
62
1068
|
/**
|
|
63
1069
|
* The query optimizer transforms logical plan trees into physical plan trees
|
|
64
1070
|
*/
|
|
@@ -77,857 +1083,13 @@ export class Optimizer {
|
|
|
77
1083
|
updateTuning(tuning) {
|
|
78
1084
|
this.tuning = tuning;
|
|
79
1085
|
}
|
|
80
|
-
static globalRulesRegistered = false;
|
|
81
|
-
/**
|
|
82
|
-
* Legacy method removed; keep empty to avoid duplicate registrations
|
|
83
|
-
*/
|
|
84
1086
|
/**
|
|
85
|
-
* Register
|
|
1087
|
+
* Register every rule in `RULE_MANIFEST` with its pass, in manifest array order.
|
|
1088
|
+
* Delegates to the exported {@link registerManifest} (kept module-level so the
|
|
1089
|
+
* ordering / dedup guarantees are unit-testable against a synthetic manifest).
|
|
86
1090
|
*/
|
|
87
1091
|
registerRulesToPasses() {
|
|
88
|
-
|
|
89
|
-
// Structural pass so it fires on the pristine `Project(Filter?(Retrieve(
|
|
90
|
-
// TableReference)))` — before grow-retrieve / predicate-pushdown reposition
|
|
91
|
-
// the Filter and before the Physical pass absorbs a predicate into a range
|
|
92
|
-
// scan — where the matcher can read the fragment's WHERE off the live plan.
|
|
93
|
-
// Pass rules fire in REGISTRATION order (not by `priority`), so placement here
|
|
94
|
-
// is what guarantees first-fire. Logical→logical: the substituted maintained-table
|
|
95
|
-
// TableReference then flows through normal physical access selection, so
|
|
96
|
-
// `query_plan()` shows an ordinary scan of the MV's own table for free.
|
|
97
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
98
|
-
id: 'materialized-view-rewrite',
|
|
99
|
-
nodeType: PlanNodeType.Project,
|
|
100
|
-
phase: 'rewrite',
|
|
101
|
-
fn: ruleMaterializedViewRewrite,
|
|
102
|
-
priority: 6,
|
|
103
|
-
// Replaces a read-only scan-project-filter fragment with a provably
|
|
104
|
-
// row-equivalent backing scan; the dropped base-scan subtree is pure (the
|
|
105
|
-
// matcher admits only recognized predicates, no subqueries) and the
|
|
106
|
-
// replacement re-emits the fragment's identical output attribute ids.
|
|
107
|
-
sideEffectMode: 'safe',
|
|
108
|
-
});
|
|
109
|
-
// Aggregate arm of the same rewrite (`mv-query-rewrite-aggregate-rollup`):
|
|
110
|
-
// recognizes a logical `Aggregate(Filter?(scan))` answered from a grouped MV
|
|
111
|
-
// (exact-key direct scan or superset-key rollup re-aggregation). Registered as
|
|
112
|
-
// a SECOND handle because pass rules fire only on their `nodeType` and are
|
|
113
|
-
// deduped by id — so the aggregate arm needs the `Aggregate` node type and a
|
|
114
|
-
// distinct id. It honors the canonical `materialized-view-rewrite` disable
|
|
115
|
-
// switch internally (see the rule), so existing rule-disable controls turn off
|
|
116
|
-
// both arms. Registered immediately after the Project arm so it likewise fires
|
|
117
|
-
// on the pristine fragment, before grow-retrieve / predicate-pushdown.
|
|
118
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
119
|
-
id: 'materialized-view-rewrite-aggregate',
|
|
120
|
-
nodeType: PlanNodeType.Aggregate,
|
|
121
|
-
phase: 'rewrite',
|
|
122
|
-
fn: ruleMaterializedViewRewrite,
|
|
123
|
-
priority: 6,
|
|
124
|
-
sideEffectMode: 'safe',
|
|
125
|
-
});
|
|
126
|
-
// Structural pass rules (top-down) - for operations that need parent context
|
|
127
|
-
// Register grow-retrieve for ALL relational node types
|
|
128
|
-
// The rule itself will determine if growth is possible
|
|
129
|
-
const relationalNodeTypes = [
|
|
130
|
-
PlanNodeType.Filter,
|
|
131
|
-
PlanNodeType.Project,
|
|
132
|
-
PlanNodeType.Sort,
|
|
133
|
-
PlanNodeType.LimitOffset,
|
|
134
|
-
PlanNodeType.Aggregate,
|
|
135
|
-
PlanNodeType.Distinct,
|
|
136
|
-
PlanNodeType.Join,
|
|
137
|
-
PlanNodeType.Window,
|
|
138
|
-
// Add any other relational node types as needed
|
|
139
|
-
];
|
|
140
|
-
for (const nodeType of relationalNodeTypes) {
|
|
141
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
142
|
-
id: `grow-retrieve-${nodeType}`,
|
|
143
|
-
nodeType,
|
|
144
|
-
phase: 'rewrite',
|
|
145
|
-
fn: ruleGrowRetrieve,
|
|
146
|
-
priority: 10,
|
|
147
|
-
// Slides operators down into a Retrieve boundary, whose pipeline is
|
|
148
|
-
// always a read by construction (RetrieveNode is the vtab read entry).
|
|
149
|
-
sideEffectMode: 'safe',
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Join key inference (structural/characteristic)
|
|
153
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
154
|
-
id: 'join-key-inference',
|
|
155
|
-
nodeType: PlanNodeType.Join,
|
|
156
|
-
phase: 'rewrite',
|
|
157
|
-
fn: ruleJoinKeyInference,
|
|
158
|
-
priority: 15,
|
|
159
|
-
// Diagnostic-only: never returns a transformed node.
|
|
160
|
-
sideEffectMode: 'safe',
|
|
161
|
-
});
|
|
162
|
-
// Greedy join commute: place smaller input on the left to improve nested-loop-like costs
|
|
163
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
164
|
-
id: 'join-greedy-commute',
|
|
165
|
-
nodeType: PlanNodeType.Join,
|
|
166
|
-
phase: 'rewrite',
|
|
167
|
-
fn: ruleJoinGreedyCommute,
|
|
168
|
-
priority: 16,
|
|
169
|
-
// Swaps left/right of an inner join — would reorder side-effect
|
|
170
|
-
// execution. The rule refuses when either side carries a write.
|
|
171
|
-
sideEffectMode: 'aware',
|
|
172
|
-
});
|
|
173
|
-
// DISTINCT elimination: remove redundant DISTINCT when source already has unique keys
|
|
174
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
175
|
-
id: 'distinct-elimination',
|
|
176
|
-
nodeType: PlanNodeType.Distinct,
|
|
177
|
-
phase: 'rewrite',
|
|
178
|
-
fn: ruleDistinctElimination,
|
|
179
|
-
priority: 18,
|
|
180
|
-
// Unwraps DISTINCT around its source; source survives verbatim and any
|
|
181
|
-
// writes inside it still execute the same number of times.
|
|
182
|
-
sideEffectMode: 'safe',
|
|
183
|
-
});
|
|
184
|
-
// Projection pruning: remove unused inner projections in Project-on-Project
|
|
185
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
186
|
-
id: 'projection-pruning',
|
|
187
|
-
nodeType: PlanNodeType.Project,
|
|
188
|
-
phase: 'rewrite',
|
|
189
|
-
fn: ruleProjectionPruning,
|
|
190
|
-
priority: 19,
|
|
191
|
-
// Drops unreferenced inner projections — refuses to drop any whose
|
|
192
|
-
// scalar expression carries a side effect.
|
|
193
|
-
sideEffectMode: 'aware',
|
|
194
|
-
});
|
|
195
|
-
// Lens auxiliary-access routing: route an outer-query predicate over an
|
|
196
|
-
// inlined lens view through an advertised auxiliary structure (nd-tree /
|
|
197
|
-
// vector / full-text) — an auxiliary seek ⋈ logical-key semi-join — instead
|
|
198
|
-
// of a residual filter over the full decomposition scan. Registered BEFORE
|
|
199
|
-
// predicate-pushdown (priority 20) so the matched predicate is still directly
|
|
200
|
-
// above the LensAuxiliaryAccess marker when this fires; within the top-down
|
|
201
|
-
// Structural pass, rules run in registration order, so placing this block
|
|
202
|
-
// ahead of pushdown is what guarantees first-fire on the Filter. No-ops on
|
|
203
|
-
// any Filter whose subtree has no marker (every non-lens / non-routable-lens
|
|
204
|
-
// view), so ordinary queries are untouched.
|
|
205
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
206
|
-
id: 'lens-auxiliary-access',
|
|
207
|
-
nodeType: PlanNodeType.Filter,
|
|
208
|
-
phase: 'rewrite',
|
|
209
|
-
fn: ruleLensAuxiliaryAccess,
|
|
210
|
-
priority: 17,
|
|
211
|
-
// Replaces a Filter over the lens marker with a semi-join against an
|
|
212
|
-
// auxiliary scan; the logical body (left) survives verbatim and the
|
|
213
|
-
// auxiliary scan it adds is a fresh read-only table reference.
|
|
214
|
-
sideEffectMode: 'safe',
|
|
215
|
-
});
|
|
216
|
-
// Sargable range rewrite: turn `f(col) = c` (for monotone-lossy `f` with
|
|
217
|
-
// a bucketBounds-aware logical type) into `col >= L AND col < U` so the
|
|
218
|
-
// subsequent pushdown wave can carry the bare `col op literal` shape into
|
|
219
|
-
// Retrieve / access-path selection. Runs before aggregate-predicate-pushdown
|
|
220
|
-
// (priority 19) and predicate-pushdown (priority 20) so the rewritten
|
|
221
|
-
// conjuncts ride the same pushdown pass.
|
|
222
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
223
|
-
id: 'sargable-range-rewrite',
|
|
224
|
-
nodeType: PlanNodeType.Filter,
|
|
225
|
-
phase: 'rewrite',
|
|
226
|
-
fn: ruleSargableRangeRewrite,
|
|
227
|
-
priority: 18,
|
|
228
|
-
// Rewrites a single scalar conjunct shape in place; no subtree moved.
|
|
229
|
-
sideEffectMode: 'safe',
|
|
230
|
-
});
|
|
231
|
-
// Aggregate-aware predicate pushdown: splits a Filter above an aggregate so
|
|
232
|
-
// conjuncts on GROUP-BY-determined columns land below the aggregate. Runs
|
|
233
|
-
// before the cross-node predicate pushdown (priority 20) so anything we
|
|
234
|
-
// push below the aggregate can propagate further via that rule.
|
|
235
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
236
|
-
id: 'aggregate-predicate-pushdown',
|
|
237
|
-
nodeType: PlanNodeType.Filter,
|
|
238
|
-
phase: 'rewrite',
|
|
239
|
-
fn: ruleAggregatePredicatePushdown,
|
|
240
|
-
priority: 19,
|
|
241
|
-
// Moves Filter conjuncts below an Aggregate, changing which rows reach
|
|
242
|
-
// the source subtree. Refuses when source has side effects.
|
|
243
|
-
sideEffectMode: 'aware',
|
|
244
|
-
});
|
|
245
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
246
|
-
id: 'predicate-pushdown',
|
|
247
|
-
nodeType: PlanNodeType.Filter,
|
|
248
|
-
phase: 'rewrite',
|
|
249
|
-
fn: rulePredicatePushdown,
|
|
250
|
-
priority: 20,
|
|
251
|
-
// Slides Filter past Sort/Distinct/Project/Alias/Retrieve, changing
|
|
252
|
-
// which rows reach the layer below — refuses when the immediate child
|
|
253
|
-
// subtree carries a write.
|
|
254
|
-
sideEffectMode: 'aware',
|
|
255
|
-
});
|
|
256
|
-
// Filter merge: combine adjacent Filter nodes into one AND-combined Filter
|
|
257
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
258
|
-
id: 'filter-merge',
|
|
259
|
-
nodeType: PlanNodeType.Filter,
|
|
260
|
-
phase: 'rewrite',
|
|
261
|
-
fn: ruleFilterMerge,
|
|
262
|
-
priority: 21,
|
|
263
|
-
// Merges two adjacent Filters into AND; the source subtree is untouched
|
|
264
|
-
// and only the order of predicate-clause evaluation changes (predicates
|
|
265
|
-
// are pure today; the audit gate that DML-in-expression-position needs
|
|
266
|
-
// is on rules that move or drop SUBTREES, not predicate ASTs).
|
|
267
|
-
sideEffectMode: 'safe',
|
|
268
|
-
});
|
|
269
|
-
// Scalar CSE: deduplicate common scalar expressions across Project + Filter + Sort chains
|
|
270
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
271
|
-
id: 'scalar-cse',
|
|
272
|
-
nodeType: PlanNodeType.Project,
|
|
273
|
-
phase: 'rewrite',
|
|
274
|
-
fn: ruleScalarCSE,
|
|
275
|
-
priority: 22,
|
|
276
|
-
// Deduplicates scalar expressions — would silently collapse N copies of
|
|
277
|
-
// a side-effect-bearing scalar into 1. The rule's collector skips any
|
|
278
|
-
// non-deterministic or side-effect-bearing expression.
|
|
279
|
-
sideEffectMode: 'aware',
|
|
280
|
-
});
|
|
281
|
-
// EC-driven predicate inference: materialize inferred equality predicates
|
|
282
|
-
// from the cross of predicate-derived constant bindings and the source's
|
|
283
|
-
// equivalence classes. Runs after predicate-pushdown (priority 20) and
|
|
284
|
-
// filter-merge (priority 21) so the predicate is already consolidated and
|
|
285
|
-
// pushdown won't immediately reabsorb the inferred conjuncts on this
|
|
286
|
-
// iteration; the Structural pass's fixed-point loop then re-runs pushdown
|
|
287
|
-
// on subsequent iterations so the new conjuncts can be carried to
|
|
288
|
-
// branch-level Retrieve pipelines.
|
|
289
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
290
|
-
id: 'predicate-inference-equivalence',
|
|
291
|
-
nodeType: PlanNodeType.Filter,
|
|
292
|
-
phase: 'rewrite',
|
|
293
|
-
fn: rulePredicateInferenceEquivalence,
|
|
294
|
-
priority: 22,
|
|
295
|
-
// Materializes inferred equality conjuncts and optionally injects
|
|
296
|
-
// branch filters above an inner/cross join's children — would change
|
|
297
|
-
// which rows reach a side-effect-bearing branch. Refuses branch
|
|
298
|
-
// injection when the target branch has side effects.
|
|
299
|
-
sideEffectMode: 'aware',
|
|
300
|
-
});
|
|
301
|
-
// GROUP BY FD simplification: drop GROUP BY columns determined by other
|
|
302
|
-
// GROUP BY columns under the aggregate's output FDs + ECs. Picker MIN()
|
|
303
|
-
// aggregates re-emit the dropped columns so output attribute IDs survive.
|
|
304
|
-
// Runs after aggregate-predicate-pushdown (priority 19) so filter-derived
|
|
305
|
-
// ECs are already on the aggregate's source, and before
|
|
306
|
-
// ruleAggregatePhysical (Physical pass) so the smaller GROUP BY feeds
|
|
307
|
-
// the stream/hash decision.
|
|
308
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
309
|
-
id: 'groupby-fd-simplification',
|
|
310
|
-
nodeType: PlanNodeType.Aggregate,
|
|
311
|
-
phase: 'rewrite',
|
|
312
|
-
fn: ruleGroupByFdSimplification,
|
|
313
|
-
priority: 23,
|
|
314
|
-
// Drops bare-column GROUP BY entries (re-emitting them as picker
|
|
315
|
-
// aggregates). The dropped expressions are pure ColumnReferenceNodes
|
|
316
|
-
// by construction, so no side-effect-bearing scalar can be lost.
|
|
317
|
-
sideEffectMode: 'safe',
|
|
318
|
-
});
|
|
319
|
-
// Join existence-flag pruning (demand-gated): drop an `exists … as` match
|
|
320
|
-
// flag from a JoinNode when no ancestor demands its output attribute id, so
|
|
321
|
-
// `hasExistenceColumns` flips false on the last drop and the five
|
|
322
|
-
// flag-guarded join rules re-enable. Registered AFTER projection-pruning
|
|
323
|
-
// (19) / predicate-pushdown (20) / scalar-cse (22) so the demand set is
|
|
324
|
-
// settled, and BEFORE fanout-lookup-join (23) and join-elimination (24) so
|
|
325
|
-
// the freshly-pruned Project threads through them in the same applyRules
|
|
326
|
-
// loop. The PostOptimization join rules (join-physical-selection,
|
|
327
|
-
// monotonic-merge-join) and the Structural Join-typed lateral-top1-asof
|
|
328
|
-
// (visited top-down after this ancestor Project) see the flag-free join
|
|
329
|
-
// automatically.
|
|
330
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
331
|
-
id: 'join-existence-pruning',
|
|
332
|
-
nodeType: PlanNodeType.Project,
|
|
333
|
-
phase: 'rewrite',
|
|
334
|
-
fn: ruleJoinExistencePruning,
|
|
335
|
-
priority: 22,
|
|
336
|
-
// Drops only a derived, read-only `{true,false}` boolean column; both
|
|
337
|
-
// join sides survive verbatim, so no write can be skipped or reordered.
|
|
338
|
-
sideEffectMode: 'safe',
|
|
339
|
-
});
|
|
340
|
-
// Aggregate variant of existence-flag pruning: drop an `exists … as` match
|
|
341
|
-
// flag from a JoinNode reachable through a pass-through chain under an
|
|
342
|
-
// AggregateNode (the Project entrypoint never sees this shape). Priority 22
|
|
343
|
-
// mirrors the Project entrypoint and places it BEFORE
|
|
344
|
-
// join-elimination-aggregate (priority 26), so a freshly-pruned Aggregate
|
|
345
|
-
// threads into that rule in the same applyRules loop — the aggregate-side
|
|
346
|
-
// analogue of why join-existence-pruning (22) runs before
|
|
347
|
-
// join-elimination (24).
|
|
348
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
349
|
-
id: 'join-existence-pruning-aggregate',
|
|
350
|
-
nodeType: PlanNodeType.Aggregate,
|
|
351
|
-
phase: 'rewrite',
|
|
352
|
-
fn: ruleJoinExistencePruningUnderAggregate,
|
|
353
|
-
priority: 22,
|
|
354
|
-
// Same as the Project entrypoint: drops only a derived, read-only
|
|
355
|
-
// `{true,false}` boolean column; both join sides survive verbatim and
|
|
356
|
-
// the Aggregate is reconstructed with identical groupBy / aggregates /
|
|
357
|
-
// output attrs (a pure source swap), so no write can be skipped.
|
|
358
|
-
sideEffectMode: 'safe',
|
|
359
|
-
});
|
|
360
|
-
// Semi/anti-join existence-flag recovery (demand-SHAPE gated): the complement
|
|
361
|
-
// of `join-existence-pruning`. When the sole `exists … as` flag on a
|
|
362
|
-
// `left join` is demanded ONLY as a top-level boolean probe
|
|
363
|
-
// (`where flag` ⇒ semi, `where not flag` ⇒ anti), rewrite the JoinNode to the
|
|
364
|
-
// equivalent semi/anti join — the same shape `subquery-decorrelation` emits —
|
|
365
|
-
// re-opening physical join selection and the IND-folding cascade. Registered
|
|
366
|
-
// AFTER `join-existence-pruning` (so an undemanded sibling flag is dropped
|
|
367
|
-
// first, maximizing the sole-spec precondition) and BEFORE
|
|
368
|
-
// `fanout-lookup-join` / `join-elimination` and the IND folders
|
|
369
|
-
// `anti-join-fk-empty` / `semi-join-fk-trivial` (Join, registered below) so
|
|
370
|
-
// the recovered semi/anti threads into them in the same applyRules loop —
|
|
371
|
-
// exactly why `subquery-decorrelation` precedes those folders. Pass rules fire
|
|
372
|
-
// in REGISTRATION order, so this placement (22 < priority 23 < 26) is what
|
|
373
|
-
// realizes the ordering; the priority value is documentation.
|
|
374
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
375
|
-
id: 'semijoin-existence-recovery',
|
|
376
|
-
nodeType: PlanNodeType.Project,
|
|
377
|
-
phase: 'rewrite',
|
|
378
|
-
fn: ruleSemijoinExistenceRecovery,
|
|
379
|
-
priority: 23,
|
|
380
|
-
// Recovers a semi/anti join, which short-circuits the right side's scan at
|
|
381
|
-
// the first match — changing R's execution count. Refuses when R carries a
|
|
382
|
-
// write (mirrors subquery-decorrelation's impure-inner refusal).
|
|
383
|
-
sideEffectMode: 'aware',
|
|
384
|
-
});
|
|
385
|
-
// Inner-join existence-flag recovery (demand-SHAPE gated): the fallback
|
|
386
|
-
// complement of `semijoin-existence-recovery`. When the sole `exists … as`
|
|
387
|
-
// flag on a `left join` is a POSITIVE top-level probe (`where flag`) AND
|
|
388
|
-
// EITHER ≥1 right-side column is demanded above the join OR R fans out
|
|
389
|
-
// (non-unique on the join column, where a semi join would unsoundly collapse
|
|
390
|
-
// duplicates), rewrite the JoinNode to a plain `inner join` (drop the flag,
|
|
391
|
-
// keep both sides) — re-opening physical join selection, non-nullable right
|
|
392
|
-
// typing, and the FK/IND cascade the live flag pinned shut. The two recovery
|
|
393
|
-
// rules consult the SAME `rightMatchesAtMostOne` and so are provably disjoint
|
|
394
|
-
// on the positive-probe space INDEPENDENT of registration order (semi fires
|
|
395
|
-
// iff !rightColDemanded && unique-R; inner iff rightColDemanded || !unique-R).
|
|
396
|
-
// Registered (in registration order) BEFORE `fanout-lookup-join` /
|
|
397
|
-
// `join-elimination` (24) / the Join-typed IND folders (26) so the recovered
|
|
398
|
-
// inner join threads into them in the same applyRules loop.
|
|
399
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
400
|
-
id: 'inner-join-existence-recovery',
|
|
401
|
-
nodeType: PlanNodeType.Project,
|
|
402
|
-
phase: 'rewrite',
|
|
403
|
-
fn: ruleInnerJoinExistenceRecovery,
|
|
404
|
-
priority: 23,
|
|
405
|
-
// Logically scans R the same number of times as the flag-bearing left
|
|
406
|
-
// join, but dropping the flag re-enables join-physical-selection, which can
|
|
407
|
-
// pick a hash join that scans R once total — changing an impure R's
|
|
408
|
-
// execution count. Refuses when R carries a write (mirrors the sibling).
|
|
409
|
-
sideEffectMode: 'aware',
|
|
410
|
-
});
|
|
411
|
-
// Aggregate counterpart of `semijoin-existence-recovery`: the same probe-only
|
|
412
|
-
// flag recovery anchored on an `AggregateNode` for the bare `count(*) … where
|
|
413
|
-
// flag` / `group by` shape that plans with NO enclosing Project (the probe
|
|
414
|
-
// Filter + flag-bearing join sit under the Aggregate, so the Project entrypoint
|
|
415
|
-
// never fires). Registered (in registration order) AFTER
|
|
416
|
-
// `join-existence-pruning-aggregate` (22, so an undemanded sibling flag is
|
|
417
|
-
// dropped first, maximizing the sole-spec precondition) and BEFORE the
|
|
418
|
-
// Join-typed IND folders `anti-join-fk-empty` / `semi-join-fk-trivial` and
|
|
419
|
-
// `join-elimination-aggregate` (all 26), so the recovered semi/anti threads
|
|
420
|
-
// into them in the same applyRules loop — the aggregate analogue of the Project
|
|
421
|
-
// rule's placement. No nodeType collision with the Project `semijoin-existence-
|
|
422
|
-
// recovery` (Project vs Aggregate). Unlike the Project anchor it has NO inner
|
|
423
|
-
// fallback: a right-col-demanded / fan-out positive probe stays `left`.
|
|
424
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
425
|
-
id: 'semijoin-existence-recovery-aggregate',
|
|
426
|
-
nodeType: PlanNodeType.Aggregate,
|
|
427
|
-
phase: 'rewrite',
|
|
428
|
-
fn: ruleSemijoinExistenceRecoveryUnderAggregate,
|
|
429
|
-
priority: 23,
|
|
430
|
-
// Recovers a semi/anti join under an Aggregate — short-circuits R's scan at
|
|
431
|
-
// the first match (semi), changing R's execution count. Same impure-R refusal
|
|
432
|
-
// as the Project entrypoint.
|
|
433
|
-
sideEffectMode: 'aware',
|
|
434
|
-
});
|
|
435
|
-
// Fan-out lookup join (FK→PK): cluster N LEFT/INNER nested-loop joins from
|
|
436
|
-
// a common outer into one parallel `FanOutLookupJoinNode` when the cost
|
|
437
|
-
// gate (per-branch latency × (N - cap) > N × branchSetupCost) approves.
|
|
438
|
-
// Runs *before* `join-elimination` (priority 24) so the rule sees the full
|
|
439
|
-
// branch set; elimination would otherwise steal any single branch whose
|
|
440
|
-
// non-preserved side isn't referenced upstream. The rule's cost gate is
|
|
441
|
-
// inert when `expectedLatencyMs === 0`, so memory-vtab chains never
|
|
442
|
-
// transform (single golden-plan sweep verified — see
|
|
443
|
-
// test/optimizer/parallel-fanout.spec.ts).
|
|
444
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
445
|
-
id: 'fanout-lookup-join',
|
|
446
|
-
nodeType: PlanNodeType.Project,
|
|
447
|
-
phase: 'rewrite',
|
|
448
|
-
fn: ruleFanOutLookupJoin,
|
|
449
|
-
priority: 23,
|
|
450
|
-
// Clusters per-outer-row branches into a parallel fan-out — drives
|
|
451
|
-
// branches concurrently. Refuses to cluster a branch whose subtree
|
|
452
|
-
// carries a write.
|
|
453
|
-
sideEffectMode: 'aware',
|
|
454
|
-
});
|
|
455
|
-
// Join elimination (FK→PK): drop LEFT/INNER joins whose non-preserved side
|
|
456
|
-
// is never referenced above the join and is at-most-one-matching per a
|
|
457
|
-
// declared FK→PK relationship. Runs after predicate-pushdown (priority 20)
|
|
458
|
-
// so any pushed-up filter that *uses* the eliminable side has had a chance
|
|
459
|
-
// to land below the join (and thereby protect itself from elimination).
|
|
460
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
461
|
-
id: 'join-elimination',
|
|
462
|
-
nodeType: PlanNodeType.Project,
|
|
463
|
-
phase: 'rewrite',
|
|
464
|
-
fn: ruleJoinElimination,
|
|
465
|
-
priority: 24,
|
|
466
|
-
// Drops the non-preserved side of a join — refuses to drop a subtree
|
|
467
|
-
// that carries a write.
|
|
468
|
-
sideEffectMode: 'aware',
|
|
469
|
-
});
|
|
470
|
-
// Subquery decorrelation: transform correlated EXISTS/IN into semi/anti joins
|
|
471
|
-
// Runs after predicate pushdown (priority 25 > 20) so inner predicates are already pushed
|
|
472
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
473
|
-
id: 'subquery-decorrelation',
|
|
474
|
-
nodeType: PlanNodeType.Filter,
|
|
475
|
-
phase: 'rewrite',
|
|
476
|
-
fn: ruleSubqueryDecorrelation,
|
|
477
|
-
priority: 25,
|
|
478
|
-
// Transforms EXISTS(correlated) / IN(correlated) into semi/anti
|
|
479
|
-
// joins, changing how many times the inner subquery's subtree is
|
|
480
|
-
// executed — refuses when the inner subtree carries a write.
|
|
481
|
-
sideEffectMode: 'aware',
|
|
482
|
-
});
|
|
483
|
-
// IND-driven existence folding (priority 26 — runs after decorrelation has
|
|
484
|
-
// materialized EXISTS / NOT EXISTS as semi/anti joins):
|
|
485
|
-
// - Anti-join over a covering non-null FK → Filter(L, false)
|
|
486
|
-
// - Semi-join over a covering FK → drop join (or Filter L on IS NOT NULL
|
|
487
|
-
// when the FK is nullable)
|
|
488
|
-
// Both rules read `lookupCoveringFK` from `util/ind-utils.ts`.
|
|
489
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
490
|
-
id: 'anti-join-fk-empty',
|
|
491
|
-
nodeType: PlanNodeType.Join,
|
|
492
|
-
phase: 'rewrite',
|
|
493
|
-
fn: ruleAntiJoinFkEmpty,
|
|
494
|
-
priority: 26,
|
|
495
|
-
// Folds an anti-join to EmptyRelation, dropping both sides. Refuses
|
|
496
|
-
// when either side carries a write.
|
|
497
|
-
sideEffectMode: 'aware',
|
|
498
|
-
});
|
|
499
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
500
|
-
id: 'semi-join-fk-trivial',
|
|
501
|
-
nodeType: PlanNodeType.Join,
|
|
502
|
-
phase: 'rewrite',
|
|
503
|
-
fn: ruleSemiJoinFkTrivial,
|
|
504
|
-
priority: 26,
|
|
505
|
-
// Drops the R side of a semi-join (replacing with a NOT NULL filter on
|
|
506
|
-
// L). Refuses when R carries a write.
|
|
507
|
-
sideEffectMode: 'aware',
|
|
508
|
-
});
|
|
509
|
-
// Aggregate variant of join-elimination: when an Aggregate sits over an
|
|
510
|
-
// FK-covered left/right/inner join and only references the FK side (or `count(*)`),
|
|
511
|
-
// drop the join. Shares chain-walking + FK-PK alignment with
|
|
512
|
-
// ruleJoinElimination via the same module.
|
|
513
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
514
|
-
id: 'join-elimination-aggregate',
|
|
515
|
-
nodeType: PlanNodeType.Aggregate,
|
|
516
|
-
phase: 'rewrite',
|
|
517
|
-
fn: ruleJoinEliminationUnderAggregate,
|
|
518
|
-
priority: 26,
|
|
519
|
-
// Drops the non-preserved side of a left/right/inner join sitting under
|
|
520
|
-
// an Aggregate — same guard as ruleJoinElimination.
|
|
521
|
-
sideEffectMode: 'aware',
|
|
522
|
-
});
|
|
523
|
-
// ORDER BY FD pruning: drop trailing ORDER BY keys functionally determined
|
|
524
|
-
// by the leading bare-column keys (under the source's FDs + ECs). Reduces
|
|
525
|
-
// multi-key sorts to single-key sorts when a leading key (e.g. a primary
|
|
526
|
-
// key) determines the rest, which in turn lets `monotonic-limit-pushdown`
|
|
527
|
-
// (PostOptimization priority 8) fire. Structural runs before
|
|
528
|
-
// PostOptimization, so the ordering is automatic. Priority 26 — independent
|
|
529
|
-
// of `subquery-decorrelation` (25); the relative ordering across these
|
|
530
|
-
// Structural priorities is not load-bearing for this rule.
|
|
531
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
532
|
-
id: 'orderby-fd-pruning',
|
|
533
|
-
nodeType: PlanNodeType.Sort,
|
|
534
|
-
phase: 'rewrite',
|
|
535
|
-
fn: ruleOrderByFdPruning,
|
|
536
|
-
priority: 26,
|
|
537
|
-
// Drops trailing ORDER BY keys (or the whole Sort) — the keys are
|
|
538
|
-
// either bare ColumnReferenceNodes (pure) or kept opaque. The Sort's
|
|
539
|
-
// source is preserved verbatim. Whole-Sort elimination is also safe:
|
|
540
|
-
// it returns `node.source`, so every subtree below survives intact.
|
|
541
|
-
sideEffectMode: 'safe',
|
|
542
|
-
});
|
|
543
|
-
// Predicate-contradiction folding (priority 27 — after IND rules at 26):
|
|
544
|
-
// detect when (filter predicate ∧ source domainConstraints ∧ literal
|
|
545
|
-
// constantBindings) is provably unsatisfiable, and emit EmptyRelationNode
|
|
546
|
-
// carrying the Filter's own schema. Runs alongside the empty-relation
|
|
547
|
-
// folding rules so its output cascades up the same pass.
|
|
548
|
-
//
|
|
549
|
-
// Inner-join `on`-clause contradiction is intentionally NOT registered
|
|
550
|
-
// here. The filter rule already covers WHERE clauses pushed onto the
|
|
551
|
-
// lowest Filter by `predicate-pushdown`; the join-on variant is tracked
|
|
552
|
-
// as follow-up work — it requires deciding how to preserve the join's
|
|
553
|
-
// post-rewrite output schema for parent operators that reference the
|
|
554
|
-
// right side's attribute IDs.
|
|
555
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
556
|
-
id: 'filter-contradiction',
|
|
557
|
-
nodeType: PlanNodeType.Filter,
|
|
558
|
-
phase: 'rewrite',
|
|
559
|
-
fn: ruleFilterContradiction,
|
|
560
|
-
priority: 27,
|
|
561
|
-
// Replaces the Filter (and its source) with EmptyRelation — refuses
|
|
562
|
-
// when the source subtree carries a write.
|
|
563
|
-
sideEffectMode: 'aware',
|
|
564
|
-
});
|
|
565
|
-
// Empty-relation folding (priority 27 — after IND rules at 26): recognize
|
|
566
|
-
// provably-empty subtrees (Filter on lit-false, or any host with an
|
|
567
|
-
// EmptyRelation source under appropriate join semantics) and replace them
|
|
568
|
-
// with EmptyRelationNode carrying the host's attribute IDs. Cascades to a
|
|
569
|
-
// fixed point via the Structural pass loop.
|
|
570
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
571
|
-
id: 'fold-filter-empty',
|
|
572
|
-
nodeType: PlanNodeType.Filter,
|
|
573
|
-
phase: 'rewrite',
|
|
574
|
-
fn: ruleFilterFoldEmpty,
|
|
575
|
-
priority: 27,
|
|
576
|
-
// `Filter(x, lit-false)` drops `x` — refuses when `x` has side effects.
|
|
577
|
-
sideEffectMode: 'aware',
|
|
578
|
-
});
|
|
579
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
580
|
-
id: 'fold-project-empty',
|
|
581
|
-
nodeType: PlanNodeType.Project,
|
|
582
|
-
phase: 'rewrite',
|
|
583
|
-
fn: ruleProjectFoldEmpty,
|
|
584
|
-
priority: 27,
|
|
585
|
-
// Fires only when source is already an EmptyRelation (a pure marker
|
|
586
|
-
// with no children); side-effect-bearing subtree cannot reach this
|
|
587
|
-
// fold without itself first being folded.
|
|
588
|
-
sideEffectMode: 'safe',
|
|
589
|
-
});
|
|
590
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
591
|
-
id: 'fold-sort-empty',
|
|
592
|
-
nodeType: PlanNodeType.Sort,
|
|
593
|
-
phase: 'rewrite',
|
|
594
|
-
fn: ruleSortFoldEmpty,
|
|
595
|
-
priority: 27,
|
|
596
|
-
// Source is EmptyRelation; see fold-project-empty.
|
|
597
|
-
sideEffectMode: 'safe',
|
|
598
|
-
});
|
|
599
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
600
|
-
id: 'fold-limit-empty',
|
|
601
|
-
nodeType: PlanNodeType.LimitOffset,
|
|
602
|
-
phase: 'rewrite',
|
|
603
|
-
fn: ruleLimitOffsetFoldEmpty,
|
|
604
|
-
priority: 27,
|
|
605
|
-
// Source is EmptyRelation; see fold-project-empty.
|
|
606
|
-
sideEffectMode: 'safe',
|
|
607
|
-
});
|
|
608
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
609
|
-
id: 'fold-distinct-empty',
|
|
610
|
-
nodeType: PlanNodeType.Distinct,
|
|
611
|
-
phase: 'rewrite',
|
|
612
|
-
fn: ruleDistinctFoldEmpty,
|
|
613
|
-
priority: 27,
|
|
614
|
-
// Source is EmptyRelation; see fold-project-empty.
|
|
615
|
-
sideEffectMode: 'safe',
|
|
616
|
-
});
|
|
617
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
618
|
-
id: 'fold-join-empty',
|
|
619
|
-
nodeType: PlanNodeType.Join,
|
|
620
|
-
phase: 'rewrite',
|
|
621
|
-
fn: ruleJoinFoldEmpty,
|
|
622
|
-
priority: 27,
|
|
623
|
-
// Folds an inner/cross/semi/anti join with an empty side to Empty,
|
|
624
|
-
// dropping the *other* side — refuses when the dropped side carries
|
|
625
|
-
// a write.
|
|
626
|
-
sideEffectMode: 'aware',
|
|
627
|
-
});
|
|
628
|
-
// Physical pass rules (bottom-up) - for logical to physical transformations
|
|
629
|
-
this.passManager.addRuleToPass(PassId.Physical, {
|
|
630
|
-
id: 'select-access-path',
|
|
631
|
-
nodeType: PlanNodeType.Retrieve,
|
|
632
|
-
phase: 'impl',
|
|
633
|
-
fn: ruleSelectAccessPath,
|
|
634
|
-
priority: 10,
|
|
635
|
-
// Replaces a logical Retrieve with a physical access node over the
|
|
636
|
-
// same TableReference — read-only by construction.
|
|
637
|
-
sideEffectMode: 'safe',
|
|
638
|
-
});
|
|
639
|
-
// QuickPick join enumeration (optional via tuning)
|
|
640
|
-
this.passManager.addRuleToPass(PassId.Physical, {
|
|
641
|
-
id: 'quickpick-join-enumeration',
|
|
642
|
-
nodeType: PlanNodeType.Join,
|
|
643
|
-
phase: 'impl',
|
|
644
|
-
fn: ruleQuickPickJoinEnumeration,
|
|
645
|
-
priority: 5,
|
|
646
|
-
// Reorders inner-join trees by cost — would change side-effect
|
|
647
|
-
// execution order. Refuses when any leaf relation has side effects.
|
|
648
|
-
sideEffectMode: 'aware',
|
|
649
|
-
});
|
|
650
|
-
this.passManager.addRuleToPass(PassId.Physical, {
|
|
651
|
-
id: 'aggregate-physical',
|
|
652
|
-
nodeType: PlanNodeType.Aggregate,
|
|
653
|
-
phase: 'impl',
|
|
654
|
-
fn: ruleAggregatePhysical,
|
|
655
|
-
priority: 20,
|
|
656
|
-
// Selects Stream vs Hash aggregate; the source is preserved verbatim
|
|
657
|
-
// (or wrapped in a Sort, which executes its source once).
|
|
658
|
-
sideEffectMode: 'safe',
|
|
659
|
-
});
|
|
660
|
-
// Recognize lateral-top-1 asof. Runs in the Structural pass (before
|
|
661
|
-
// predicate-pushdown at priority 20) so the lateral's Filter still
|
|
662
|
-
// carries the asof predicate intact — predicate-pushdown would
|
|
663
|
-
// otherwise consume it into the inner Retrieve pipeline.
|
|
664
|
-
this.passManager.addRuleToPass(PassId.Structural, {
|
|
665
|
-
id: 'lateral-top1-asof',
|
|
666
|
-
nodeType: PlanNodeType.Join,
|
|
667
|
-
phase: 'rewrite',
|
|
668
|
-
fn: ruleLateralTop1Asof,
|
|
669
|
-
priority: 5,
|
|
670
|
-
// Recognizes a very narrow shape (Project/Limit/Sort/Filter chain
|
|
671
|
-
// over a vtab leaf that advertises asofRight) — leaf must be a
|
|
672
|
-
// physical TableReference, so all participating subtrees are
|
|
673
|
-
// read-only by construction.
|
|
674
|
-
sideEffectMode: 'safe',
|
|
675
|
-
});
|
|
676
|
-
// Post-optimization pass rules (bottom-up) - for cleanup and caching
|
|
677
|
-
// Physical join selection runs here (after Physical pass) so QuickPick can
|
|
678
|
-
// see the full logical join tree before any physical conversion happens.
|
|
679
|
-
// Monotonic-aware merge-join recognition runs first (lower priority) so
|
|
680
|
-
// it can recognise cases where both sides advertise MonotonicOn but
|
|
681
|
-
// `physical.ordering` does not match positionally — once it converts a
|
|
682
|
-
// Join into a MergeJoin, the ordering-based rule no-ops on it.
|
|
683
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
684
|
-
id: 'monotonic-merge-join',
|
|
685
|
-
nodeType: PlanNodeType.Join,
|
|
686
|
-
phase: 'impl',
|
|
687
|
-
fn: ruleMonotonicMergeJoin,
|
|
688
|
-
priority: 4,
|
|
689
|
-
// Replaces a logical Join with a MergeJoin; both children survive
|
|
690
|
-
// in their original positions (no swap).
|
|
691
|
-
sideEffectMode: 'safe',
|
|
692
|
-
});
|
|
693
|
-
// Monotonic streaming-window recognition. Runs after monotonic-merge-join
|
|
694
|
-
// (priority 4) so child joins have already become MergeJoins and
|
|
695
|
-
// propagate their `monotonicOn`; runs before monotonic-limit-pushdown
|
|
696
|
-
// (priority 8) but does not interact with it (different node type).
|
|
697
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
698
|
-
id: 'monotonic-window',
|
|
699
|
-
nodeType: PlanNodeType.Window,
|
|
700
|
-
phase: 'impl',
|
|
701
|
-
fn: ruleMonotonicWindow,
|
|
702
|
-
priority: 6,
|
|
703
|
-
// Tags the WindowNode with a streaming config in place; source and
|
|
704
|
-
// functions are preserved verbatim.
|
|
705
|
-
sideEffectMode: 'safe',
|
|
706
|
-
});
|
|
707
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
708
|
-
id: 'join-physical-selection',
|
|
709
|
-
nodeType: PlanNodeType.Join,
|
|
710
|
-
phase: 'impl',
|
|
711
|
-
fn: ruleJoinPhysicalSelection,
|
|
712
|
-
priority: 5,
|
|
713
|
-
// May swap build/probe sides of an INNER hash join — would reorder
|
|
714
|
-
// side-effect execution. Refuses when either side has side effects.
|
|
715
|
-
sideEffectMode: 'aware',
|
|
716
|
-
});
|
|
717
|
-
// Monotonic LIMIT/OFFSET pushdown: replace LimitOffset[/Sort]/access-leaf
|
|
718
|
-
// with OrdinalSlice when the leaf advertises supportsOrdinalSeek. Runs in
|
|
719
|
-
// PostOptimization so the leaf already carries its physical capabilities.
|
|
720
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
721
|
-
id: 'monotonic-limit-pushdown',
|
|
722
|
-
nodeType: PlanNodeType.LimitOffset,
|
|
723
|
-
phase: 'impl',
|
|
724
|
-
fn: ruleMonotonicLimitPushdown,
|
|
725
|
-
priority: 8,
|
|
726
|
-
// Slides LIMIT/OFFSET into a physical access leaf via OrdinalSlice;
|
|
727
|
-
// only fires when the chain peels to a SeqScan/IndexScan/IndexSeek
|
|
728
|
-
// (all read-only by construction).
|
|
729
|
-
sideEffectMode: 'safe',
|
|
730
|
-
});
|
|
731
|
-
// Monotonic range-scan recognition. Runs on physical leaves to annotate
|
|
732
|
-
// `rangeBoundedOn` when a handled range/equality bounds the monotonic
|
|
733
|
-
// column. Also runs on Filter nodes for the defensive escalation: drop
|
|
734
|
-
// `monotonicOn` from a leaf when an unhandled range predicate sits in a
|
|
735
|
-
// directly-overhead Filter. Runs after the limit pushdown (priority 9)
|
|
736
|
-
// so that an OrdinalSlice rewrite has already replaced any leaf it
|
|
737
|
-
// would have annotated; ordering vs. join-physical-selection (priority 5)
|
|
738
|
-
// is not load-bearing — `rangeBoundedOn` is a pure annotation today and
|
|
739
|
-
// the defensive drop only matters for downstream rules that check
|
|
740
|
-
// `physical.monotonicOn` (asof/merge-join/limit-pushdown), which run
|
|
741
|
-
// later in the same pass or have already run.
|
|
742
|
-
const rangeAccessLeafTypes = [
|
|
743
|
-
PlanNodeType.IndexScan,
|
|
744
|
-
PlanNodeType.IndexSeek,
|
|
745
|
-
PlanNodeType.SeqScan,
|
|
746
|
-
];
|
|
747
|
-
for (const nodeType of rangeAccessLeafTypes) {
|
|
748
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
749
|
-
id: `monotonic-range-access-${nodeType}`,
|
|
750
|
-
nodeType,
|
|
751
|
-
phase: 'rewrite',
|
|
752
|
-
fn: ruleMonotonicRangeAccess,
|
|
753
|
-
priority: 9,
|
|
754
|
-
// Pure annotation of a physical access leaf (read-only).
|
|
755
|
-
sideEffectMode: 'safe',
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
759
|
-
id: 'monotonic-range-access-filter',
|
|
760
|
-
nodeType: PlanNodeType.Filter,
|
|
761
|
-
phase: 'rewrite',
|
|
762
|
-
fn: ruleMonotonicRangeAccess,
|
|
763
|
-
priority: 9,
|
|
764
|
-
// Defensive escalation: drops a leaf's monotonicOn advertisement;
|
|
765
|
-
// the leaf and Filter source tree survive verbatim.
|
|
766
|
-
sideEffectMode: 'safe',
|
|
767
|
-
});
|
|
768
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
769
|
-
id: 'mutating-subquery-cache',
|
|
770
|
-
nodeType: PlanNodeType.Join,
|
|
771
|
-
phase: 'rewrite',
|
|
772
|
-
fn: ruleMutatingSubqueryCache,
|
|
773
|
-
priority: 10,
|
|
774
|
-
// Specifically *targets* side-effect-bearing right sides and wraps
|
|
775
|
-
// them in a run-once CacheNode — the canonical aware rule.
|
|
776
|
-
sideEffectMode: 'aware',
|
|
777
|
-
});
|
|
778
|
-
// AsofScan strategy selection (hash → merge). Runs after the leaves'
|
|
779
|
-
// physical.ordering / monotonicOn are finalized (range-access at
|
|
780
|
-
// priority 9) so the predicate-driven check can read them off.
|
|
781
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
782
|
-
id: 'asof-strategy-select',
|
|
783
|
-
nodeType: PlanNodeType.AsofScan,
|
|
784
|
-
phase: 'impl',
|
|
785
|
-
fn: ruleAsofStrategySelect,
|
|
786
|
-
priority: 11,
|
|
787
|
-
// Flips a strategy field on an existing AsofScan; children survive.
|
|
788
|
-
sideEffectMode: 'safe',
|
|
789
|
-
});
|
|
790
|
-
// Async-gather UNION ALL fold: collapse a chain of
|
|
791
|
-
// SetOperationNode(unionAll) into one N-ary AsyncGatherNode(unionAll)
|
|
792
|
-
// when every flattened child clears `concurrencySafe` AND the slowest
|
|
793
|
-
// child meets `tuning.parallel.gatherThresholdMs`. Runs after
|
|
794
|
-
// `asof-strategy-select` (priority 11) — by which point physical-pass
|
|
795
|
-
// selection has finalized `expectedLatencyMs` / `concurrencySafe` on
|
|
796
|
-
// the leaves — and before `materialization-advisory` (priority 30) so
|
|
797
|
-
// any cache the advisory introduces sits *inside* each gather branch
|
|
798
|
-
// (preserving the parallel-drive overlap of high-latency I/O with
|
|
799
|
-
// branch-local compute). The cost gate is inert on memory-vtab
|
|
800
|
-
// plans (expectedLatencyMs=0), so the local-only golden-plan sweep
|
|
801
|
-
// is unaffected.
|
|
802
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
803
|
-
id: 'async-gather-union-all',
|
|
804
|
-
nodeType: PlanNodeType.SetOperation,
|
|
805
|
-
phase: 'rewrite',
|
|
806
|
-
fn: ruleAsyncGatherUnionAll,
|
|
807
|
-
priority: 17,
|
|
808
|
-
// Drives N branches concurrently — would interleave writes from
|
|
809
|
-
// side-effect-bearing branches in non-deterministic order. Refuses
|
|
810
|
-
// when any branch carries a write.
|
|
811
|
-
sideEffectMode: 'aware',
|
|
812
|
-
});
|
|
813
|
-
// Async-gather ZIP BY KEY fold: collapse a `Project` over a chain of
|
|
814
|
-
// binary full-outer `JoinNode`s sharing a common key set into one N-ary
|
|
815
|
-
// AsyncGatherNode(zipByKey). Same gates and placement rationale as
|
|
816
|
-
// `async-gather-union-all` (concurrencySafe + gatherThresholdMs +
|
|
817
|
-
// uncorrelated branches; inert on memory-vtab plans where
|
|
818
|
-
// expectedLatencyMs=0). Matches `Project` rather than `SetOperation`;
|
|
819
|
-
// the full-outer chain underneath has no other physical lowering, so it
|
|
820
|
-
// survives untouched to this pass.
|
|
821
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
822
|
-
id: 'async-gather-zip-by-key',
|
|
823
|
-
nodeType: PlanNodeType.Project,
|
|
824
|
-
phase: 'rewrite',
|
|
825
|
-
fn: ruleAsyncGatherZipByKey,
|
|
826
|
-
priority: 17,
|
|
827
|
-
// Concurrent N-ary zip by key — same concern as union-all gather.
|
|
828
|
-
sideEffectMode: 'aware',
|
|
829
|
-
});
|
|
830
|
-
// Eager-prefetch probe wrap: when a physical hash join's build (right)
|
|
831
|
-
// side is high-latency, wrap the probe (left) side in an
|
|
832
|
-
// `EagerPrefetchNode` so the buffered pump pipelines probe reads with
|
|
833
|
-
// the parent emit's per-row work. Gated on
|
|
834
|
-
// `right.physical.expectedLatencyMs >= prefetchProbeThresholdMs`, which
|
|
835
|
-
// is 0 on memory-vtab leaves — so the rule is inert on local-only plans
|
|
836
|
-
// (the golden-plan sweep is unaffected). Runs after `mutating-subquery-
|
|
837
|
-
// cache` (priority 10) and `asof-strategy-select` (priority 11) — by
|
|
838
|
-
// which point leaf physical properties incl. `expectedLatencyMs` are
|
|
839
|
-
// finalized — and before `cte-optimization` (priority 20) and
|
|
840
|
-
// `materialization-advisory` (priority 30), so the advisory sees the
|
|
841
|
-
// prefetch-wrapped tree and does not re-wrap the probe in a Cache.
|
|
842
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
843
|
-
id: 'eager-prefetch-probe',
|
|
844
|
-
nodeType: PlanNodeType.HashJoin,
|
|
845
|
-
phase: 'rewrite',
|
|
846
|
-
fn: ruleEagerPrefetchProbe,
|
|
847
|
-
priority: 15,
|
|
848
|
-
// Wraps the probe side in a concurrent prefetch pump — iterates the
|
|
849
|
-
// probe subtree concurrently with the build side, which would
|
|
850
|
-
// interleave writes. Refuses when either side has side effects.
|
|
851
|
-
sideEffectMode: 'aware',
|
|
852
|
-
});
|
|
853
|
-
// Fan-out batched-outer recognition: flip an already-formed
|
|
854
|
-
// `FanOutLookupJoinNode` from serial to batched outer mode when the per-row
|
|
855
|
-
// branch count under-saturates the global in-flight budget, the slowest
|
|
856
|
-
// branch is high-latency, and the outer cardinality is large enough for
|
|
857
|
-
// cross-row pipelining to pay off. Runs after physical selection (so leaf
|
|
858
|
-
// expectedLatencyMs / estimatedRows / concurrencySafe are final) and before
|
|
859
|
-
// `materialization-advisory` (priority 30) so the EagerPrefetch the rule
|
|
860
|
-
// wraps the outer in is already in place when the advisory walks the tree.
|
|
861
|
-
// Inert on memory-vtab plans (expectedLatencyMs = 0 AND estimatedRows = 0),
|
|
862
|
-
// so the golden-plan sweep is unaffected.
|
|
863
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
864
|
-
id: 'fanout-batched-outer',
|
|
865
|
-
nodeType: PlanNodeType.FanOutLookupJoin,
|
|
866
|
-
phase: 'rewrite',
|
|
867
|
-
fn: ruleFanOutBatchedOuter,
|
|
868
|
-
priority: 16,
|
|
869
|
-
// Flips fan-out outer pump to batched (concurrent) — interleaves
|
|
870
|
-
// outer iteration with branch lookups. Refuses on side-effect outer.
|
|
871
|
-
sideEffectMode: 'aware',
|
|
872
|
-
});
|
|
873
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
874
|
-
id: 'cte-optimization',
|
|
875
|
-
nodeType: PlanNodeType.CTE,
|
|
876
|
-
phase: 'rewrite',
|
|
877
|
-
fn: ruleCteOptimization,
|
|
878
|
-
priority: 20,
|
|
879
|
-
// Wraps a CTE source in CacheNode. CacheNode materializes on first
|
|
880
|
-
// read and replays on subsequent reads — a run-once fence over the
|
|
881
|
-
// source, so a side-effect-bearing CTE that was previously rerun
|
|
882
|
-
// per reference would now run once. That is sound but order-changing,
|
|
883
|
-
// so the rule is aware of side effects.
|
|
884
|
-
sideEffectMode: 'aware',
|
|
885
|
-
});
|
|
886
|
-
// IN-subquery caching: wrap uncorrelated IN subquery sources in CacheNode
|
|
887
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
888
|
-
id: 'in-subquery-cache',
|
|
889
|
-
nodeType: PlanNodeType.In,
|
|
890
|
-
phase: 'rewrite',
|
|
891
|
-
fn: ruleInSubqueryCache,
|
|
892
|
-
priority: 25,
|
|
893
|
-
// Already gates on `isFunctional(source)` (deterministic + read-only).
|
|
894
|
-
sideEffectMode: 'aware',
|
|
895
|
-
});
|
|
896
|
-
// Register materialization advisory for multiple node types
|
|
897
|
-
const nodeTypesForMaterialization = [
|
|
898
|
-
PlanNodeType.Block,
|
|
899
|
-
PlanNodeType.ScalarSubquery,
|
|
900
|
-
PlanNodeType.Exists,
|
|
901
|
-
PlanNodeType.In,
|
|
902
|
-
PlanNodeType.Insert,
|
|
903
|
-
PlanNodeType.Update,
|
|
904
|
-
PlanNodeType.Delete,
|
|
905
|
-
PlanNodeType.CTE,
|
|
906
|
-
PlanNodeType.RecursiveCTE,
|
|
907
|
-
PlanNodeType.Returning,
|
|
908
|
-
PlanNodeType.ScalarFunctionCall,
|
|
909
|
-
PlanNodeType.CaseExpr,
|
|
910
|
-
];
|
|
911
|
-
for (const nodeType of nodeTypesForMaterialization) {
|
|
912
|
-
this.passManager.addRuleToPass(PassId.PostOptimization, {
|
|
913
|
-
id: `materialization-advisory-${nodeType}`,
|
|
914
|
-
nodeType,
|
|
915
|
-
phase: 'rewrite',
|
|
916
|
-
fn: ruleMaterializationAdvisory,
|
|
917
|
-
priority: 30,
|
|
918
|
-
// Delegates to MaterializationAdvisory. The advisory does not
|
|
919
|
-
// explicitly consult `hasSideEffects` — soundness for impure
|
|
920
|
-
// subtrees rests on CacheNode itself being a run-once fence
|
|
921
|
-
// (materialize-on-first-read, replay thereafter), so a
|
|
922
|
-
// side-effect-bearing subtree that the advisory would otherwise
|
|
923
|
-
// wrap runs exactly once instead of per-reference. That is a
|
|
924
|
-
// count-change but order-preserving rewrite — and matches the
|
|
925
|
-
// run-once contract the scalar / IN / EXISTS emitters apply
|
|
926
|
-
// directly when their inner is impure (see `docs/runtime.md`).
|
|
927
|
-
sideEffectMode: 'aware',
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
log('Registered rules to optimization passes');
|
|
1092
|
+
registerManifest(RULE_MANIFEST, this.passManager);
|
|
931
1093
|
}
|
|
932
1094
|
/**
|
|
933
1095
|
* Optimize a plan tree by applying transformation rules
|
|
@@ -978,49 +1140,6 @@ export class Optimizer {
|
|
|
978
1140
|
tracePhaseEnd('pre-physical-analysis');
|
|
979
1141
|
}
|
|
980
1142
|
}
|
|
981
|
-
optimizeNode(node, context) {
|
|
982
|
-
traceNodeStart(node);
|
|
983
|
-
// Check if we've already optimized this exact node instance
|
|
984
|
-
const cached = context.optimizedNodes.get(node.id);
|
|
985
|
-
if (cached) {
|
|
986
|
-
log('Reusing optimized version of shared node %s (%s)', node.id, node.nodeType);
|
|
987
|
-
traceNodeEnd(node, cached);
|
|
988
|
-
return cached;
|
|
989
|
-
}
|
|
990
|
-
// Note: We removed the broken `if (node.physical)` check here
|
|
991
|
-
// The `physical` property is always truthy (it returns a PhysicalProperties object)
|
|
992
|
-
// Physical vs logical distinction should be handled by the rules themselves
|
|
993
|
-
// First optimize all children
|
|
994
|
-
const optimizedNode = this.optimizeChildren(node, context);
|
|
995
|
-
// Apply rules
|
|
996
|
-
const rulesApplied = applyRules(optimizedNode, context);
|
|
997
|
-
if (rulesApplied !== optimizedNode) {
|
|
998
|
-
// Rules transformed the node
|
|
999
|
-
log(`Rules applied to ${optimizedNode.nodeType}, transformed to ${rulesApplied.nodeType}`);
|
|
1000
|
-
traceNodeEnd(node, rulesApplied);
|
|
1001
|
-
// Cache the final result
|
|
1002
|
-
context.optimizedNodes.set(node.id, rulesApplied);
|
|
1003
|
-
return rulesApplied;
|
|
1004
|
-
}
|
|
1005
|
-
// No rule applied - assume node is physical
|
|
1006
|
-
traceNodeEnd(node, optimizedNode);
|
|
1007
|
-
// Cache the result even if no rules applied
|
|
1008
|
-
context.optimizedNodes.set(node.id, optimizedNode);
|
|
1009
|
-
return optimizedNode;
|
|
1010
|
-
}
|
|
1011
|
-
optimizeChildren(node, context) {
|
|
1012
|
-
// Generic tree walk using withChildren
|
|
1013
|
-
const originalChildren = node.getChildren();
|
|
1014
|
-
const optimizedChildren = originalChildren.map(child => this.optimizeNode(child, context));
|
|
1015
|
-
// Check if any children changed
|
|
1016
|
-
const childrenChanged = optimizedChildren.some((child, i) => child !== originalChildren[i]);
|
|
1017
|
-
if (!childrenChanged) {
|
|
1018
|
-
return node; // No changes
|
|
1019
|
-
}
|
|
1020
|
-
// Use withChildren to create new node with optimized children
|
|
1021
|
-
// withChildren is a required contract - any errors should propagate
|
|
1022
|
-
return node.withChildren(optimizedChildren);
|
|
1023
|
-
}
|
|
1024
1143
|
/**
|
|
1025
1144
|
* Get the statistics provider
|
|
1026
1145
|
*/
|