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