@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.
Files changed (788) hide show
  1. package/README.md +25 -7
  2. package/dist/src/common/types.d.ts +9 -59
  3. package/dist/src/common/types.d.ts.map +1 -1
  4. package/dist/src/common/types.js.map +1 -1
  5. package/dist/src/core/database-assertions.d.ts +2 -2
  6. package/dist/src/core/database-assertions.d.ts.map +1 -1
  7. package/dist/src/core/database-assertions.js +4 -3
  8. package/dist/src/core/database-assertions.js.map +1 -1
  9. package/dist/src/core/database-external-changes.d.ts +3 -3
  10. package/dist/src/core/database-external-changes.d.ts.map +1 -1
  11. package/dist/src/core/database-external-changes.js +13 -10
  12. package/dist/src/core/database-external-changes.js.map +1 -1
  13. package/dist/src/core/database-internal.d.ts.map +1 -1
  14. package/dist/src/core/database-materialized-views-analysis.d.ts +199 -0
  15. package/dist/src/core/database-materialized-views-analysis.d.ts.map +1 -0
  16. package/dist/src/core/database-materialized-views-analysis.js +502 -0
  17. package/dist/src/core/database-materialized-views-analysis.js.map +1 -0
  18. package/dist/src/core/database-materialized-views-apply.d.ts +422 -0
  19. package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -0
  20. package/dist/src/core/database-materialized-views-apply.js +929 -0
  21. package/dist/src/core/database-materialized-views-apply.js.map +1 -0
  22. package/dist/src/core/database-materialized-views-plan-builders.d.ts +267 -0
  23. package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -0
  24. package/dist/src/core/database-materialized-views-plan-builders.js +1423 -0
  25. package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -0
  26. package/dist/src/core/database-materialized-views-plans.d.ts +641 -0
  27. package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -0
  28. package/dist/src/core/database-materialized-views-plans.js +13 -0
  29. package/dist/src/core/database-materialized-views-plans.js.map +1 -0
  30. package/dist/src/core/database-materialized-views.d.ts +108 -860
  31. package/dist/src/core/database-materialized-views.d.ts.map +1 -1
  32. package/dist/src/core/database-materialized-views.js +212 -2165
  33. package/dist/src/core/database-materialized-views.js.map +1 -1
  34. package/dist/src/core/database-transaction.d.ts +10 -1
  35. package/dist/src/core/database-transaction.d.ts.map +1 -1
  36. package/dist/src/core/database-transaction.js +16 -4
  37. package/dist/src/core/database-transaction.js.map +1 -1
  38. package/dist/src/core/database-watchers.d.ts.map +1 -1
  39. package/dist/src/core/database-watchers.js +2 -1
  40. package/dist/src/core/database-watchers.js.map +1 -1
  41. package/dist/src/core/database.d.ts +99 -13
  42. package/dist/src/core/database.d.ts.map +1 -1
  43. package/dist/src/core/database.js +270 -50
  44. package/dist/src/core/database.js.map +1 -1
  45. package/dist/src/core/derived-row-validator.d.ts +1 -1
  46. package/dist/src/core/derived-row-validator.d.ts.map +1 -1
  47. package/dist/src/core/derived-row-validator.js +5 -3
  48. package/dist/src/core/derived-row-validator.js.map +1 -1
  49. package/dist/src/core/statement.d.ts +8 -0
  50. package/dist/src/core/statement.d.ts.map +1 -1
  51. package/dist/src/core/statement.js +98 -18
  52. package/dist/src/core/statement.js.map +1 -1
  53. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  54. package/dist/src/emit/ast-stringify.js +24 -13
  55. package/dist/src/emit/ast-stringify.js.map +1 -1
  56. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  57. package/dist/src/func/builtins/aggregate.js +127 -21
  58. package/dist/src/func/builtins/aggregate.js.map +1 -1
  59. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  60. package/dist/src/func/builtins/explain.js +2 -1
  61. package/dist/src/func/builtins/explain.js.map +1 -1
  62. package/dist/src/func/builtins/json.d.ts.map +1 -1
  63. package/dist/src/func/builtins/json.js +2 -1
  64. package/dist/src/func/builtins/json.js.map +1 -1
  65. package/dist/src/func/builtins/mutation.d.ts.map +1 -1
  66. package/dist/src/func/builtins/mutation.js +3 -2
  67. package/dist/src/func/builtins/mutation.js.map +1 -1
  68. package/dist/src/func/registration.d.ts +6 -1
  69. package/dist/src/func/registration.d.ts.map +1 -1
  70. package/dist/src/func/registration.js +16 -0
  71. package/dist/src/func/registration.js.map +1 -1
  72. package/dist/src/index.d.ts +16 -6
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/index.js +33 -7
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/parser/ast.d.ts +2 -2
  77. package/dist/src/parser/lexer.d.ts +11 -1
  78. package/dist/src/parser/lexer.d.ts.map +1 -1
  79. package/dist/src/parser/lexer.js +40 -34
  80. package/dist/src/parser/lexer.js.map +1 -1
  81. package/dist/src/parser/parser.d.ts.map +1 -1
  82. package/dist/src/parser/parser.js +16 -9
  83. package/dist/src/parser/parser.js.map +1 -1
  84. package/dist/src/planner/analysis/authored-inverse.js +1 -1
  85. package/dist/src/planner/analysis/comparison-collation.d.ts +52 -0
  86. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
  87. package/dist/src/planner/analysis/comparison-collation.js +84 -7
  88. package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
  89. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  90. package/dist/src/planner/analysis/constraint-extractor.js +64 -17
  91. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  92. package/dist/src/planner/analysis/coverage-prover.d.ts +1 -1
  93. package/dist/src/planner/analysis/coverage-prover.js +1 -1
  94. package/dist/src/planner/analysis/equi-correlation.d.ts +26 -0
  95. package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
  96. package/dist/src/planner/analysis/equi-correlation.js +62 -0
  97. package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
  98. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +54 -18
  99. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  100. package/dist/src/planner/analysis/query-rewrite-matcher.js +86 -42
  101. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  102. package/dist/src/planner/analysis/sat-checker.d.ts +8 -1
  103. package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
  104. package/dist/src/planner/analysis/sat-checker.js +117 -22
  105. package/dist/src/planner/analysis/sat-checker.js.map +1 -1
  106. package/dist/src/planner/analysis/scalar-invertibility.d.ts +13 -4
  107. package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -1
  108. package/dist/src/planner/analysis/scalar-invertibility.js +12 -3
  109. package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -1
  110. package/dist/src/planner/analysis/scalar-subqueries.d.ts +28 -0
  111. package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
  112. package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
  113. package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
  114. package/dist/src/planner/analysis/update-lineage.d.ts +1 -1
  115. package/dist/src/planner/analysis/update-lineage.js +1 -1
  116. package/dist/src/planner/analysis/update-lineage.js.map +1 -1
  117. package/dist/src/planner/analysis/view-complement.d.ts +2 -2
  118. package/dist/src/planner/building/delete.js +2 -2
  119. package/dist/src/planner/building/delete.js.map +1 -1
  120. package/dist/src/planner/building/dml-target.d.ts +2 -2
  121. package/dist/src/planner/building/dml-target.js +2 -2
  122. package/dist/src/planner/building/insert.d.ts.map +1 -1
  123. package/dist/src/planner/building/insert.js +53 -2
  124. package/dist/src/planner/building/insert.js.map +1 -1
  125. package/dist/src/planner/building/schema-resolution.d.ts +3 -0
  126. package/dist/src/planner/building/schema-resolution.d.ts.map +1 -1
  127. package/dist/src/planner/building/schema-resolution.js +4 -5
  128. package/dist/src/planner/building/schema-resolution.js.map +1 -1
  129. package/dist/src/planner/building/select.d.ts.map +1 -1
  130. package/dist/src/planner/building/select.js +18 -4
  131. package/dist/src/planner/building/select.js.map +1 -1
  132. package/dist/src/planner/building/update.js +2 -2
  133. package/dist/src/planner/building/update.js.map +1 -1
  134. package/dist/src/planner/building/view-mutation-builder.js +7 -7
  135. package/dist/src/planner/building/view-mutation-builder.js.map +1 -1
  136. package/dist/src/planner/building/with.d.ts.map +1 -1
  137. package/dist/src/planner/building/with.js +5 -8
  138. package/dist/src/planner/building/with.js.map +1 -1
  139. package/dist/src/planner/cache/materialization-advisory.d.ts +34 -2
  140. package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
  141. package/dist/src/planner/cache/materialization-advisory.js +185 -42
  142. package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
  143. package/dist/src/planner/cache/reference-graph.d.ts +14 -5
  144. package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
  145. package/dist/src/planner/cache/reference-graph.js +31 -39
  146. package/dist/src/planner/cache/reference-graph.js.map +1 -1
  147. package/dist/src/planner/cost/index.d.ts +30 -5
  148. package/dist/src/planner/cost/index.d.ts.map +1 -1
  149. package/dist/src/planner/cost/index.js +26 -0
  150. package/dist/src/planner/cost/index.js.map +1 -1
  151. package/dist/src/planner/debug.d.ts +3 -2
  152. package/dist/src/planner/debug.d.ts.map +1 -1
  153. package/dist/src/planner/debug.js +9 -3
  154. package/dist/src/planner/debug.js.map +1 -1
  155. package/dist/src/planner/framework/characteristics.d.ts +46 -19
  156. package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
  157. package/dist/src/planner/framework/characteristics.js +50 -134
  158. package/dist/src/planner/framework/characteristics.js.map +1 -1
  159. package/dist/src/planner/framework/context.d.ts +8 -44
  160. package/dist/src/planner/framework/context.d.ts.map +1 -1
  161. package/dist/src/planner/framework/context.js +0 -91
  162. package/dist/src/planner/framework/context.js.map +1 -1
  163. package/dist/src/planner/framework/pass.d.ts +7 -0
  164. package/dist/src/planner/framework/pass.d.ts.map +1 -1
  165. package/dist/src/planner/framework/pass.js +84 -0
  166. package/dist/src/planner/framework/pass.js.map +1 -1
  167. package/dist/src/planner/framework/registry.d.ts +13 -42
  168. package/dist/src/planner/framework/registry.d.ts.map +1 -1
  169. package/dist/src/planner/framework/registry.js +14 -182
  170. package/dist/src/planner/framework/registry.js.map +1 -1
  171. package/dist/src/planner/mutation/backward-body.d.ts +2 -2
  172. package/dist/src/planner/mutation/cte-flatten.js +1 -1
  173. package/dist/src/planner/mutation/decomposition.d.ts +2 -2
  174. package/dist/src/planner/mutation/decomposition.js +1 -1
  175. package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -1
  176. package/dist/src/planner/mutation/lens-enforcement.js +2 -1
  177. package/dist/src/planner/mutation/lens-enforcement.js.map +1 -1
  178. package/dist/src/planner/mutation/multi-source.d.ts +8 -8
  179. package/dist/src/planner/mutation/multi-source.js +11 -11
  180. package/dist/src/planner/mutation/multi-source.js.map +1 -1
  181. package/dist/src/planner/mutation/propagate.d.ts +1 -1
  182. package/dist/src/planner/mutation/propagate.js +1 -1
  183. package/dist/src/planner/mutation/scope-transform.d.ts +2 -2
  184. package/dist/src/planner/mutation/scope-transform.js +3 -3
  185. package/dist/src/planner/mutation/scope-transform.js.map +1 -1
  186. package/dist/src/planner/mutation/set-op.d.ts +1 -1
  187. package/dist/src/planner/mutation/set-op.d.ts.map +1 -1
  188. package/dist/src/planner/mutation/set-op.js +3 -2
  189. package/dist/src/planner/mutation/set-op.js.map +1 -1
  190. package/dist/src/planner/mutation/single-source.d.ts +3 -3
  191. package/dist/src/planner/mutation/single-source.js +9 -9
  192. package/dist/src/planner/mutation/single-source.js.map +1 -1
  193. package/dist/src/planner/nodes/aggregate-function.d.ts +3 -1
  194. package/dist/src/planner/nodes/aggregate-function.d.ts.map +1 -1
  195. package/dist/src/planner/nodes/aggregate-function.js +1 -0
  196. package/dist/src/planner/nodes/aggregate-function.js.map +1 -1
  197. package/dist/src/planner/nodes/aggregate-node.d.ts +1 -0
  198. package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
  199. package/dist/src/planner/nodes/aggregate-node.js +8 -1
  200. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  201. package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
  202. package/dist/src/planner/nodes/alias-node.js +3 -1
  203. package/dist/src/planner/nodes/alias-node.js.map +1 -1
  204. package/dist/src/planner/nodes/asof-scan-node.d.ts.map +1 -1
  205. package/dist/src/planner/nodes/asof-scan-node.js +3 -2
  206. package/dist/src/planner/nodes/asof-scan-node.js.map +1 -1
  207. package/dist/src/planner/nodes/asserted-keys-node.d.ts +1 -1
  208. package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -1
  209. package/dist/src/planner/nodes/asserted-keys-node.js +5 -2
  210. package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -1
  211. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  212. package/dist/src/planner/nodes/async-gather-node.js +3 -1
  213. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  214. package/dist/src/planner/nodes/block.js +3 -3
  215. package/dist/src/planner/nodes/block.js.map +1 -1
  216. package/dist/src/planner/nodes/bloom-join-node.d.ts +2 -0
  217. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  218. package/dist/src/planner/nodes/bloom-join-node.js +5 -1
  219. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  220. package/dist/src/planner/nodes/cache-node.d.ts +19 -2
  221. package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
  222. package/dist/src/planner/nodes/cache-node.js +43 -3
  223. package/dist/src/planner/nodes/cache-node.js.map +1 -1
  224. package/dist/src/planner/nodes/cte-node.d.ts +16 -1
  225. package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
  226. package/dist/src/planner/nodes/cte-node.js +16 -3
  227. package/dist/src/planner/nodes/cte-node.js.map +1 -1
  228. package/dist/src/planner/nodes/distinct-node.js +3 -3
  229. package/dist/src/planner/nodes/distinct-node.js.map +1 -1
  230. package/dist/src/planner/nodes/dml-executor-node.d.ts +21 -0
  231. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  232. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  233. package/dist/src/planner/nodes/envelope-scan-node.d.ts +1 -1
  234. package/dist/src/planner/nodes/envelope-scan-node.js +1 -1
  235. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
  236. package/dist/src/planner/nodes/fanout-lookup-join-node.js +4 -2
  237. package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
  238. package/dist/src/planner/nodes/filter.d.ts +10 -2
  239. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  240. package/dist/src/planner/nodes/filter.js +81 -14
  241. package/dist/src/planner/nodes/filter.js.map +1 -1
  242. package/dist/src/planner/nodes/hash-aggregate.d.ts +12 -1
  243. package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
  244. package/dist/src/planner/nodes/hash-aggregate.js +28 -1
  245. package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
  246. package/dist/src/planner/nodes/internal-recursive-cte-ref-node.d.ts +3 -1
  247. package/dist/src/planner/nodes/internal-recursive-cte-ref-node.d.ts.map +1 -1
  248. package/dist/src/planner/nodes/internal-recursive-cte-ref-node.js +1 -0
  249. package/dist/src/planner/nodes/internal-recursive-cte-ref-node.js.map +1 -1
  250. package/dist/src/planner/nodes/join-node.d.ts +3 -1
  251. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  252. package/dist/src/planner/nodes/join-node.js +5 -5
  253. package/dist/src/planner/nodes/join-node.js.map +1 -1
  254. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -1
  255. package/dist/src/planner/nodes/lens-auxiliary-access-node.js +4 -1
  256. package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -1
  257. package/dist/src/planner/nodes/limit-offset.d.ts +1 -0
  258. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  259. package/dist/src/planner/nodes/limit-offset.js +4 -3
  260. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  261. package/dist/src/planner/nodes/merge-join-node.d.ts +2 -0
  262. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  263. package/dist/src/planner/nodes/merge-join-node.js +6 -1
  264. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  265. package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
  266. package/dist/src/planner/nodes/ordinal-slice-node.js +3 -1
  267. package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
  268. package/dist/src/planner/nodes/plan-node.d.ts +68 -5
  269. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  270. package/dist/src/planner/nodes/plan-node.js +137 -24
  271. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  272. package/dist/src/planner/nodes/project-node.d.ts +3 -2
  273. package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
  274. package/dist/src/planner/nodes/project-node.js +1 -0
  275. package/dist/src/planner/nodes/project-node.js.map +1 -1
  276. package/dist/src/planner/nodes/recursive-cte-node.d.ts +30 -1
  277. package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
  278. package/dist/src/planner/nodes/recursive-cte-node.js +38 -5
  279. package/dist/src/planner/nodes/recursive-cte-node.js.map +1 -1
  280. package/dist/src/planner/nodes/reference.d.ts +5 -1
  281. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  282. package/dist/src/planner/nodes/reference.js +3 -0
  283. package/dist/src/planner/nodes/reference.js.map +1 -1
  284. package/dist/src/planner/nodes/remote-query-node.d.ts.map +1 -1
  285. package/dist/src/planner/nodes/remote-query-node.js +3 -1
  286. package/dist/src/planner/nodes/remote-query-node.js.map +1 -1
  287. package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
  288. package/dist/src/planner/nodes/retrieve-node.js +3 -1
  289. package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
  290. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  291. package/dist/src/planner/nodes/set-operation-node.js +3 -1
  292. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  293. package/dist/src/planner/nodes/sink-node.d.ts.map +1 -1
  294. package/dist/src/planner/nodes/sink-node.js +3 -1
  295. package/dist/src/planner/nodes/sink-node.js.map +1 -1
  296. package/dist/src/planner/nodes/sort.d.ts +2 -1
  297. package/dist/src/planner/nodes/sort.d.ts.map +1 -1
  298. package/dist/src/planner/nodes/sort.js +9 -5
  299. package/dist/src/planner/nodes/sort.js.map +1 -1
  300. package/dist/src/planner/nodes/stream-aggregate.d.ts +12 -1
  301. package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
  302. package/dist/src/planner/nodes/stream-aggregate.js +32 -3
  303. package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
  304. package/dist/src/planner/nodes/table-access-nodes.d.ts +2 -1
  305. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  306. package/dist/src/planner/nodes/table-access-nodes.js +2 -0
  307. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  308. package/dist/src/planner/nodes/view-mutation-node.d.ts +2 -2
  309. package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -1
  310. package/dist/src/planner/nodes/view-mutation-node.js +5 -1
  311. package/dist/src/planner/nodes/view-mutation-node.js.map +1 -1
  312. package/dist/src/planner/nodes/window-function.d.ts +3 -1
  313. package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
  314. package/dist/src/planner/nodes/window-function.js +1 -0
  315. package/dist/src/planner/nodes/window-function.js.map +1 -1
  316. package/dist/src/planner/optimizer.d.ts +52 -8
  317. package/dist/src/planner/optimizer.d.ts.map +1 -1
  318. package/dist/src/planner/optimizer.js +1014 -895
  319. package/dist/src/planner/optimizer.js.map +1 -1
  320. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  321. package/dist/src/planner/rules/access/rule-select-access-path.js +235 -202
  322. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  323. package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
  324. package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
  325. package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
  326. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +7 -0
  327. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +1 -1
  328. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +14 -1
  329. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +1 -1
  330. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +2 -2
  331. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
  332. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +55 -56
  333. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
  334. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
  335. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
  336. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
  337. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
  338. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
  339. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
  340. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
  341. package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts +1 -1
  342. package/dist/src/planner/rules/cache/rule-scalar-cse.js +1 -1
  343. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +29 -0
  344. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
  345. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +66 -0
  346. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
  347. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts +1 -1
  348. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +1 -1
  349. package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
  350. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
  351. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  352. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +1 -1
  353. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +1 -1
  354. package/dist/src/planner/rules/join/rule-join-elimination.d.ts +1 -1
  355. package/dist/src/planner/rules/join/rule-join-elimination.js +1 -1
  356. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +5 -6
  357. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
  358. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts +1 -1
  359. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +1 -1
  360. package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts +2 -2
  361. package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
  362. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +4 -3
  363. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
  364. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts +19 -0
  365. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -0
  366. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +48 -0
  367. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -0
  368. package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js +12 -1
  369. package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js.map +1 -1
  370. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  371. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +22 -20
  372. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  373. package/dist/src/planner/rules/shared/index-style-context.d.ts +39 -0
  374. package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -0
  375. package/dist/src/planner/rules/shared/index-style-context.js +26 -0
  376. package/dist/src/planner/rules/shared/index-style-context.js.map +1 -0
  377. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +1 -1
  378. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +1 -1
  379. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts +1 -1
  380. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +1 -1
  381. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
  382. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
  383. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +623 -0
  384. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
  385. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
  386. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  387. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
  388. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  389. package/dist/src/planner/stats/analyze.d.ts.map +1 -1
  390. package/dist/src/planner/stats/analyze.js +2 -20
  391. package/dist/src/planner/stats/analyze.js.map +1 -1
  392. package/dist/src/planner/stats/histogram.d.ts.map +1 -1
  393. package/dist/src/planner/stats/histogram.js +4 -0
  394. package/dist/src/planner/stats/histogram.js.map +1 -1
  395. package/dist/src/planner/util/fd-utils.d.ts +2 -3
  396. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  397. package/dist/src/planner/util/fd-utils.js +2 -3
  398. package/dist/src/planner/util/fd-utils.js.map +1 -1
  399. package/dist/src/planner/validation/plan-validator.d.ts +16 -0
  400. package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
  401. package/dist/src/planner/validation/plan-validator.js +36 -0
  402. package/dist/src/planner/validation/plan-validator.js.map +1 -1
  403. package/dist/src/runtime/async-util.d.ts +26 -0
  404. package/dist/src/runtime/async-util.d.ts.map +1 -1
  405. package/dist/src/runtime/async-util.js +139 -55
  406. package/dist/src/runtime/async-util.js.map +1 -1
  407. package/dist/src/runtime/cache/shared-cache.d.ts +7 -0
  408. package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
  409. package/dist/src/runtime/cache/shared-cache.js +34 -1
  410. package/dist/src/runtime/cache/shared-cache.js.map +1 -1
  411. package/dist/src/runtime/context-helpers.d.ts +32 -4
  412. package/dist/src/runtime/context-helpers.d.ts.map +1 -1
  413. package/dist/src/runtime/context-helpers.js +26 -9
  414. package/dist/src/runtime/context-helpers.js.map +1 -1
  415. package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
  416. package/dist/src/runtime/deferred-constraint-queue.js +2 -1
  417. package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
  418. package/dist/src/runtime/emission-context.d.ts +27 -3
  419. package/dist/src/runtime/emission-context.d.ts.map +1 -1
  420. package/dist/src/runtime/emission-context.js +38 -7
  421. package/dist/src/runtime/emission-context.js.map +1 -1
  422. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  423. package/dist/src/runtime/emit/add-constraint.js +7 -1
  424. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  425. package/dist/src/runtime/emit/aggregate.d.ts +2 -3
  426. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  427. package/dist/src/runtime/emit/aggregate.js +23 -28
  428. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  429. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  430. package/dist/src/runtime/emit/alter-table.js +98 -9
  431. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  432. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  433. package/dist/src/runtime/emit/analyze.js +2 -1
  434. package/dist/src/runtime/emit/analyze.js.map +1 -1
  435. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  436. package/dist/src/runtime/emit/asof-scan.js +13 -10
  437. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  438. package/dist/src/runtime/emit/async-gather.d.ts.map +1 -1
  439. package/dist/src/runtime/emit/async-gather.js +4 -3
  440. package/dist/src/runtime/emit/async-gather.js.map +1 -1
  441. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  442. package/dist/src/runtime/emit/between.js +2 -1
  443. package/dist/src/runtime/emit/between.js.map +1 -1
  444. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  445. package/dist/src/runtime/emit/binary.js +122 -14
  446. package/dist/src/runtime/emit/binary.js.map +1 -1
  447. package/dist/src/runtime/emit/block.d.ts.map +1 -1
  448. package/dist/src/runtime/emit/block.js +2 -1
  449. package/dist/src/runtime/emit/block.js.map +1 -1
  450. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  451. package/dist/src/runtime/emit/bloom-join.js +20 -8
  452. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  453. package/dist/src/runtime/emit/cache.d.ts +7 -3
  454. package/dist/src/runtime/emit/cache.d.ts.map +1 -1
  455. package/dist/src/runtime/emit/cache.js +25 -7
  456. package/dist/src/runtime/emit/cache.js.map +1 -1
  457. package/dist/src/runtime/emit/case.d.ts.map +1 -1
  458. package/dist/src/runtime/emit/case.js +66 -45
  459. package/dist/src/runtime/emit/case.js.map +1 -1
  460. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  461. package/dist/src/runtime/emit/cast.js +2 -1
  462. package/dist/src/runtime/emit/cast.js.map +1 -1
  463. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  464. package/dist/src/runtime/emit/constraint-check.js +13 -7
  465. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  466. package/dist/src/runtime/emit/create-assertion.d.ts.map +1 -1
  467. package/dist/src/runtime/emit/create-assertion.js +2 -1
  468. package/dist/src/runtime/emit/create-assertion.js.map +1 -1
  469. package/dist/src/runtime/emit/create-index.d.ts.map +1 -1
  470. package/dist/src/runtime/emit/create-index.js +12 -1
  471. package/dist/src/runtime/emit/create-index.js.map +1 -1
  472. package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
  473. package/dist/src/runtime/emit/create-table.js +12 -1
  474. package/dist/src/runtime/emit/create-table.js.map +1 -1
  475. package/dist/src/runtime/emit/cte-reference.d.ts.map +1 -1
  476. package/dist/src/runtime/emit/cte-reference.js +2 -1
  477. package/dist/src/runtime/emit/cte-reference.js.map +1 -1
  478. package/dist/src/runtime/emit/cte.d.ts.map +1 -1
  479. package/dist/src/runtime/emit/cte.js +45 -21
  480. package/dist/src/runtime/emit/cte.js.map +1 -1
  481. package/dist/src/runtime/emit/ddl-transaction-policy.d.ts +50 -0
  482. package/dist/src/runtime/emit/ddl-transaction-policy.d.ts.map +1 -0
  483. package/dist/src/runtime/emit/ddl-transaction-policy.js +69 -0
  484. package/dist/src/runtime/emit/ddl-transaction-policy.js.map +1 -0
  485. package/dist/src/runtime/emit/delete.d.ts.map +1 -1
  486. package/dist/src/runtime/emit/delete.js +2 -1
  487. package/dist/src/runtime/emit/delete.js.map +1 -1
  488. package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
  489. package/dist/src/runtime/emit/distinct.js +2 -1
  490. package/dist/src/runtime/emit/distinct.js.map +1 -1
  491. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  492. package/dist/src/runtime/emit/dml-executor.js +138 -74
  493. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  494. package/dist/src/runtime/emit/drop-assertion.d.ts.map +1 -1
  495. package/dist/src/runtime/emit/drop-assertion.js +2 -1
  496. package/dist/src/runtime/emit/drop-assertion.js.map +1 -1
  497. package/dist/src/runtime/emit/drop-index.d.ts.map +1 -1
  498. package/dist/src/runtime/emit/drop-index.js +17 -1
  499. package/dist/src/runtime/emit/drop-index.js.map +1 -1
  500. package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
  501. package/dist/src/runtime/emit/drop-table.js +11 -1
  502. package/dist/src/runtime/emit/drop-table.js.map +1 -1
  503. package/dist/src/runtime/emit/eager-prefetch.d.ts.map +1 -1
  504. package/dist/src/runtime/emit/eager-prefetch.js +2 -1
  505. package/dist/src/runtime/emit/eager-prefetch.js.map +1 -1
  506. package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -1
  507. package/dist/src/runtime/emit/envelope-scan.js +2 -1
  508. package/dist/src/runtime/emit/envelope-scan.js.map +1 -1
  509. package/dist/src/runtime/emit/fanout-lookup-join.d.ts.map +1 -1
  510. package/dist/src/runtime/emit/fanout-lookup-join.js +3 -2
  511. package/dist/src/runtime/emit/fanout-lookup-join.js.map +1 -1
  512. package/dist/src/runtime/emit/filter.d.ts.map +1 -1
  513. package/dist/src/runtime/emit/filter.js +8 -3
  514. package/dist/src/runtime/emit/filter.js.map +1 -1
  515. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  516. package/dist/src/runtime/emit/hash-aggregate.js +16 -11
  517. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  518. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  519. package/dist/src/runtime/emit/insert.js +2 -1
  520. package/dist/src/runtime/emit/insert.js.map +1 -1
  521. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  522. package/dist/src/runtime/emit/join.js +20 -6
  523. package/dist/src/runtime/emit/join.js.map +1 -1
  524. package/dist/src/runtime/emit/limit-offset.d.ts.map +1 -1
  525. package/dist/src/runtime/emit/limit-offset.js +2 -1
  526. package/dist/src/runtime/emit/limit-offset.js.map +1 -1
  527. package/dist/src/runtime/emit/literal.d.ts.map +1 -1
  528. package/dist/src/runtime/emit/literal.js +2 -1
  529. package/dist/src/runtime/emit/literal.js.map +1 -1
  530. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +22 -0
  531. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  532. package/dist/src/runtime/emit/materialized-view-helpers.js +128 -16
  533. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  534. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  535. package/dist/src/runtime/emit/materialized-view.js +6 -0
  536. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  537. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  538. package/dist/src/runtime/emit/merge-join.js +14 -4
  539. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  540. package/dist/src/runtime/emit/ordinal-slice.d.ts.map +1 -1
  541. package/dist/src/runtime/emit/ordinal-slice.js +2 -1
  542. package/dist/src/runtime/emit/ordinal-slice.js.map +1 -1
  543. package/dist/src/runtime/emit/pragma.d.ts.map +1 -1
  544. package/dist/src/runtime/emit/pragma.js +2 -1
  545. package/dist/src/runtime/emit/pragma.js.map +1 -1
  546. package/dist/src/runtime/emit/project.d.ts.map +1 -1
  547. package/dist/src/runtime/emit/project.js +7 -2
  548. package/dist/src/runtime/emit/project.js.map +1 -1
  549. package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
  550. package/dist/src/runtime/emit/recursive-cte.js +61 -2
  551. package/dist/src/runtime/emit/recursive-cte.js.map +1 -1
  552. package/dist/src/runtime/emit/returning.d.ts.map +1 -1
  553. package/dist/src/runtime/emit/returning.js +7 -2
  554. package/dist/src/runtime/emit/returning.js.map +1 -1
  555. package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
  556. package/dist/src/runtime/emit/scalar-function.js +2 -1
  557. package/dist/src/runtime/emit/scalar-function.js.map +1 -1
  558. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  559. package/dist/src/runtime/emit/scan.js +55 -17
  560. package/dist/src/runtime/emit/scan.js.map +1 -1
  561. package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
  562. package/dist/src/runtime/emit/schema-declarative.js +6 -5
  563. package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
  564. package/dist/src/runtime/emit/sequencing.d.ts.map +1 -1
  565. package/dist/src/runtime/emit/sequencing.js +2 -1
  566. package/dist/src/runtime/emit/sequencing.js.map +1 -1
  567. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  568. package/dist/src/runtime/emit/set-operation.js +6 -5
  569. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  570. package/dist/src/runtime/emit/sink.d.ts.map +1 -1
  571. package/dist/src/runtime/emit/sink.js +2 -1
  572. package/dist/src/runtime/emit/sink.js.map +1 -1
  573. package/dist/src/runtime/emit/sort.d.ts.map +1 -1
  574. package/dist/src/runtime/emit/sort.js +2 -1
  575. package/dist/src/runtime/emit/sort.js.map +1 -1
  576. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  577. package/dist/src/runtime/emit/subquery.js +42 -23
  578. package/dist/src/runtime/emit/subquery.js.map +1 -1
  579. package/dist/src/runtime/emit/table-valued-function.d.ts.map +1 -1
  580. package/dist/src/runtime/emit/table-valued-function.js +2 -1
  581. package/dist/src/runtime/emit/table-valued-function.js.map +1 -1
  582. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
  583. package/dist/src/runtime/emit/temporal-arithmetic.js +2 -1
  584. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
  585. package/dist/src/runtime/emit/transaction.d.ts.map +1 -1
  586. package/dist/src/runtime/emit/transaction.js +2 -1
  587. package/dist/src/runtime/emit/transaction.js.map +1 -1
  588. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  589. package/dist/src/runtime/emit/unary.js +2 -1
  590. package/dist/src/runtime/emit/unary.js.map +1 -1
  591. package/dist/src/runtime/emit/update.d.ts.map +1 -1
  592. package/dist/src/runtime/emit/update.js +2 -1
  593. package/dist/src/runtime/emit/update.js.map +1 -1
  594. package/dist/src/runtime/emit/values.d.ts.map +1 -1
  595. package/dist/src/runtime/emit/values.js +2 -1
  596. package/dist/src/runtime/emit/values.js.map +1 -1
  597. package/dist/src/runtime/emit/view-mutation.d.ts +1 -1
  598. package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -1
  599. package/dist/src/runtime/emit/view-mutation.js +6 -3
  600. package/dist/src/runtime/emit/view-mutation.js.map +1 -1
  601. package/dist/src/runtime/emit/window.d.ts.map +1 -1
  602. package/dist/src/runtime/emit/window.js +26 -9
  603. package/dist/src/runtime/emit/window.js.map +1 -1
  604. package/dist/src/runtime/emitters.d.ts +11 -3
  605. package/dist/src/runtime/emitters.d.ts.map +1 -1
  606. package/dist/src/runtime/emitters.js +13 -22
  607. package/dist/src/runtime/emitters.js.map +1 -1
  608. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  609. package/dist/src/runtime/foreign-key-actions.js +39 -13
  610. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  611. package/dist/src/runtime/parallel-driver.d.ts +32 -4
  612. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  613. package/dist/src/runtime/parallel-driver.js +88 -18
  614. package/dist/src/runtime/parallel-driver.js.map +1 -1
  615. package/dist/src/runtime/scheduler.d.ts +56 -7
  616. package/dist/src/runtime/scheduler.d.ts.map +1 -1
  617. package/dist/src/runtime/scheduler.js +223 -200
  618. package/dist/src/runtime/scheduler.js.map +1 -1
  619. package/dist/src/runtime/strict-flags.d.ts +22 -0
  620. package/dist/src/runtime/strict-flags.d.ts.map +1 -0
  621. package/dist/src/runtime/strict-flags.js +26 -0
  622. package/dist/src/runtime/strict-flags.js.map +1 -0
  623. package/dist/src/runtime/strict-fork.d.ts +4 -4
  624. package/dist/src/runtime/strict-fork.d.ts.map +1 -1
  625. package/dist/src/runtime/strict-fork.js +175 -31
  626. package/dist/src/runtime/strict-fork.js.map +1 -1
  627. package/dist/src/runtime/types.d.ts +102 -4
  628. package/dist/src/runtime/types.d.ts.map +1 -1
  629. package/dist/src/runtime/types.js +37 -0
  630. package/dist/src/runtime/types.js.map +1 -1
  631. package/dist/src/runtime/utils.d.ts.map +1 -1
  632. package/dist/src/runtime/utils.js +8 -17
  633. package/dist/src/runtime/utils.js.map +1 -1
  634. package/dist/src/schema/column.d.ts +8 -0
  635. package/dist/src/schema/column.d.ts.map +1 -1
  636. package/dist/src/schema/column.js.map +1 -1
  637. package/dist/src/schema/ddl-generator.js +5 -0
  638. package/dist/src/schema/ddl-generator.js.map +1 -1
  639. package/dist/src/schema/function.d.ts +79 -0
  640. package/dist/src/schema/function.d.ts.map +1 -1
  641. package/dist/src/schema/function.js.map +1 -1
  642. package/dist/src/schema/lens-compiler.js +2 -2
  643. package/dist/src/schema/lens-compiler.js.map +1 -1
  644. package/dist/src/schema/lens-prover.d.ts.map +1 -1
  645. package/dist/src/schema/lens-prover.js +2 -3
  646. package/dist/src/schema/lens-prover.js.map +1 -1
  647. package/dist/src/schema/lens.d.ts +1 -1
  648. package/dist/src/schema/manager.d.ts.map +1 -1
  649. package/dist/src/schema/manager.js +7 -2
  650. package/dist/src/schema/manager.js.map +1 -1
  651. package/dist/src/schema/rename-rewriter.d.ts +61 -0
  652. package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
  653. package/dist/src/schema/rename-rewriter.js +77 -0
  654. package/dist/src/schema/rename-rewriter.js.map +1 -1
  655. package/dist/src/schema/table.d.ts +36 -8
  656. package/dist/src/schema/table.d.ts.map +1 -1
  657. package/dist/src/schema/table.js +61 -11
  658. package/dist/src/schema/table.js.map +1 -1
  659. package/dist/src/schema/unique-enforcement.d.ts +28 -5
  660. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  661. package/dist/src/schema/unique-enforcement.js +37 -5
  662. package/dist/src/schema/unique-enforcement.js.map +1 -1
  663. package/dist/src/schema/view.d.ts +1 -1
  664. package/dist/src/schema/view.js +1 -1
  665. package/dist/src/types/json-type.d.ts.map +1 -1
  666. package/dist/src/types/json-type.js +18 -8
  667. package/dist/src/types/json-type.js.map +1 -1
  668. package/dist/src/types/logical-type.d.ts +26 -0
  669. package/dist/src/types/logical-type.d.ts.map +1 -1
  670. package/dist/src/types/logical-type.js.map +1 -1
  671. package/dist/src/util/comparison.d.ts +83 -59
  672. package/dist/src/util/comparison.d.ts.map +1 -1
  673. package/dist/src/util/comparison.js +191 -131
  674. package/dist/src/util/comparison.js.map +1 -1
  675. package/dist/src/util/json-canonical.d.ts +34 -0
  676. package/dist/src/util/json-canonical.d.ts.map +1 -0
  677. package/dist/src/util/json-canonical.js +32 -0
  678. package/dist/src/util/json-canonical.js.map +1 -0
  679. package/dist/src/util/key-serializer.d.ts +10 -3
  680. package/dist/src/util/key-serializer.d.ts.map +1 -1
  681. package/dist/src/util/key-serializer.js +43 -17
  682. package/dist/src/util/key-serializer.js.map +1 -1
  683. package/dist/src/util/key-tuple-codec.d.ts +37 -0
  684. package/dist/src/util/key-tuple-codec.d.ts.map +1 -0
  685. package/dist/src/util/key-tuple-codec.js +65 -0
  686. package/dist/src/util/key-tuple-codec.js.map +1 -0
  687. package/dist/src/util/latches.d.ts +29 -3
  688. package/dist/src/util/latches.d.ts.map +1 -1
  689. package/dist/src/util/latches.js +60 -6
  690. package/dist/src/util/latches.js.map +1 -1
  691. package/dist/src/util/patterns.d.ts +22 -8
  692. package/dist/src/util/patterns.d.ts.map +1 -1
  693. package/dist/src/util/patterns.js +87 -16
  694. package/dist/src/util/patterns.js.map +1 -1
  695. package/dist/src/util/qualified-name.d.ts +10 -0
  696. package/dist/src/util/qualified-name.d.ts.map +1 -0
  697. package/dist/src/util/qualified-name.js +15 -0
  698. package/dist/src/util/qualified-name.js.map +1 -0
  699. package/dist/src/vtab/backing-host.d.ts +8 -1
  700. package/dist/src/vtab/backing-host.d.ts.map +1 -1
  701. package/dist/src/vtab/backing-host.js +7 -0
  702. package/dist/src/vtab/backing-host.js.map +1 -1
  703. package/dist/src/vtab/best-access-plan.d.ts +18 -0
  704. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  705. package/dist/src/vtab/best-access-plan.js +15 -0
  706. package/dist/src/vtab/best-access-plan.js.map +1 -1
  707. package/dist/src/vtab/capabilities.d.ts +44 -0
  708. package/dist/src/vtab/capabilities.d.ts.map +1 -1
  709. package/dist/src/vtab/filter-info.d.ts +42 -0
  710. package/dist/src/vtab/filter-info.d.ts.map +1 -1
  711. package/dist/src/vtab/filter-info.js +137 -1
  712. package/dist/src/vtab/filter-info.js.map +1 -1
  713. package/dist/src/vtab/idx-str.d.ts +62 -0
  714. package/dist/src/vtab/idx-str.d.ts.map +1 -0
  715. package/dist/src/vtab/idx-str.js +130 -0
  716. package/dist/src/vtab/idx-str.js.map +1 -0
  717. package/dist/src/vtab/index-descriptor.d.ts +122 -0
  718. package/dist/src/vtab/index-descriptor.d.ts.map +1 -0
  719. package/dist/src/vtab/index-descriptor.js +104 -0
  720. package/dist/src/vtab/index-descriptor.js.map +1 -0
  721. package/dist/src/vtab/mapping-advertisement.d.ts +1 -1
  722. package/dist/src/vtab/memory/index.d.ts +12 -4
  723. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  724. package/dist/src/vtab/memory/index.js +17 -9
  725. package/dist/src/vtab/memory/index.js.map +1 -1
  726. package/dist/src/vtab/memory/layer/base.d.ts +85 -31
  727. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  728. package/dist/src/vtab/memory/layer/base.js +127 -86
  729. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  730. package/dist/src/vtab/memory/layer/connection.d.ts +27 -0
  731. package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
  732. package/dist/src/vtab/memory/layer/connection.js +36 -1
  733. package/dist/src/vtab/memory/layer/connection.js.map +1 -1
  734. package/dist/src/vtab/memory/layer/interface.d.ts +24 -4
  735. package/dist/src/vtab/memory/layer/interface.d.ts.map +1 -1
  736. package/dist/src/vtab/memory/layer/manager.d.ts +212 -16
  737. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  738. package/dist/src/vtab/memory/layer/manager.js +754 -170
  739. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  740. package/dist/src/vtab/memory/layer/plan-filter.d.ts +26 -1
  741. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  742. package/dist/src/vtab/memory/layer/plan-filter.js +29 -11
  743. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  744. package/dist/src/vtab/memory/layer/safe-iterate.d.ts +2 -2
  745. package/dist/src/vtab/memory/layer/safe-iterate.d.ts.map +1 -1
  746. package/dist/src/vtab/memory/layer/safe-iterate.js +2 -2
  747. package/dist/src/vtab/memory/layer/safe-iterate.js.map +1 -1
  748. package/dist/src/vtab/memory/layer/scan-layer.d.ts +5 -1
  749. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  750. package/dist/src/vtab/memory/layer/scan-layer.js +59 -24
  751. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  752. package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
  753. package/dist/src/vtab/memory/layer/scan-plan.js +13 -21
  754. package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
  755. package/dist/src/vtab/memory/layer/transaction.d.ts +169 -7
  756. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  757. package/dist/src/vtab/memory/layer/transaction.js +334 -9
  758. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  759. package/dist/src/vtab/memory/module.d.ts +11 -4
  760. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  761. package/dist/src/vtab/memory/module.js +35 -16
  762. package/dist/src/vtab/memory/module.js.map +1 -1
  763. package/dist/src/vtab/memory/table.d.ts +30 -4
  764. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  765. package/dist/src/vtab/memory/table.js +63 -15
  766. package/dist/src/vtab/memory/table.js.map +1 -1
  767. package/dist/src/vtab/memory/utils/predicate.d.ts +9 -1
  768. package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
  769. package/dist/src/vtab/memory/utils/predicate.js +35 -14
  770. package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
  771. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -1
  772. package/dist/src/vtab/memory/utils/primary-key-encode.js +12 -6
  773. package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -1
  774. package/dist/src/vtab/memory/utils/primary-key.d.ts +6 -1
  775. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  776. package/dist/src/vtab/memory/utils/primary-key.js +12 -8
  777. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  778. package/dist/src/vtab/module.d.ts +38 -14
  779. package/dist/src/vtab/module.d.ts.map +1 -1
  780. package/dist/src/vtab/table.d.ts +41 -2
  781. package/dist/src/vtab/table.d.ts.map +1 -1
  782. package/dist/src/vtab/table.js +19 -0
  783. package/dist/src/vtab/table.js.map +1 -1
  784. package/package.json +5 -4
  785. package/dist/src/planner/rules/cache/rule-materialization-advisory.d.ts +0 -18
  786. package/dist/src/planner/rules/cache/rule-materialization-advisory.d.ts.map +0 -1
  787. package/dist/src/planner/rules/cache/rule-materialization-advisory.js +0 -65
  788. 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 { applyRules } from './framework/registry.js';
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 { ruleSubqueryDecorrelation } from './rules/subquery/rule-subquery-decorrelation.js';
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 rules with their appropriate passes
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
- // Materialized-view query rewrite (read side). Registered FIRST in the
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
  */