@quereus/quereus 4.3.1 → 4.3.2

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