@quereus/quereus 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (900) hide show
  1. package/README.md +7 -0
  2. package/dist/src/common/datatype.d.ts +12 -0
  3. package/dist/src/common/datatype.d.ts.map +1 -1
  4. package/dist/src/common/datatype.js.map +1 -1
  5. package/dist/src/common/types.d.ts +24 -0
  6. package/dist/src/common/types.d.ts.map +1 -1
  7. package/dist/src/common/types.js.map +1 -1
  8. package/dist/src/core/database-assertions.d.ts +37 -9
  9. package/dist/src/core/database-assertions.d.ts.map +1 -1
  10. package/dist/src/core/database-assertions.js +62 -110
  11. package/dist/src/core/database-assertions.js.map +1 -1
  12. package/dist/src/core/database-events.d.ts +163 -0
  13. package/dist/src/core/database-events.d.ts.map +1 -1
  14. package/dist/src/core/database-events.js +235 -21
  15. package/dist/src/core/database-events.js.map +1 -1
  16. package/dist/src/core/database-external-changes.d.ts +28 -0
  17. package/dist/src/core/database-external-changes.d.ts.map +1 -0
  18. package/dist/src/core/database-external-changes.js +242 -0
  19. package/dist/src/core/database-external-changes.js.map +1 -0
  20. package/dist/src/core/database-internal.d.ts +50 -1
  21. package/dist/src/core/database-internal.d.ts.map +1 -1
  22. package/dist/src/core/database-materialized-views.d.ts +1253 -0
  23. package/dist/src/core/database-materialized-views.d.ts.map +1 -0
  24. package/dist/src/core/database-materialized-views.js +3064 -0
  25. package/dist/src/core/database-materialized-views.js.map +1 -0
  26. package/dist/src/core/database-options.d.ts +4 -0
  27. package/dist/src/core/database-options.d.ts.map +1 -1
  28. package/dist/src/core/database-options.js +10 -0
  29. package/dist/src/core/database-options.js.map +1 -1
  30. package/dist/src/core/database-transaction.d.ts +19 -3
  31. package/dist/src/core/database-transaction.d.ts.map +1 -1
  32. package/dist/src/core/database-transaction.js +30 -3
  33. package/dist/src/core/database-transaction.js.map +1 -1
  34. package/dist/src/core/database-watchers.d.ts +19 -0
  35. package/dist/src/core/database-watchers.d.ts.map +1 -1
  36. package/dist/src/core/database-watchers.js +63 -3
  37. package/dist/src/core/database-watchers.js.map +1 -1
  38. package/dist/src/core/database.d.ts +203 -11
  39. package/dist/src/core/database.d.ts.map +1 -1
  40. package/dist/src/core/database.js +493 -29
  41. package/dist/src/core/database.js.map +1 -1
  42. package/dist/src/core/derived-row-validator.d.ts +137 -0
  43. package/dist/src/core/derived-row-validator.d.ts.map +1 -0
  44. package/dist/src/core/derived-row-validator.js +314 -0
  45. package/dist/src/core/derived-row-validator.js.map +1 -0
  46. package/dist/src/core/statement.d.ts.map +1 -1
  47. package/dist/src/core/statement.js +30 -9
  48. package/dist/src/core/statement.js.map +1 -1
  49. package/dist/src/emit/ast-stringify.d.ts +135 -1
  50. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  51. package/dist/src/emit/ast-stringify.js +793 -118
  52. package/dist/src/emit/ast-stringify.js.map +1 -1
  53. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  54. package/dist/src/func/builtins/aggregate.js +11 -10
  55. package/dist/src/func/builtins/aggregate.js.map +1 -1
  56. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  57. package/dist/src/func/builtins/builtin-window-functions.js +32 -0
  58. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  59. package/dist/src/func/builtins/explain.d.ts +3 -0
  60. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  61. package/dist/src/func/builtins/explain.js +229 -0
  62. package/dist/src/func/builtins/explain.js.map +1 -1
  63. package/dist/src/func/builtins/index.d.ts.map +1 -1
  64. package/dist/src/func/builtins/index.js +10 -2
  65. package/dist/src/func/builtins/index.js.map +1 -1
  66. package/dist/src/func/builtins/json.d.ts.map +1 -1
  67. package/dist/src/func/builtins/json.js +3 -2
  68. package/dist/src/func/builtins/json.js.map +1 -1
  69. package/dist/src/func/builtins/mutation.d.ts +2 -0
  70. package/dist/src/func/builtins/mutation.d.ts.map +1 -0
  71. package/dist/src/func/builtins/mutation.js +53 -0
  72. package/dist/src/func/builtins/mutation.js.map +1 -0
  73. package/dist/src/func/builtins/schema.d.ts +2 -0
  74. package/dist/src/func/builtins/schema.d.ts.map +1 -1
  75. package/dist/src/func/builtins/schema.js +713 -26
  76. package/dist/src/func/builtins/schema.js.map +1 -1
  77. package/dist/src/func/builtins/string.js +1 -1
  78. package/dist/src/func/builtins/string.js.map +1 -1
  79. package/dist/src/func/registration.d.ts +9 -0
  80. package/dist/src/func/registration.d.ts.map +1 -1
  81. package/dist/src/func/registration.js +4 -0
  82. package/dist/src/func/registration.js.map +1 -1
  83. package/dist/src/index.d.ts +25 -6
  84. package/dist/src/index.d.ts.map +1 -1
  85. package/dist/src/index.js +27 -3
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/parser/ast.d.ts +353 -21
  88. package/dist/src/parser/ast.d.ts.map +1 -1
  89. package/dist/src/parser/index.d.ts +14 -1
  90. package/dist/src/parser/index.d.ts.map +1 -1
  91. package/dist/src/parser/index.js +19 -0
  92. package/dist/src/parser/index.js.map +1 -1
  93. package/dist/src/parser/lexer.d.ts +9 -0
  94. package/dist/src/parser/lexer.d.ts.map +1 -1
  95. package/dist/src/parser/lexer.js +9 -0
  96. package/dist/src/parser/lexer.js.map +1 -1
  97. package/dist/src/parser/parser.d.ts +276 -7
  98. package/dist/src/parser/parser.d.ts.map +1 -1
  99. package/dist/src/parser/parser.js +1387 -469
  100. package/dist/src/parser/parser.js.map +1 -1
  101. package/dist/src/parser/visitor.d.ts.map +1 -1
  102. package/dist/src/parser/visitor.js +12 -8
  103. package/dist/src/parser/visitor.js.map +1 -1
  104. package/dist/src/planner/analysis/assertion-classifier.d.ts.map +1 -1
  105. package/dist/src/planner/analysis/assertion-classifier.js +4 -0
  106. package/dist/src/planner/analysis/assertion-classifier.js.map +1 -1
  107. package/dist/src/planner/analysis/assertion-hoist-cache.d.ts.map +1 -1
  108. package/dist/src/planner/analysis/assertion-hoist-cache.js +8 -4
  109. package/dist/src/planner/analysis/assertion-hoist-cache.js.map +1 -1
  110. package/dist/src/planner/analysis/authored-inverse.d.ts +22 -0
  111. package/dist/src/planner/analysis/authored-inverse.d.ts.map +1 -0
  112. package/dist/src/planner/analysis/authored-inverse.js +267 -0
  113. package/dist/src/planner/analysis/authored-inverse.js.map +1 -0
  114. package/dist/src/planner/analysis/change-scope.d.ts +34 -4
  115. package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
  116. package/dist/src/planner/analysis/change-scope.js +108 -7
  117. package/dist/src/planner/analysis/change-scope.js.map +1 -1
  118. package/dist/src/planner/analysis/check-extraction.d.ts +36 -2
  119. package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
  120. package/dist/src/planner/analysis/check-extraction.js +174 -46
  121. package/dist/src/planner/analysis/check-extraction.js.map +1 -1
  122. package/dist/src/planner/analysis/coarsened-key.d.ts +109 -0
  123. package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -0
  124. package/dist/src/planner/analysis/coarsened-key.js +228 -0
  125. package/dist/src/planner/analysis/coarsened-key.js.map +1 -0
  126. package/dist/src/planner/analysis/comparison-collation.d.ts +216 -0
  127. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -0
  128. package/dist/src/planner/analysis/comparison-collation.js +341 -0
  129. package/dist/src/planner/analysis/comparison-collation.js.map +1 -0
  130. package/dist/src/planner/analysis/constraint-extractor.d.ts +3 -1
  131. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  132. package/dist/src/planner/analysis/constraint-extractor.js +192 -9
  133. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  134. package/dist/src/planner/analysis/coverage-prover.d.ts +321 -0
  135. package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -0
  136. package/dist/src/planner/analysis/coverage-prover.js +1038 -0
  137. package/dist/src/planner/analysis/coverage-prover.js.map +1 -0
  138. package/dist/src/planner/analysis/key-filter.d.ts +22 -0
  139. package/dist/src/planner/analysis/key-filter.d.ts.map +1 -0
  140. package/dist/src/planner/analysis/key-filter.js +105 -0
  141. package/dist/src/planner/analysis/key-filter.js.map +1 -0
  142. package/dist/src/planner/analysis/partial-unique-extraction.d.ts +36 -1
  143. package/dist/src/planner/analysis/partial-unique-extraction.d.ts.map +1 -1
  144. package/dist/src/planner/analysis/partial-unique-extraction.js +148 -22
  145. package/dist/src/planner/analysis/partial-unique-extraction.js.map +1 -1
  146. package/dist/src/planner/analysis/predicate-normalizer.d.ts.map +1 -1
  147. package/dist/src/planner/analysis/predicate-normalizer.js +30 -1
  148. package/dist/src/planner/analysis/predicate-normalizer.js.map +1 -1
  149. package/dist/src/planner/analysis/predicate-shape.d.ts +36 -1
  150. package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
  151. package/dist/src/planner/analysis/predicate-shape.js +51 -13
  152. package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
  153. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +314 -0
  154. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -0
  155. package/dist/src/planner/analysis/query-rewrite-matcher.js +1081 -0
  156. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -0
  157. package/dist/src/planner/analysis/scalar-invertibility.d.ts +92 -0
  158. package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -0
  159. package/dist/src/planner/analysis/scalar-invertibility.js +129 -0
  160. package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -0
  161. package/dist/src/planner/analysis/update-lineage.d.ts +196 -0
  162. package/dist/src/planner/analysis/update-lineage.d.ts.map +1 -0
  163. package/dist/src/planner/analysis/update-lineage.js +322 -0
  164. package/dist/src/planner/analysis/update-lineage.js.map +1 -0
  165. package/dist/src/planner/analysis/view-complement.d.ts +42 -0
  166. package/dist/src/planner/analysis/view-complement.d.ts.map +1 -0
  167. package/dist/src/planner/analysis/view-complement.js +54 -0
  168. package/dist/src/planner/analysis/view-complement.js.map +1 -0
  169. package/dist/src/planner/building/alter-table.d.ts +1 -1
  170. package/dist/src/planner/building/alter-table.d.ts.map +1 -1
  171. package/dist/src/planner/building/alter-table.js +211 -2
  172. package/dist/src/planner/building/alter-table.js.map +1 -1
  173. package/dist/src/planner/building/block.d.ts.map +1 -1
  174. package/dist/src/planner/building/block.js +18 -1
  175. package/dist/src/planner/building/block.js.map +1 -1
  176. package/dist/src/planner/building/constraint-builder.d.ts +33 -5
  177. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  178. package/dist/src/planner/building/constraint-builder.js +63 -28
  179. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  180. package/dist/src/planner/building/create-view.d.ts +9 -0
  181. package/dist/src/planner/building/create-view.d.ts.map +1 -1
  182. package/dist/src/planner/building/create-view.js +41 -12
  183. package/dist/src/planner/building/create-view.js.map +1 -1
  184. package/dist/src/planner/building/ddl.d.ts.map +1 -1
  185. package/dist/src/planner/building/ddl.js +94 -0
  186. package/dist/src/planner/building/ddl.js.map +1 -1
  187. package/dist/src/planner/building/declare-schema.d.ts +1 -0
  188. package/dist/src/planner/building/declare-schema.d.ts.map +1 -1
  189. package/dist/src/planner/building/declare-schema.js +4 -1
  190. package/dist/src/planner/building/declare-schema.js.map +1 -1
  191. package/dist/src/planner/building/default-scope.d.ts +26 -0
  192. package/dist/src/planner/building/default-scope.d.ts.map +1 -0
  193. package/dist/src/planner/building/default-scope.js +41 -0
  194. package/dist/src/planner/building/default-scope.js.map +1 -0
  195. package/dist/src/planner/building/delete.d.ts +19 -1
  196. package/dist/src/planner/building/delete.d.ts.map +1 -1
  197. package/dist/src/planner/building/delete.js +109 -30
  198. package/dist/src/planner/building/delete.js.map +1 -1
  199. package/dist/src/planner/building/dml-target.d.ts +118 -0
  200. package/dist/src/planner/building/dml-target.d.ts.map +1 -0
  201. package/dist/src/planner/building/dml-target.js +282 -0
  202. package/dist/src/planner/building/dml-target.js.map +1 -0
  203. package/dist/src/planner/building/drop-index.d.ts.map +1 -1
  204. package/dist/src/planner/building/drop-index.js +4 -1
  205. package/dist/src/planner/building/drop-index.js.map +1 -1
  206. package/dist/src/planner/building/drop-view.d.ts.map +1 -1
  207. package/dist/src/planner/building/drop-view.js +4 -2
  208. package/dist/src/planner/building/drop-view.js.map +1 -1
  209. package/dist/src/planner/building/expression.d.ts.map +1 -1
  210. package/dist/src/planner/building/expression.js +60 -21
  211. package/dist/src/planner/building/expression.js.map +1 -1
  212. package/dist/src/planner/building/foreign-key-builder.d.ts +30 -0
  213. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  214. package/dist/src/planner/building/foreign-key-builder.js +160 -129
  215. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  216. package/dist/src/planner/building/insert.d.ts +45 -2
  217. package/dist/src/planner/building/insert.d.ts.map +1 -1
  218. package/dist/src/planner/building/insert.js +257 -88
  219. package/dist/src/planner/building/insert.js.map +1 -1
  220. package/dist/src/planner/building/lens-auxiliary-access.d.ts +22 -0
  221. package/dist/src/planner/building/lens-auxiliary-access.d.ts.map +1 -0
  222. package/dist/src/planner/building/lens-auxiliary-access.js +132 -0
  223. package/dist/src/planner/building/lens-auxiliary-access.js.map +1 -0
  224. package/dist/src/planner/building/materialized-view.d.ts +16 -0
  225. package/dist/src/planner/building/materialized-view.d.ts.map +1 -0
  226. package/dist/src/planner/building/materialized-view.js +57 -0
  227. package/dist/src/planner/building/materialized-view.js.map +1 -0
  228. package/dist/src/planner/building/returning-star.d.ts +32 -0
  229. package/dist/src/planner/building/returning-star.d.ts.map +1 -0
  230. package/dist/src/planner/building/returning-star.js +45 -0
  231. package/dist/src/planner/building/returning-star.js.map +1 -0
  232. package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
  233. package/dist/src/planner/building/select-aggregates.js +47 -0
  234. package/dist/src/planner/building/select-aggregates.js.map +1 -1
  235. package/dist/src/planner/building/select-compound.d.ts.map +1 -1
  236. package/dist/src/planner/building/select-compound.js +84 -11
  237. package/dist/src/planner/building/select-compound.js.map +1 -1
  238. package/dist/src/planner/building/select-context.d.ts +10 -2
  239. package/dist/src/planner/building/select-context.d.ts.map +1 -1
  240. package/dist/src/planner/building/select-context.js +7 -1
  241. package/dist/src/planner/building/select-context.js.map +1 -1
  242. package/dist/src/planner/building/select-modifiers.js +6 -0
  243. package/dist/src/planner/building/select-modifiers.js.map +1 -1
  244. package/dist/src/planner/building/select-ordinal.d.ts +18 -0
  245. package/dist/src/planner/building/select-ordinal.d.ts.map +1 -1
  246. package/dist/src/planner/building/select-ordinal.js +30 -0
  247. package/dist/src/planner/building/select-ordinal.js.map +1 -1
  248. package/dist/src/planner/building/select-projections.d.ts +8 -2
  249. package/dist/src/planner/building/select-projections.d.ts.map +1 -1
  250. package/dist/src/planner/building/select-projections.js +26 -4
  251. package/dist/src/planner/building/select-projections.js.map +1 -1
  252. package/dist/src/planner/building/select-window.d.ts.map +1 -1
  253. package/dist/src/planner/building/select-window.js +8 -5
  254. package/dist/src/planner/building/select-window.js.map +1 -1
  255. package/dist/src/planner/building/select.d.ts.map +1 -1
  256. package/dist/src/planner/building/select.js +164 -59
  257. package/dist/src/planner/building/select.js.map +1 -1
  258. package/dist/src/planner/building/set-object-tags.d.ts +7 -0
  259. package/dist/src/planner/building/set-object-tags.d.ts.map +1 -0
  260. package/dist/src/planner/building/set-object-tags.js +38 -0
  261. package/dist/src/planner/building/set-object-tags.js.map +1 -0
  262. package/dist/src/planner/building/tag-diagnostics.d.ts +27 -0
  263. package/dist/src/planner/building/tag-diagnostics.d.ts.map +1 -0
  264. package/dist/src/planner/building/tag-diagnostics.js +37 -0
  265. package/dist/src/planner/building/tag-diagnostics.js.map +1 -0
  266. package/dist/src/planner/building/update.d.ts +18 -1
  267. package/dist/src/planner/building/update.d.ts.map +1 -1
  268. package/dist/src/planner/building/update.js +134 -58
  269. package/dist/src/planner/building/update.js.map +1 -1
  270. package/dist/src/planner/building/view-mutation-builder.d.ts +15 -0
  271. package/dist/src/planner/building/view-mutation-builder.d.ts.map +1 -0
  272. package/dist/src/planner/building/view-mutation-builder.js +1158 -0
  273. package/dist/src/planner/building/view-mutation-builder.js.map +1 -0
  274. package/dist/src/planner/building/with.d.ts +11 -0
  275. package/dist/src/planner/building/with.d.ts.map +1 -1
  276. package/dist/src/planner/building/with.js +48 -10
  277. package/dist/src/planner/building/with.js.map +1 -1
  278. package/dist/src/planner/cost/index.d.ts +83 -0
  279. package/dist/src/planner/cost/index.d.ts.map +1 -1
  280. package/dist/src/planner/cost/index.js +114 -0
  281. package/dist/src/planner/cost/index.js.map +1 -1
  282. package/dist/src/planner/framework/characteristics.d.ts +38 -4
  283. package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
  284. package/dist/src/planner/framework/characteristics.js +50 -6
  285. package/dist/src/planner/framework/characteristics.js.map +1 -1
  286. package/dist/src/planner/framework/pass.d.ts.map +1 -1
  287. package/dist/src/planner/framework/pass.js +2 -1
  288. package/dist/src/planner/framework/pass.js.map +1 -1
  289. package/dist/src/planner/framework/registry.d.ts +39 -1
  290. package/dist/src/planner/framework/registry.d.ts.map +1 -1
  291. package/dist/src/planner/framework/registry.js +18 -2
  292. package/dist/src/planner/framework/registry.js.map +1 -1
  293. package/dist/src/planner/mutation/backward-body.d.ts +131 -0
  294. package/dist/src/planner/mutation/backward-body.d.ts.map +1 -0
  295. package/dist/src/planner/mutation/backward-body.js +135 -0
  296. package/dist/src/planner/mutation/backward-body.js.map +1 -0
  297. package/dist/src/planner/mutation/cte-flatten.d.ts +17 -0
  298. package/dist/src/planner/mutation/cte-flatten.d.ts.map +1 -0
  299. package/dist/src/planner/mutation/cte-flatten.js +364 -0
  300. package/dist/src/planner/mutation/cte-flatten.js.map +1 -0
  301. package/dist/src/planner/mutation/decomposition.d.ts +273 -0
  302. package/dist/src/planner/mutation/decomposition.d.ts.map +1 -0
  303. package/dist/src/planner/mutation/decomposition.js +1719 -0
  304. package/dist/src/planner/mutation/decomposition.js.map +1 -0
  305. package/dist/src/planner/mutation/lens-enforcement.d.ts +165 -0
  306. package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -0
  307. package/dist/src/planner/mutation/lens-enforcement.js +745 -0
  308. package/dist/src/planner/mutation/lens-enforcement.js.map +1 -0
  309. package/dist/src/planner/mutation/multi-source.d.ts +568 -0
  310. package/dist/src/planner/mutation/multi-source.d.ts.map +1 -0
  311. package/dist/src/planner/mutation/multi-source.js +2915 -0
  312. package/dist/src/planner/mutation/multi-source.js.map +1 -0
  313. package/dist/src/planner/mutation/mutation-diagnostic.d.ts +37 -0
  314. package/dist/src/planner/mutation/mutation-diagnostic.d.ts.map +1 -0
  315. package/dist/src/planner/mutation/mutation-diagnostic.js +24 -0
  316. package/dist/src/planner/mutation/mutation-diagnostic.js.map +1 -0
  317. package/dist/src/planner/mutation/mutation-tags.d.ts +33 -0
  318. package/dist/src/planner/mutation/mutation-tags.d.ts.map +1 -0
  319. package/dist/src/planner/mutation/mutation-tags.js +31 -0
  320. package/dist/src/planner/mutation/mutation-tags.js.map +1 -0
  321. package/dist/src/planner/mutation/propagate.d.ts +97 -0
  322. package/dist/src/planner/mutation/propagate.d.ts.map +1 -0
  323. package/dist/src/planner/mutation/propagate.js +220 -0
  324. package/dist/src/planner/mutation/propagate.js.map +1 -0
  325. package/dist/src/planner/mutation/scope-transform.d.ts +181 -0
  326. package/dist/src/planner/mutation/scope-transform.d.ts.map +1 -0
  327. package/dist/src/planner/mutation/scope-transform.js +574 -0
  328. package/dist/src/planner/mutation/scope-transform.js.map +1 -0
  329. package/dist/src/planner/mutation/set-op.d.ts +242 -0
  330. package/dist/src/planner/mutation/set-op.d.ts.map +1 -0
  331. package/dist/src/planner/mutation/set-op.js +1687 -0
  332. package/dist/src/planner/mutation/set-op.js.map +1 -0
  333. package/dist/src/planner/mutation/single-source.d.ts +261 -0
  334. package/dist/src/planner/mutation/single-source.d.ts.map +1 -0
  335. package/dist/src/planner/mutation/single-source.js +1096 -0
  336. package/dist/src/planner/mutation/single-source.js.map +1 -0
  337. package/dist/src/planner/nodes/aggregate-node.js +3 -3
  338. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  339. package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
  340. package/dist/src/planner/nodes/alias-node.js +5 -1
  341. package/dist/src/planner/nodes/alias-node.js.map +1 -1
  342. package/dist/src/planner/nodes/alter-table-node.d.ts +124 -1
  343. package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
  344. package/dist/src/planner/nodes/alter-table-node.js +27 -0
  345. package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
  346. package/dist/src/planner/nodes/analyze-node.d.ts +2 -1
  347. package/dist/src/planner/nodes/analyze-node.d.ts.map +1 -1
  348. package/dist/src/planner/nodes/analyze-node.js +18 -1
  349. package/dist/src/planner/nodes/analyze-node.js.map +1 -1
  350. package/dist/src/planner/nodes/asserted-keys-node.d.ts +43 -0
  351. package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -0
  352. package/dist/src/planner/nodes/asserted-keys-node.js +99 -0
  353. package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -0
  354. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  355. package/dist/src/planner/nodes/async-gather-node.js +33 -8
  356. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  357. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  358. package/dist/src/planner/nodes/bloom-join-node.js +2 -1
  359. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  360. package/dist/src/planner/nodes/create-view-node.d.ts +7 -2
  361. package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
  362. package/dist/src/planner/nodes/create-view-node.js +4 -1
  363. package/dist/src/planner/nodes/create-view-node.js.map +1 -1
  364. package/dist/src/planner/nodes/declarative-schema.d.ts +13 -1
  365. package/dist/src/planner/nodes/declarative-schema.d.ts.map +1 -1
  366. package/dist/src/planner/nodes/declarative-schema.js +32 -0
  367. package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
  368. package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
  369. package/dist/src/planner/nodes/distinct-node.js +2 -0
  370. package/dist/src/planner/nodes/distinct-node.js.map +1 -1
  371. package/dist/src/planner/nodes/dml-executor-node.d.ts +29 -1
  372. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  373. package/dist/src/planner/nodes/dml-executor-node.js +27 -3
  374. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  375. package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
  376. package/dist/src/planner/nodes/eager-prefetch-node.js +2 -0
  377. package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
  378. package/dist/src/planner/nodes/envelope-scan-node.d.ts +42 -0
  379. package/dist/src/planner/nodes/envelope-scan-node.d.ts.map +1 -0
  380. package/dist/src/planner/nodes/envelope-scan-node.js +62 -0
  381. package/dist/src/planner/nodes/envelope-scan-node.js.map +1 -0
  382. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
  383. package/dist/src/planner/nodes/fanout-lookup-join-node.js +11 -1
  384. package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
  385. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  386. package/dist/src/planner/nodes/filter.js +63 -13
  387. package/dist/src/planner/nodes/filter.js.map +1 -1
  388. package/dist/src/planner/nodes/join-node.d.ts +41 -1
  389. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  390. package/dist/src/planner/nodes/join-node.js +78 -8
  391. package/dist/src/planner/nodes/join-node.js.map +1 -1
  392. package/dist/src/planner/nodes/join-utils.d.ts +33 -6
  393. package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
  394. package/dist/src/planner/nodes/join-utils.js +124 -9
  395. package/dist/src/planner/nodes/join-utils.js.map +1 -1
  396. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts +104 -0
  397. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -0
  398. package/dist/src/planner/nodes/lens-auxiliary-access-node.js +91 -0
  399. package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -0
  400. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  401. package/dist/src/planner/nodes/limit-offset.js +4 -5
  402. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  403. package/dist/src/planner/nodes/materialized-view-nodes.d.ts +69 -0
  404. package/dist/src/planner/nodes/materialized-view-nodes.d.ts.map +1 -0
  405. package/dist/src/planner/nodes/materialized-view-nodes.js +111 -0
  406. package/dist/src/planner/nodes/materialized-view-nodes.js.map +1 -0
  407. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  408. package/dist/src/planner/nodes/merge-join-node.js +2 -1
  409. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  410. package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
  411. package/dist/src/planner/nodes/ordinal-slice-node.js +2 -0
  412. package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
  413. package/dist/src/planner/nodes/plan-node-type.d.ts +9 -0
  414. package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
  415. package/dist/src/planner/nodes/plan-node-type.js +9 -0
  416. package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
  417. package/dist/src/planner/nodes/plan-node.d.ts +265 -5
  418. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  419. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  420. package/dist/src/planner/nodes/pragma.d.ts +2 -1
  421. package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
  422. package/dist/src/planner/nodes/pragma.js +12 -0
  423. package/dist/src/planner/nodes/pragma.js.map +1 -1
  424. package/dist/src/planner/nodes/project-node.d.ts +14 -1
  425. package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
  426. package/dist/src/planner/nodes/project-node.js +85 -11
  427. package/dist/src/planner/nodes/project-node.js.map +1 -1
  428. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  429. package/dist/src/planner/nodes/reference.js +62 -27
  430. package/dist/src/planner/nodes/reference.js.map +1 -1
  431. package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
  432. package/dist/src/planner/nodes/retrieve-node.js +7 -0
  433. package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
  434. package/dist/src/planner/nodes/returning-node.d.ts.map +1 -1
  435. package/dist/src/planner/nodes/returning-node.js +10 -3
  436. package/dist/src/planner/nodes/returning-node.js.map +1 -1
  437. package/dist/src/planner/nodes/scalar.d.ts +20 -0
  438. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  439. package/dist/src/planner/nodes/scalar.js +71 -14
  440. package/dist/src/planner/nodes/scalar.js.map +1 -1
  441. package/dist/src/planner/nodes/set-object-tags-node.d.ts +39 -0
  442. package/dist/src/planner/nodes/set-object-tags-node.d.ts.map +1 -0
  443. package/dist/src/planner/nodes/set-object-tags-node.js +41 -0
  444. package/dist/src/planner/nodes/set-object-tags-node.js.map +1 -0
  445. package/dist/src/planner/nodes/set-operation-node.d.ts +123 -1
  446. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  447. package/dist/src/planner/nodes/set-operation-node.js +291 -18
  448. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  449. package/dist/src/planner/nodes/single-row.d.ts.map +1 -1
  450. package/dist/src/planner/nodes/single-row.js +3 -0
  451. package/dist/src/planner/nodes/single-row.js.map +1 -1
  452. package/dist/src/planner/nodes/sort.d.ts.map +1 -1
  453. package/dist/src/planner/nodes/sort.js +7 -6
  454. package/dist/src/planner/nodes/sort.js.map +1 -1
  455. package/dist/src/planner/nodes/subquery.d.ts +2 -0
  456. package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
  457. package/dist/src/planner/nodes/subquery.js +18 -2
  458. package/dist/src/planner/nodes/subquery.js.map +1 -1
  459. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  460. package/dist/src/planner/nodes/table-access-nodes.js +23 -3
  461. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  462. package/dist/src/planner/nodes/table-function-call.js +6 -0
  463. package/dist/src/planner/nodes/table-function-call.js.map +1 -1
  464. package/dist/src/planner/nodes/values-node.d.ts +1 -0
  465. package/dist/src/planner/nodes/values-node.d.ts.map +1 -1
  466. package/dist/src/planner/nodes/values-node.js +16 -6
  467. package/dist/src/planner/nodes/values-node.js.map +1 -1
  468. package/dist/src/planner/nodes/view-mutation-node.d.ts +259 -0
  469. package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -0
  470. package/dist/src/planner/nodes/view-mutation-node.js +273 -0
  471. package/dist/src/planner/nodes/view-mutation-node.js.map +1 -0
  472. package/dist/src/planner/nodes/window-function.d.ts +17 -1
  473. package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
  474. package/dist/src/planner/nodes/window-function.js +15 -1
  475. package/dist/src/planner/nodes/window-function.js.map +1 -1
  476. package/dist/src/planner/nodes/window-node.js +2 -2
  477. package/dist/src/planner/nodes/window-node.js.map +1 -1
  478. package/dist/src/planner/optimizer.d.ts.map +1 -1
  479. package/dist/src/planner/optimizer.js +372 -39
  480. package/dist/src/planner/optimizer.js.map +1 -1
  481. package/dist/src/planner/planning-context.d.ts +1 -1
  482. package/dist/src/planner/planning-context.d.ts.map +1 -1
  483. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts +70 -0
  484. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts.map +1 -0
  485. package/dist/src/planner/rules/access/lens-access-form-matcher.js +156 -0
  486. package/dist/src/planner/rules/access/lens-access-form-matcher.js.map +1 -0
  487. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts +31 -0
  488. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts.map +1 -0
  489. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js +176 -0
  490. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js.map +1 -0
  491. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  492. package/dist/src/planner/rules/access/rule-select-access-path.js +435 -37
  493. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  494. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
  495. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +9 -0
  496. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
  497. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +39 -0
  498. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -0
  499. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +616 -0
  500. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -0
  501. package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts.map +1 -1
  502. package/dist/src/planner/rules/cache/rule-scalar-cse.js +8 -1
  503. package/dist/src/planner/rules/cache/rule-scalar-cse.js.map +1 -1
  504. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +36 -0
  505. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
  506. package/dist/src/planner/rules/join/equi-pair-extractor.js +38 -1
  507. package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
  508. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
  509. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +10 -0
  510. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
  511. package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
  512. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +19 -1
  513. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  514. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +130 -0
  515. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts.map +1 -0
  516. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +206 -0
  517. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js.map +1 -0
  518. package/dist/src/planner/rules/join/rule-join-elimination.d.ts +67 -14
  519. package/dist/src/planner/rules/join/rule-join-elimination.d.ts.map +1 -1
  520. package/dist/src/planner/rules/join/rule-join-elimination.js +81 -25
  521. package/dist/src/planner/rules/join/rule-join-elimination.js.map +1 -1
  522. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts +84 -0
  523. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts.map +1 -0
  524. package/dist/src/planner/rules/join/rule-join-existence-pruning.js +138 -0
  525. package/dist/src/planner/rules/join/rule-join-existence-pruning.js.map +1 -0
  526. package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
  527. package/dist/src/planner/rules/join/rule-join-greedy-commute.js +9 -1
  528. package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
  529. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  530. package/dist/src/planner/rules/join/rule-join-physical-selection.js +12 -1
  531. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  532. package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -1
  533. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +4 -0
  534. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -1
  535. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  536. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +4 -0
  537. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  538. package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
  539. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +10 -0
  540. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
  541. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts +286 -0
  542. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts.map +1 -0
  543. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js +548 -0
  544. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js.map +1 -0
  545. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.d.ts.map +1 -1
  546. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js +9 -1
  547. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js.map +1 -1
  548. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.d.ts.map +1 -1
  549. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +7 -0
  550. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
  551. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.d.ts.map +1 -1
  552. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js +10 -1
  553. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js.map +1 -1
  554. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.d.ts.map +1 -1
  555. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js +9 -0
  556. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js.map +1 -1
  557. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts.map +1 -1
  558. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +18 -0
  559. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js.map +1 -1
  560. package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
  561. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +7 -0
  562. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
  563. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
  564. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +9 -0
  565. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  566. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -3
  567. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
  568. package/dist/src/planner/rules/retrieve/rule-projection-pruning.d.ts.map +1 -1
  569. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js +14 -0
  570. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js.map +1 -1
  571. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +1 -1
  572. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +4 -4
  573. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js.map +1 -1
  574. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
  575. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +8 -0
  576. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
  577. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
  578. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +7 -0
  579. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
  580. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  581. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +12 -0
  582. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  583. package/dist/src/planner/type-utils.d.ts +14 -0
  584. package/dist/src/planner/type-utils.d.ts.map +1 -1
  585. package/dist/src/planner/type-utils.js +66 -21
  586. package/dist/src/planner/type-utils.js.map +1 -1
  587. package/dist/src/planner/util/fd-utils.d.ts +177 -43
  588. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  589. package/dist/src/planner/util/fd-utils.js +396 -101
  590. package/dist/src/planner/util/fd-utils.js.map +1 -1
  591. package/dist/src/planner/util/ind-utils.d.ts +27 -1
  592. package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
  593. package/dist/src/planner/util/ind-utils.js +80 -6
  594. package/dist/src/planner/util/ind-utils.js.map +1 -1
  595. package/dist/src/planner/util/key-utils.d.ts.map +1 -1
  596. package/dist/src/planner/util/key-utils.js +81 -12
  597. package/dist/src/planner/util/key-utils.js.map +1 -1
  598. package/dist/src/planner/util/set-op-wrapper.d.ts +37 -0
  599. package/dist/src/planner/util/set-op-wrapper.d.ts.map +1 -0
  600. package/dist/src/planner/util/set-op-wrapper.js +82 -0
  601. package/dist/src/planner/util/set-op-wrapper.js.map +1 -0
  602. package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
  603. package/dist/src/planner/validation/plan-validator.js +1 -0
  604. package/dist/src/planner/validation/plan-validator.js.map +1 -1
  605. package/dist/src/runtime/context-helpers.d.ts +13 -1
  606. package/dist/src/runtime/context-helpers.d.ts.map +1 -1
  607. package/dist/src/runtime/context-helpers.js +7 -1
  608. package/dist/src/runtime/context-helpers.js.map +1 -1
  609. package/dist/src/runtime/delta-executor.d.ts +30 -1
  610. package/dist/src/runtime/delta-executor.d.ts.map +1 -1
  611. package/dist/src/runtime/delta-executor.js +29 -4
  612. package/dist/src/runtime/delta-executor.js.map +1 -1
  613. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  614. package/dist/src/runtime/emit/add-constraint.js +38 -5
  615. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  616. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  617. package/dist/src/runtime/emit/aggregate.js +10 -8
  618. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  619. package/dist/src/runtime/emit/alter-table.d.ts +1 -1
  620. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  621. package/dist/src/runtime/emit/alter-table.js +664 -108
  622. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  623. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  624. package/dist/src/runtime/emit/analyze.js +2 -1
  625. package/dist/src/runtime/emit/analyze.js.map +1 -1
  626. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  627. package/dist/src/runtime/emit/asof-scan.js +18 -5
  628. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  629. package/dist/src/runtime/emit/asserted-keys.d.ts +13 -0
  630. package/dist/src/runtime/emit/asserted-keys.d.ts.map +1 -0
  631. package/dist/src/runtime/emit/asserted-keys.js +13 -0
  632. package/dist/src/runtime/emit/asserted-keys.js.map +1 -0
  633. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  634. package/dist/src/runtime/emit/between.js +24 -19
  635. package/dist/src/runtime/emit/between.js.map +1 -1
  636. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  637. package/dist/src/runtime/emit/binary.js +5 -9
  638. package/dist/src/runtime/emit/binary.js.map +1 -1
  639. package/dist/src/runtime/emit/block.d.ts.map +1 -1
  640. package/dist/src/runtime/emit/block.js +11 -2
  641. package/dist/src/runtime/emit/block.js.map +1 -1
  642. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  643. package/dist/src/runtime/emit/bloom-join.js +8 -2
  644. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  645. package/dist/src/runtime/emit/constraint-check.js +15 -0
  646. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  647. package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
  648. package/dist/src/runtime/emit/create-table.js +8 -0
  649. package/dist/src/runtime/emit/create-table.js.map +1 -1
  650. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  651. package/dist/src/runtime/emit/create-view.js +16 -1
  652. package/dist/src/runtime/emit/create-view.js.map +1 -1
  653. package/dist/src/runtime/emit/dml-executor.d.ts +27 -0
  654. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  655. package/dist/src/runtime/emit/dml-executor.js +413 -193
  656. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  657. package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
  658. package/dist/src/runtime/emit/drop-table.js +10 -0
  659. package/dist/src/runtime/emit/drop-table.js.map +1 -1
  660. package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
  661. package/dist/src/runtime/emit/drop-view.js +17 -0
  662. package/dist/src/runtime/emit/drop-view.js.map +1 -1
  663. package/dist/src/runtime/emit/envelope-scan.d.ts +13 -0
  664. package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -0
  665. package/dist/src/runtime/emit/envelope-scan.js +22 -0
  666. package/dist/src/runtime/emit/envelope-scan.js.map +1 -0
  667. package/dist/src/runtime/emit/join.d.ts +10 -2
  668. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  669. package/dist/src/runtime/emit/join.js +128 -38
  670. package/dist/src/runtime/emit/join.js.map +1 -1
  671. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts +16 -0
  672. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts.map +1 -0
  673. package/dist/src/runtime/emit/lens-auxiliary-access.js +16 -0
  674. package/dist/src/runtime/emit/lens-auxiliary-access.js.map +1 -0
  675. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +640 -0
  676. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -0
  677. package/dist/src/runtime/emit/materialized-view-helpers.js +2576 -0
  678. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -0
  679. package/dist/src/runtime/emit/materialized-view.d.ts +31 -0
  680. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -0
  681. package/dist/src/runtime/emit/materialized-view.js +187 -0
  682. package/dist/src/runtime/emit/materialized-view.js.map +1 -0
  683. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  684. package/dist/src/runtime/emit/merge-join.js +15 -3
  685. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  686. package/dist/src/runtime/emit/project.d.ts.map +1 -1
  687. package/dist/src/runtime/emit/project.js +10 -5
  688. package/dist/src/runtime/emit/project.js.map +1 -1
  689. package/dist/src/runtime/emit/schema-declarative.d.ts +1 -0
  690. package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
  691. package/dist/src/runtime/emit/schema-declarative.js +101 -5
  692. package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
  693. package/dist/src/runtime/emit/set-object-tags.d.ts +16 -0
  694. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -0
  695. package/dist/src/runtime/emit/set-object-tags.js +57 -0
  696. package/dist/src/runtime/emit/set-object-tags.js.map +1 -0
  697. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  698. package/dist/src/runtime/emit/set-operation.js +140 -24
  699. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  700. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  701. package/dist/src/runtime/emit/subquery.js +110 -5
  702. package/dist/src/runtime/emit/subquery.js.map +1 -1
  703. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  704. package/dist/src/runtime/emit/unary.js +34 -6
  705. package/dist/src/runtime/emit/unary.js.map +1 -1
  706. package/dist/src/runtime/emit/view-mutation.d.ts +70 -0
  707. package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -0
  708. package/dist/src/runtime/emit/view-mutation.js +299 -0
  709. package/dist/src/runtime/emit/view-mutation.js.map +1 -0
  710. package/dist/src/runtime/emit/window.js +29 -5
  711. package/dist/src/runtime/emit/window.js.map +1 -1
  712. package/dist/src/runtime/foreign-key-actions.d.ts +66 -3
  713. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  714. package/dist/src/runtime/foreign-key-actions.js +580 -172
  715. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  716. package/dist/src/runtime/parallel-driver.d.ts +4 -1
  717. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  718. package/dist/src/runtime/parallel-driver.js +5 -1
  719. package/dist/src/runtime/parallel-driver.js.map +1 -1
  720. package/dist/src/runtime/register.d.ts.map +1 -1
  721. package/dist/src/runtime/register.js +17 -1
  722. package/dist/src/runtime/register.js.map +1 -1
  723. package/dist/src/runtime/types.d.ts +10 -0
  724. package/dist/src/runtime/types.d.ts.map +1 -1
  725. package/dist/src/runtime/types.js.map +1 -1
  726. package/dist/src/schema/basis-backfill.d.ts +63 -0
  727. package/dist/src/schema/basis-backfill.d.ts.map +1 -0
  728. package/dist/src/schema/basis-backfill.js +161 -0
  729. package/dist/src/schema/basis-backfill.js.map +1 -0
  730. package/dist/src/schema/catalog.d.ts +115 -1
  731. package/dist/src/schema/catalog.d.ts.map +1 -1
  732. package/dist/src/schema/catalog.js +249 -22
  733. package/dist/src/schema/catalog.js.map +1 -1
  734. package/dist/src/schema/change-events.d.ts +42 -1
  735. package/dist/src/schema/change-events.d.ts.map +1 -1
  736. package/dist/src/schema/change-events.js.map +1 -1
  737. package/dist/src/schema/column.d.ts +16 -0
  738. package/dist/src/schema/column.d.ts.map +1 -1
  739. package/dist/src/schema/column.js.map +1 -1
  740. package/dist/src/schema/constraint-builder.d.ts +182 -0
  741. package/dist/src/schema/constraint-builder.d.ts.map +1 -0
  742. package/dist/src/schema/constraint-builder.js +424 -0
  743. package/dist/src/schema/constraint-builder.js.map +1 -0
  744. package/dist/src/schema/ddl-generator.d.ts +86 -1
  745. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  746. package/dist/src/schema/ddl-generator.js +316 -20
  747. package/dist/src/schema/ddl-generator.js.map +1 -1
  748. package/dist/src/schema/declared-schema-manager.d.ts +51 -0
  749. package/dist/src/schema/declared-schema-manager.d.ts.map +1 -1
  750. package/dist/src/schema/declared-schema-manager.js +61 -0
  751. package/dist/src/schema/declared-schema-manager.js.map +1 -1
  752. package/dist/src/schema/derivation.d.ts +106 -0
  753. package/dist/src/schema/derivation.d.ts.map +1 -0
  754. package/dist/src/schema/derivation.js +25 -0
  755. package/dist/src/schema/derivation.js.map +1 -0
  756. package/dist/src/schema/function.d.ts +13 -0
  757. package/dist/src/schema/function.d.ts.map +1 -1
  758. package/dist/src/schema/function.js.map +1 -1
  759. package/dist/src/schema/lens-ack.d.ts +90 -0
  760. package/dist/src/schema/lens-ack.d.ts.map +1 -0
  761. package/dist/src/schema/lens-ack.js +361 -0
  762. package/dist/src/schema/lens-ack.js.map +1 -0
  763. package/dist/src/schema/lens-compiler.d.ts +62 -0
  764. package/dist/src/schema/lens-compiler.d.ts.map +1 -0
  765. package/dist/src/schema/lens-compiler.js +1594 -0
  766. package/dist/src/schema/lens-compiler.js.map +1 -0
  767. package/dist/src/schema/lens-fk-discovery.d.ts +175 -0
  768. package/dist/src/schema/lens-fk-discovery.d.ts.map +1 -0
  769. package/dist/src/schema/lens-fk-discovery.js +336 -0
  770. package/dist/src/schema/lens-fk-discovery.js.map +1 -0
  771. package/dist/src/schema/lens-prover.d.ts +336 -0
  772. package/dist/src/schema/lens-prover.d.ts.map +1 -0
  773. package/dist/src/schema/lens-prover.js +1988 -0
  774. package/dist/src/schema/lens-prover.js.map +1 -0
  775. package/dist/src/schema/lens.d.ts +254 -0
  776. package/dist/src/schema/lens.d.ts.map +1 -0
  777. package/dist/src/schema/lens.js +21 -0
  778. package/dist/src/schema/lens.js.map +1 -0
  779. package/dist/src/schema/manager.d.ts +676 -18
  780. package/dist/src/schema/manager.d.ts.map +1 -1
  781. package/dist/src/schema/manager.js +1573 -238
  782. package/dist/src/schema/manager.js.map +1 -1
  783. package/dist/src/schema/mapping-advertisement-tags.d.ts +39 -0
  784. package/dist/src/schema/mapping-advertisement-tags.d.ts.map +1 -0
  785. package/dist/src/schema/mapping-advertisement-tags.js +216 -0
  786. package/dist/src/schema/mapping-advertisement-tags.js.map +1 -0
  787. package/dist/src/schema/rename-rewriter.d.ts +45 -4
  788. package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
  789. package/dist/src/schema/rename-rewriter.js +412 -19
  790. package/dist/src/schema/rename-rewriter.js.map +1 -1
  791. package/dist/src/schema/reserved-tags-policy.d.ts +32 -0
  792. package/dist/src/schema/reserved-tags-policy.d.ts.map +1 -0
  793. package/dist/src/schema/reserved-tags-policy.js +34 -0
  794. package/dist/src/schema/reserved-tags-policy.js.map +1 -0
  795. package/dist/src/schema/reserved-tags.d.ts +170 -0
  796. package/dist/src/schema/reserved-tags.d.ts.map +1 -0
  797. package/dist/src/schema/reserved-tags.js +507 -0
  798. package/dist/src/schema/reserved-tags.js.map +1 -0
  799. package/dist/src/schema/schema-differ.d.ts +158 -2
  800. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  801. package/dist/src/schema/schema-differ.js +1460 -78
  802. package/dist/src/schema/schema-differ.js.map +1 -1
  803. package/dist/src/schema/schema-hasher.d.ts +8 -3
  804. package/dist/src/schema/schema-hasher.d.ts.map +1 -1
  805. package/dist/src/schema/schema-hasher.js +22 -2
  806. package/dist/src/schema/schema-hasher.js.map +1 -1
  807. package/dist/src/schema/schema.d.ts +25 -1
  808. package/dist/src/schema/schema.d.ts.map +1 -1
  809. package/dist/src/schema/schema.js +36 -2
  810. package/dist/src/schema/schema.js.map +1 -1
  811. package/dist/src/schema/table.d.ts +259 -10
  812. package/dist/src/schema/table.d.ts.map +1 -1
  813. package/dist/src/schema/table.js +309 -26
  814. package/dist/src/schema/table.js.map +1 -1
  815. package/dist/src/schema/unique-enforcement.d.ts +78 -0
  816. package/dist/src/schema/unique-enforcement.d.ts.map +1 -0
  817. package/dist/src/schema/unique-enforcement.js +93 -0
  818. package/dist/src/schema/unique-enforcement.js.map +1 -0
  819. package/dist/src/schema/view.d.ts +83 -2
  820. package/dist/src/schema/view.d.ts.map +1 -1
  821. package/dist/src/schema/view.js +67 -1
  822. package/dist/src/schema/view.js.map +1 -1
  823. package/dist/src/schema/window-function.d.ts +9 -1
  824. package/dist/src/schema/window-function.d.ts.map +1 -1
  825. package/dist/src/schema/window-function.js.map +1 -1
  826. package/dist/src/util/comparison.d.ts +24 -0
  827. package/dist/src/util/comparison.d.ts.map +1 -1
  828. package/dist/src/util/comparison.js +34 -0
  829. package/dist/src/util/comparison.js.map +1 -1
  830. package/dist/src/util/mutation-statement.d.ts.map +1 -1
  831. package/dist/src/util/mutation-statement.js +4 -1
  832. package/dist/src/util/mutation-statement.js.map +1 -1
  833. package/dist/src/util/serialization.d.ts +9 -0
  834. package/dist/src/util/serialization.d.ts.map +1 -1
  835. package/dist/src/util/serialization.js +26 -0
  836. package/dist/src/util/serialization.js.map +1 -1
  837. package/dist/src/vtab/backing-host.d.ts +286 -0
  838. package/dist/src/vtab/backing-host.d.ts.map +1 -0
  839. package/dist/src/vtab/backing-host.js +118 -0
  840. package/dist/src/vtab/backing-host.js.map +1 -0
  841. package/dist/src/vtab/best-access-plan.d.ts +21 -0
  842. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  843. package/dist/src/vtab/best-access-plan.js.map +1 -1
  844. package/dist/src/vtab/capabilities.d.ts +5 -5
  845. package/dist/src/vtab/capabilities.d.ts.map +1 -1
  846. package/dist/src/vtab/mapping-advertisement.d.ts +163 -0
  847. package/dist/src/vtab/mapping-advertisement.d.ts.map +1 -0
  848. package/dist/src/vtab/mapping-advertisement.js +2 -0
  849. package/dist/src/vtab/mapping-advertisement.js.map +1 -0
  850. package/dist/src/vtab/memory/index.d.ts +64 -4
  851. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  852. package/dist/src/vtab/memory/index.js +119 -12
  853. package/dist/src/vtab/memory/index.js.map +1 -1
  854. package/dist/src/vtab/memory/layer/base.d.ts +38 -1
  855. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  856. package/dist/src/vtab/memory/layer/base.js +112 -24
  857. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  858. package/dist/src/vtab/memory/layer/manager.d.ts +291 -4
  859. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  860. package/dist/src/vtab/memory/layer/manager.js +1050 -91
  861. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  862. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  863. package/dist/src/vtab/memory/layer/plan-filter.js +35 -6
  864. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  865. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  866. package/dist/src/vtab/memory/layer/scan-layer.js +66 -14
  867. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  868. package/dist/src/vtab/memory/layer/scan-plan.d.ts +14 -0
  869. package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
  870. package/dist/src/vtab/memory/layer/scan-plan.js +27 -4
  871. package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
  872. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  873. package/dist/src/vtab/memory/layer/transaction.js +5 -1
  874. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  875. package/dist/src/vtab/memory/module.d.ts +17 -0
  876. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  877. package/dist/src/vtab/memory/module.js +82 -3
  878. package/dist/src/vtab/memory/module.js.map +1 -1
  879. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  880. package/dist/src/vtab/memory/table.js +15 -5
  881. package/dist/src/vtab/memory/table.js.map +1 -1
  882. package/dist/src/vtab/memory/types.d.ts +20 -2
  883. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  884. package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
  885. package/dist/src/vtab/memory/utils/predicate.js +46 -24
  886. package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
  887. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts +31 -0
  888. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -0
  889. package/dist/src/vtab/memory/utils/primary-key-encode.js +101 -0
  890. package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -0
  891. package/dist/src/vtab/memory/utils/primary-key.d.ts +8 -0
  892. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  893. package/dist/src/vtab/memory/utils/primary-key.js +12 -5
  894. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  895. package/dist/src/vtab/module.d.ts +203 -4
  896. package/dist/src/vtab/module.d.ts.map +1 -1
  897. package/dist/src/vtab/table.d.ts +9 -0
  898. package/dist/src/vtab/table.d.ts.map +1 -1
  899. package/dist/src/vtab/table.js.map +1 -1
  900. package/package.json +6 -5
@@ -0,0 +1,1719 @@
1
+ import { FilterNode } from '../nodes/filter.js';
2
+ import { ProjectNode } from '../nodes/project-node.js';
3
+ import { buildTableReference } from '../building/table.js';
4
+ import { combineAnd } from './single-source.js';
5
+ import { transformExpr, cloneExpr } from './scope-transform.js';
6
+ import { buildExpression } from '../building/expression.js';
7
+ import { columnSchemaToScalarType } from '../type-utils.js';
8
+ import { createRuntimeExpressionEvaluator } from '../analysis/const-evaluator.js';
9
+ import { containsNonDeterministicCall } from '../analysis/check-extraction.js';
10
+ import { FunctionFlags } from '../../common/constants.js';
11
+ import { analyzeBodyLineage } from './backward-body.js';
12
+ import { keyColumnName, capturedValueSubquery } from './multi-source.js';
13
+ import { raiseMutationDiagnostic } from './mutation-diagnostic.js';
14
+ /**
15
+ * Plan the synthesized get body **once** and read its threaded `updateLineage`
16
+ * into a {@link DecompShape}: the per-column backward lineage (column → owning base
17
+ * relation) plus a `TableReferenceNode`-id → member map, so the routing / anchor
18
+ * gate decide off the plan-node backward walk shared with the multi-source path
19
+ * (not a parallel projection-AST scan). EAV pivot members are correlated subqueries,
20
+ * not join sources, so they carry no planned `TableReferenceNode` and are absent
21
+ * from {@link DecompShape.memberByTableId} (resolved off the advertisement instead).
22
+ */
23
+ export function analyzeDecomposition(ctx, view, storage) {
24
+ const anchor = storage.members.find(m => m.relationId === storage.anchorRelationId);
25
+ if (!anchor) {
26
+ // Validated at advertisement resolution; defensive.
27
+ raiseMutationDiagnostic({
28
+ reason: 'no-base-lineage',
29
+ table: view.name,
30
+ message: `cannot write through logical table '${view.name}': decomposition anchor '${storage.anchorRelationId}' is not among its members`,
31
+ });
32
+ }
33
+ const lineage = analyzeBodyLineage(ctx, view);
34
+ const memberByTableId = new Map();
35
+ for (const [id, ref] of lineage.tableRefsById) {
36
+ const matches = storage.members.filter(m => m.relation.table.toLowerCase() === ref.tableSchema.name.toLowerCase()
37
+ && m.relation.schema.toLowerCase() === ref.tableSchema.schemaName.toLowerCase());
38
+ if (matches.length > 1) {
39
+ // Two members over the **same** physical base relation (a self-decomposition)
40
+ // both claim this body `TableReferenceNode`, so routing any column off
41
+ // `memberByTableId` would be ambiguous (a last-writer would silently win).
42
+ // The multi-source path rejects self-joins upstream, but that guard sits
43
+ // outside this code; enforce the single-member-per-base-ref assumption
44
+ // locally rather than relying on it.
45
+ raiseMutationDiagnostic({
46
+ reason: 'unsupported-decomposition-member',
47
+ table: view.name,
48
+ message: `cannot write through logical table '${view.name}': decomposition members ${matches.map(m => `'${m.relationId}'`).join(' and ')} both resolve to the same base relation '${ref.tableSchema.schemaName}.${ref.tableSchema.name}' (a self-decomposition); the put fan-out cannot disambiguate which member backs a column`,
49
+ });
50
+ }
51
+ if (matches.length === 1)
52
+ memberByTableId.set(id, matches[0]);
53
+ }
54
+ return {
55
+ storage, anchor, viewColToBaseRef: lineage.viewColToBaseRef, columns: lineage.columns, memberByTableId,
56
+ bodySource: lineage.bodySource, bodyScope: lineage.bodyScope,
57
+ };
58
+ }
59
+ /**
60
+ * The decomposition put fan-out for an authored (`with inverse`) column is
61
+ * deferred (docs/view-updateability.md § Authored inverses — the documented
62
+ * decomposition deferral): reject a write that targets one, naming the member(s)
63
+ * its puts route to, rather than letting it fall through {@link classifyColumn}'s
64
+ * EAV / computed-mapping fallbacks into a silent mis-route. Read consumers (the
65
+ * WHERE anchor-resolvability gate) deliberately do NOT run this — an authored
66
+ * column reads through its forward expression like any computed column.
67
+ */
68
+ function rejectAuthoredDecompositionWrite(view, shape, name, displayName) {
69
+ const col = shape.columns.find(c => c.name === name);
70
+ if (!col?.authored)
71
+ return;
72
+ const members = [...new Set(col.authored.puts.map(p => shape.memberByTableId.get(p.table)?.relationId ?? `relation #${p.table}`))];
73
+ raiseMutationDiagnostic({
74
+ reason: 'unsupported-decomposition-member',
75
+ column: displayName,
76
+ table: view.name,
77
+ message: `cannot write through logical table '${view.name}': column '${displayName}' carries an authored inverse (WITH INVERSE) targeting member${members.length > 1 ? 's' : ''} ${members.map(m => `'${m}'`).join(', ')}; the decomposition put fan-out for authored inverses is deferred`,
78
+ });
79
+ }
80
+ /**
81
+ * Classify one logical column against the decomposition. The **primary routing**
82
+ * (which member backs a writable/insertable column, and its base column) is read
83
+ * from the threaded `updateLineage` (`shape.columns` + `shape.memberByTableId`);
84
+ * the advertisement only disambiguates the deferred shapes (a non-identity mapping,
85
+ * an EAV pivot column), preserving the exact deferral diagnostics. Precedence
86
+ * mirrors the retired advertisement scan: identity base column → member-mapping →
87
+ * EAV pivot → unbacked.
88
+ */
89
+ function classifyColumn(view, shape, name) {
90
+ const col = shape.columns.find(c => c.name === name);
91
+ // An identity base column on a join member, routed by the plan-node lineage.
92
+ if (col?.baseColumn !== undefined && col.baseTableId !== undefined && col.inverse === undefined) {
93
+ const member = shape.memberByTableId.get(col.baseTableId);
94
+ if (member)
95
+ return { kind: 'member', member, baseColumn: col.baseColumn, nullExtended: col.nullExtended };
96
+ // The lineage resolved an **identity** base column (a base site, no inverse),
97
+ // but no decomposition member owns its base `TableReferenceNode`. In a
98
+ // faithfully-synthesized body every base table-ref IS a member, so this is a
99
+ // lineage-resolution miss (e.g. a `memberByTableId` schema/name mismatch), NOT a
100
+ // non-identity mapping. Reject defensively — falling through to the name-only
101
+ // `member.columns` match below would silently degrade a *writable* column to
102
+ // `computed-mapping` (read-only), masking the lineage bug as a benign read-only
103
+ // column.
104
+ raiseMutationDiagnostic({
105
+ reason: 'no-base-lineage',
106
+ column: col.displayName,
107
+ table: view.name,
108
+ message: `cannot write through logical table '${view.name}': column '${col.displayName}' resolves to identity base column '${col.baseColumn}', but no decomposition member backs its base relation (lineage-resolution miss); a writable column must not silently degrade to read-only`,
109
+ });
110
+ }
111
+ // A `member.columns` mapping the lineage did not resolve to an identity base column
112
+ // is a non-identity (computed / non-invertible) mapping — read-only.
113
+ for (const member of shape.storage.members) {
114
+ if (member.columns.some(c => c.logicalColumn.toLowerCase() === name)) {
115
+ return { kind: 'computed-mapping', member };
116
+ }
117
+ }
118
+ // An EAV pivot backs a logical column the get body projects as a (non-column)
119
+ // correlated subquery — never a `member.columns` entry, so the loops above miss it.
120
+ const projected = shape.viewColToBaseRef.get(name);
121
+ if (projected && projected.type !== 'column') {
122
+ const eav = shape.storage.members.find(m => m.attributePivot);
123
+ if (eav)
124
+ return { kind: 'eav', member: eav };
125
+ }
126
+ return { kind: 'unbacked' };
127
+ }
128
+ /**
129
+ * Decompose a mutation through a decomposition-backed logical table into an
130
+ * ordered `BaseOp[]`. Throws a structured diagnostic for any deferred shape.
131
+ */
132
+ export function propagateDecomposition(ctx, view, storage, req) {
133
+ const shape = analyzeDecomposition(ctx, view, storage);
134
+ switch (req.op) {
135
+ case 'delete': return decomposeDelete(ctx, view, shape, req.stmt);
136
+ case 'update': return decomposeUpdate(ctx, view, shape, req.stmt);
137
+ case 'insert':
138
+ // INSERT needs the plan-level shared-surrogate envelope (materialized
139
+ // source + per-row mint), which the AST `BaseOp[]` model cannot express,
140
+ // so it is built directly by `building/view-mutation-builder.ts`
141
+ // (`buildDecompositionInsert`, off `analyzeDecompositionInsert` below).
142
+ // `buildViewMutation` routes a decomposition insert there before
143
+ // `propagate` runs, so this case is unreachable on the supported path.
144
+ raiseMutationDiagnostic({
145
+ reason: 'unsupported-decomposition-insert',
146
+ table: view.name,
147
+ message: `internal: decomposition insert must be built via buildDecompositionInsert, not propagate`,
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Decompose an INSERT through a decomposition-backed logical table into the
153
+ * per-member base inserts plus the shared-surrogate envelope they fan out from
154
+ * (anchor first — the FK-order root). Throws a structured diagnostic for any
155
+ * deferred shape (composite/absent key, non-integer surrogate, an uncoverable
156
+ * not-null member column, a computed/unbacked logical column).
157
+ */
158
+ export function analyzeDecompositionInsert(ctx, view, storage, stmt) {
159
+ rejectReturning(view, stmt.returning);
160
+ const shape = analyzeDecomposition(ctx, view, storage);
161
+ const anchor = shape.anchor;
162
+ // Resolve every member's table once (reused for schema lookups + the seed table).
163
+ const memberRefs = new Map();
164
+ for (const m of storage.members)
165
+ memberRefs.set(m.relationId, resolveMemberTable(ctx, m));
166
+ // Supplied logical columns: the explicit list, or every projected logical column
167
+ // (in projection order — the order the shared backward consumer enumerates them).
168
+ const suppliedNames = stmt.columns && stmt.columns.length > 0
169
+ ? stmt.columns
170
+ : shape.columns.map(c => c.name);
171
+ // Declared-case logical column names (the get body's projection aliases) — the
172
+ // exact attribute literals an EAV write must store (the read does not case-fold).
173
+ const declaredNames = declaredColumnNames(view);
174
+ const routed = suppliedNames.map((raw, idx) => routeInsertColumn(view, shape, memberRefs, declaredNames, raw, idx));
175
+ // Shared key: a surrogate's value comes from the anchor key column's declared
176
+ // `default`, evaluated once per row at the envelope; a logical-tuple threads the
177
+ // supplied logical PK. `keyEnvelopeIndex` is the envelope column every member's key
178
+ // column reads (the default-sourced column for a surrogate, the supplied PK column
179
+ // for a logical-tuple, or undefined for the singleton empty key).
180
+ const { keyEnvelopeIndex, keyDefault } = resolveInsertSharedKey(view, shape, memberRefs, routed);
181
+ // Member ops, anchor first (FK-order root: members may FK-reference the anchor).
182
+ const ops = [];
183
+ emitMemberInsert(view, shape, memberRefs, routed, keyEnvelopeIndex, anchor, ops);
184
+ for (const member of storage.members) {
185
+ if (member.relationId === anchor.relationId)
186
+ continue;
187
+ emitMemberInsert(view, shape, memberRefs, routed, keyEnvelopeIndex, member, ops);
188
+ }
189
+ return {
190
+ suppliedColumns: routed.map(r => ({ name: r.name, type: r.type })),
191
+ ops,
192
+ keyDefault,
193
+ };
194
+ }
195
+ /**
196
+ * Route one supplied logical column to a columnar member mapping or an EAV pivot,
197
+ * off the threaded backward lineage ({@link classifyColumn}). A columnar route binds
198
+ * the value to its member's identity base column; an EAV route writes an attribute
199
+ * triple gated on the value. Optional members are insertable here (the per-row
200
+ * presence gate in {@link emitMemberInsert} drops absent components).
201
+ */
202
+ function routeInsertColumn(view, shape, memberRefs, declaredNames, rawName, idx) {
203
+ const name = rawName.toLowerCase();
204
+ rejectAuthoredDecompositionWrite(view, shape, name, rawName);
205
+ const route = classifyColumn(view, shape, name);
206
+ switch (route.kind) {
207
+ case 'member': {
208
+ const ref = memberRefs.get(route.member.relationId);
209
+ const col = columnByName(view, ref.tableSchema, route.baseColumn);
210
+ return { name, envelopeIndex: idx, type: columnSchemaToScalarType(col), columnar: { relationId: route.member.relationId, basisColumn: route.baseColumn } };
211
+ }
212
+ case 'eav': {
213
+ const ref = memberRefs.get(route.member.relationId);
214
+ const valCol = columnByName(view, ref.tableSchema, route.member.attributePivot.valueColumn);
215
+ return { name, envelopeIndex: idx, type: columnSchemaToScalarType(valCol), eav: route.member, eavAttribute: declaredNames.get(name) ?? rawName };
216
+ }
217
+ case 'computed-mapping':
218
+ return raiseMutationDiagnostic({
219
+ reason: 'no-inverse',
220
+ column: rawName,
221
+ table: view.name,
222
+ message: `cannot insert into logical table '${view.name}': column '${rawName}' is a computed (non-invertible) decomposition mapping and cannot receive an inserted value`,
223
+ });
224
+ case 'unbacked':
225
+ return raiseMutationDiagnostic({
226
+ reason: 'no-inverse',
227
+ column: rawName,
228
+ table: view.name,
229
+ message: `cannot insert into logical table '${view.name}': column '${rawName}' is not backed by any decomposition member`,
230
+ });
231
+ }
232
+ }
233
+ /** Resolve the shared key envelope index + optional anchor-default source for an insert. */
234
+ function resolveInsertSharedKey(view, shape, memberRefs, routed) {
235
+ const sharedKey = shape.storage.sharedKey;
236
+ const anchor = shape.anchor;
237
+ if (sharedKey.kind === 'surrogate') {
238
+ const anchorKeys = memberKeyColumns(view, shape, anchor);
239
+ if (anchorKeys.length !== 1) {
240
+ raiseMutationDiagnostic({
241
+ reason: 'unsupported-decomposition-key',
242
+ table: view.name,
243
+ message: `cannot insert into logical table '${view.name}': a surrogate decomposition needs a single-column key on the anchor '${anchor.relationId}' (v1 threads a single-column surrogate)`,
244
+ });
245
+ }
246
+ const anchorRef = memberRefs.get(anchor.relationId);
247
+ // The surrogate's value comes from the anchor key column's declared `default`
248
+ // (the engine no longer auto-generates one): evaluated once per row at the
249
+ // envelope and EC-threaded into every member's key column.
250
+ const keyDefault = requireKeyDefault(view, anchorRef.tableSchema, columnByName(view, anchorRef.tableSchema, anchorKeys[0]));
251
+ return {
252
+ keyEnvelopeIndex: routed.length, // the default-sourced column is appended last
253
+ keyDefault,
254
+ };
255
+ }
256
+ // logical-tuple: the supplied logical PK threads to every member's key column.
257
+ const anchorKeys = memberKeyColumns(view, shape, anchor);
258
+ if (anchorKeys.length === 0) {
259
+ return { keyEnvelopeIndex: undefined, keyDefault: undefined }; // singleton — no key to thread
260
+ }
261
+ const anchorKeyCol = anchorKeys[0].toLowerCase();
262
+ const keyRouted = routed.find(r => r.columnar
263
+ && r.columnar.relationId === anchor.relationId
264
+ && r.columnar.basisColumn.toLowerCase() === anchorKeyCol);
265
+ if (!keyRouted) {
266
+ raiseMutationDiagnostic({
267
+ reason: 'no-default',
268
+ table: view.name,
269
+ message: `cannot insert into logical table '${view.name}': the logical-tuple shared key (anchor '${anchor.relationId}' column '${anchorKeys[0]}') is not supplied through the logical table; a logical-tuple key threads the supplied value, so it must be provided`,
270
+ });
271
+ }
272
+ return { keyEnvelopeIndex: keyRouted.envelopeIndex, keyDefault: undefined };
273
+ }
274
+ /**
275
+ * The anchor key column's declared `default` — the surrogate's per-row source —
276
+ * evaluated once per produced row at the envelope (with `mutation_ordinal()` in
277
+ * scope) and EC-threaded into every member's key column. The engine no longer
278
+ * invents a surrogate: a surrogate key whose anchor column declares no default
279
+ * raises `no-default` with the migration recipe.
280
+ */
281
+ function requireKeyDefault(view, schema, keyCol) {
282
+ if (keyCol.defaultValue === null) {
283
+ raiseMutationDiagnostic({
284
+ reason: 'no-default',
285
+ column: keyCol.name,
286
+ table: view.name,
287
+ message: `cannot insert into logical table '${view.name}': the surrogate shared key '${schema.name}.${keyCol.name}' declares no DEFAULT; a surrogate's value comes from the anchor key column's default (e.g. \`default (coalesce((select max(${keyCol.name}) from ${schema.name}), 0) + mutation_ordinal())\`) — the engine no longer auto-generates one`,
288
+ suggestion: `declare a DEFAULT on '${schema.name}.${keyCol.name}', or expose the key as a supplied logical column`,
289
+ });
290
+ }
291
+ return keyCol.defaultValue;
292
+ }
293
+ /** Emit the base insert op(s) for one member (one columnar op, or one triple per supplied EAV attribute). */
294
+ function emitMemberInsert(view, shape, memberRefs, routed, keyEnvelopeIndex, member, ops) {
295
+ const ref = memberRefs.get(member.relationId);
296
+ const schema = ref.tableSchema;
297
+ if (member.attributePivot) {
298
+ // EAV pivot: one triple insert per supplied attribute, gated on a non-null value.
299
+ const pivot = member.attributePivot;
300
+ for (const r of routed) {
301
+ if (r.eav?.relationId !== member.relationId)
302
+ continue;
303
+ const columns = [
304
+ { baseColumn: pivot.entityColumn, envelopeIndex: requireKeyIndex(view, member, keyEnvelopeIndex) },
305
+ { baseColumn: pivot.attributeColumn, literal: r.eavAttribute ?? r.name },
306
+ { baseColumn: pivot.valueColumn, envelopeIndex: r.envelopeIndex },
307
+ ];
308
+ assertNoMissingNotNull(view, schema, columns);
309
+ ops.push({ table: ref, schema, columns, presenceGateIndices: [r.envelopeIndex] });
310
+ }
311
+ return;
312
+ }
313
+ const ownedSupplied = routed.filter(r => r.columnar?.relationId === member.relationId);
314
+ // An optional member with no supplied columns materializes no row (the read's
315
+ // outer join already yields null for an absent component).
316
+ if (member.presence === 'optional' && ownedSupplied.length === 0)
317
+ return;
318
+ const memberKeys = memberKeyColumns(view, shape, member);
319
+ const columns = [];
320
+ if (memberKeys.length === 1) {
321
+ columns.push({ baseColumn: memberKeys[0], envelopeIndex: requireKeyIndex(view, member, keyEnvelopeIndex) });
322
+ }
323
+ const keyColLower = memberKeys[0]?.toLowerCase();
324
+ for (const r of ownedSupplied) {
325
+ // The anchor's own key column is already threaded above; don't double-insert it.
326
+ if (keyColLower && r.columnar.basisColumn.toLowerCase() === keyColLower)
327
+ continue;
328
+ columns.push({ baseColumn: r.columnar.basisColumn, envelopeIndex: r.envelopeIndex });
329
+ }
330
+ assertNoMissingNotNull(view, schema, columns);
331
+ // Optional members are gated per-row on supplying ≥1 of their (non-key) values.
332
+ const gate = member.presence === 'optional'
333
+ ? ownedSupplied.filter(r => r.columnar.basisColumn.toLowerCase() !== keyColLower).map(r => r.envelopeIndex)
334
+ : [];
335
+ ops.push({ table: ref, schema, columns, presenceGateIndices: gate });
336
+ }
337
+ /** The shared-key columns for a member (0 for a singleton; >1 is a deferred composite key). */
338
+ function memberKeyColumns(view, shape, member) {
339
+ const keys = shape.storage.sharedKey.keyColumnsByRelation.get(member.relationId) ?? [];
340
+ if (keys.length > 1) {
341
+ raiseMutationDiagnostic({
342
+ reason: 'unsupported-decomposition-key',
343
+ table: view.name,
344
+ message: `cannot write through a decomposition with a composite shared key on member '${member.relationId}': v1 fan-out threads a single-column key`,
345
+ });
346
+ }
347
+ return [...keys];
348
+ }
349
+ /** A member needs a shared key value to thread, but the decomposition's key is empty (singleton). */
350
+ function requireKeyIndex(view, member, keyEnvelopeIndex) {
351
+ if (keyEnvelopeIndex === undefined) {
352
+ raiseMutationDiagnostic({
353
+ reason: 'unsupported-decomposition-key',
354
+ table: view.name,
355
+ message: `cannot insert into logical table '${view.name}': member '${member.relationId}' needs a shared key value to thread, but the decomposition has an empty (singleton) key`,
356
+ });
357
+ }
358
+ return keyEnvelopeIndex;
359
+ }
360
+ /** Reject a not-null base column with no declared default that no envelope value covers. */
361
+ function assertNoMissingNotNull(view, schema, columns) {
362
+ const covered = new Set(columns.map(c => c.baseColumn.toLowerCase()));
363
+ for (const col of schema.columns) {
364
+ if (col.generated || !col.notNull || col.defaultValue !== null)
365
+ continue;
366
+ if (covered.has(col.name.toLowerCase()))
367
+ continue;
368
+ raiseMutationDiagnostic({
369
+ reason: 'no-default',
370
+ column: col.name,
371
+ table: view.name,
372
+ message: `cannot insert into logical table '${view.name}': basis relation '${schema.name}' column '${col.name}' is NOT NULL with no default and no value reaches it through the decomposition`,
373
+ });
374
+ }
375
+ }
376
+ function columnByName(view, schema, name) {
377
+ const col = schema.columns.find(c => c.name.toLowerCase() === name.toLowerCase());
378
+ if (!col) {
379
+ raiseMutationDiagnostic({
380
+ reason: 'no-base-lineage',
381
+ table: view.name,
382
+ column: name,
383
+ message: `cannot write through logical table '${view.name}': column '${name}' not found on basis relation '${schema.name}'`,
384
+ });
385
+ }
386
+ return col;
387
+ }
388
+ /**
389
+ * Map each logical column's lowercased name → its **declared** name, read off the
390
+ * get body's projection aliases (`<expr> as <col.name>`). The declared name is the
391
+ * exact attribute literal `compileDecompositionBody` matches an EAV column against,
392
+ * so an EAV write must store that spelling (the read does not case-fold).
393
+ */
394
+ function declaredColumnNames(view) {
395
+ const map = new Map();
396
+ const sel = view.selectAst;
397
+ if (sel.type !== 'select')
398
+ return map;
399
+ for (const rc of sel.columns) {
400
+ if (rc.type !== 'column')
401
+ continue;
402
+ const name = rc.alias ?? (rc.expr.type === 'column' ? rc.expr.name : undefined);
403
+ if (name)
404
+ map.set(name.toLowerCase(), name);
405
+ }
406
+ return map;
407
+ }
408
+ // --- DELETE ---------------------------------------------------------------
409
+ /**
410
+ * Fan a logical delete out to every member. Order anchor-last; each non-anchor
411
+ * member's identifying set is `select <anchorKey> from <anchor> where <pred>`, so
412
+ * deleting other members never changes it. The anchor's own delete then applies
413
+ * the predicate directly against the anchor (its IN subquery would self-reference
414
+ * the rows it removes, so the bare-predicate form is both simpler and clearer).
415
+ */
416
+ function decomposeDelete(ctx, view, shape, stmt) {
417
+ rejectReturning(view, stmt.returning);
418
+ const pred = anchorPredicate(view, shape, stmt.where);
419
+ const ops = [];
420
+ // Non-anchor members first (each reads the still-intact anchor), anchor last.
421
+ for (const member of shape.storage.members) {
422
+ if (member.relationId === shape.anchor.relationId)
423
+ continue;
424
+ ops.push(memberDeleteOp(ctx, view, shape, member, pred, stmt));
425
+ }
426
+ ops.push(anchorDeleteOp(ctx, view, shape, pred, stmt));
427
+ return ops;
428
+ }
429
+ /**
430
+ * One member's delete. No predicate ⇒ an unconditional `delete from <member>`
431
+ * (truncate the component — also the sound singleton path, which has no key to
432
+ * thread). With an anchor predicate ⇒ `delete from <member> where
433
+ * <memberKeyOrEntity> in (select <anchorKey> from <anchor> where <pred>)`.
434
+ */
435
+ function memberDeleteOp(ctx, view, shape, member, pred, stmt) {
436
+ let where;
437
+ if (pred) {
438
+ const memberCol = member.attributePivot
439
+ ? member.attributePivot.entityColumn // EAV: delete every triple for the matched entities
440
+ : singleKeyColumn(view, shape, member);
441
+ where = { type: 'in', expr: { type: 'column', name: memberCol }, subquery: anchorKeySubquery(shape, pred) };
442
+ }
443
+ const statement = {
444
+ type: 'delete',
445
+ table: memberIdentifier(member),
446
+ where,
447
+ contextValues: stmt.contextValues,
448
+ schemaPath: stmt.schemaPath,
449
+ loc: stmt.loc,
450
+ };
451
+ return { table: resolveMemberTable(ctx, member), op: 'delete', statement };
452
+ }
453
+ /** `delete from <anchor> [where <pred bare>]`. */
454
+ function anchorDeleteOp(ctx, view, shape, pred, stmt) {
455
+ const statement = {
456
+ type: 'delete',
457
+ table: memberIdentifier(shape.anchor),
458
+ where: pred ? stripAnchorQualifier(pred, shape) : undefined,
459
+ contextValues: stmt.contextValues,
460
+ schemaPath: stmt.schemaPath,
461
+ loc: stmt.loc,
462
+ };
463
+ return { table: resolveMemberTable(ctx, shape.anchor), op: 'delete', statement };
464
+ }
465
+ // --- UPDATE ---------------------------------------------------------------
466
+ /**
467
+ * Route each assignment to the member that backs it and emit the per-member base
468
+ * ops, anchor-last (so a member whose column the predicate reads is not mutated
469
+ * before a sibling's identifying set is computed, and so every materialization
470
+ * branch reads the still-intact anchor). A **mandatory, non-EAV** member takes one
471
+ * base UPDATE (the legacy path). An **optional columnar** member's write is a per-row
472
+ * materialization transition — matched → base UPDATE, absent → null-extended INSERT,
473
+ * all value columns set null → base DELETE — and an **EAV pivot** member's write is
474
+ * the per-attribute triple analogue (non-null → upsert, null → delete). The branches
475
+ * are ordinary AST base ops keyed off the anchor subquery, not a new plan-node
476
+ * substrate (the same realization consumer 1 used for the outer-join dual). Shared-key
477
+ * (identity) / computed targets stay rejected.
478
+ *
479
+ * `capturedValues` is the optional **capture carrier** the builder threads (parallel to
480
+ * `decomposeUpdate`'s `sourceValues` in multi-source.ts): when present, an **arbitrary**
481
+ * value on an *optional columnar* member OR an **EAV** member (a subquery, cross-member, or
482
+ * mixed anchor+self — and, for EAV, any self-reference, since an EAV value column lowers to a
483
+ * subquery — otherwise rejected) is lowered to base terms and accumulated as a `srcN`
484
+ * projection, and its cell rides the per-row capture two-op path
485
+ * ({@link emitCapturedMemberUpdate} for columnar, {@link emitEavCapturedAttr} for EAV); the
486
+ * builder then materializes the values once over the planned body
487
+ * ({@link buildDecompositionKeyCapture}) and every base op reads them back through the
488
+ * context-injected `__vmupd_keys`. Absent the carrier (the legacy `propagateDecomposition`
489
+ * path, unreachable from build), an arbitrary value stays rejected — the defensive legacy
490
+ * behaviour, exactly as `propagateMultiSource`'s update path does.
491
+ */
492
+ export function decomposeUpdate(ctx, view, shape, stmt, capturedValues) {
493
+ rejectReturning(view, stmt.returning);
494
+ const pred = anchorPredicate(view, shape, stmt.where);
495
+ const declaredNames = declaredColumnNames(view);
496
+ // Capture carrier (mirrors multi-source.ts `decomposeUpdate`'s `registerCapturedExpr`):
497
+ // project an already-lowered base-term value into the up-front `__vmupd_keys` capture under
498
+ // a stable `srcN` alias (deduped by `key`), returning that alias. `canCapture` gates whether
499
+ // an arbitrary value — on an optional-columnar OR an EAV member — is admitted (carrier present)
500
+ // or rejected (the legacy carrier-absent path).
501
+ const canCapture = capturedValues !== undefined;
502
+ const srcDedup = new Map();
503
+ const registerCapturedExpr = capturedValues
504
+ ? (key, expr) => {
505
+ const existing = srcDedup.get(key);
506
+ if (existing)
507
+ return existing;
508
+ const alias = `src${capturedValues.length}`;
509
+ srcDedup.set(key, alias);
510
+ capturedValues.push({ alias, expr });
511
+ return alias;
512
+ }
513
+ : undefined;
514
+ // Per-member accumulation. A member is exactly one kind (mandatory-columnar /
515
+ // optional-columnar / EAV), so at most one of the three maps holds a given member.
516
+ const mandatory = new Map();
517
+ const optional = new Map();
518
+ const eav = new Map();
519
+ // member relationId → (target-key lower → first view-column spelling). Two distinct
520
+ // logical columns can route to the same basis column / EAV attribute on one member
521
+ // (e.g. a duplicate rename `b, b as b2`); the per-member UPDATE / triple would then
522
+ // assign it twice. Reject view-aware so the message names both logical columns.
523
+ const seenPerMember = new Map();
524
+ const noteTarget = (relationId, targetKey, viewCol) => {
525
+ let seen = seenPerMember.get(relationId);
526
+ if (!seen) {
527
+ seen = new Map();
528
+ seenPerMember.set(relationId, seen);
529
+ }
530
+ const prior = seen.get(targetKey);
531
+ if (prior !== undefined) {
532
+ raiseMutationDiagnostic({
533
+ reason: 'conflicting-assignment',
534
+ column: viewCol,
535
+ table: view.name,
536
+ message: `cannot update logical table '${view.name}': columns '${prior}' and '${viewCol}' both target '${targetKey}' on member '${relationId}'; an UPDATE cannot assign one column twice`,
537
+ });
538
+ }
539
+ seen.set(targetKey, viewCol);
540
+ };
541
+ for (const asg of stmt.assignments) {
542
+ const routed = routeAssignment(view, shape, declaredNames, asg, canCapture);
543
+ switch (routed.kind) {
544
+ case 'mandatory': {
545
+ noteTarget(routed.member.relationId, routed.basisColumn.toLowerCase(), asg.column);
546
+ let list = mandatory.get(routed.member.relationId);
547
+ if (!list) {
548
+ list = [];
549
+ mandatory.set(routed.member.relationId, list);
550
+ }
551
+ list.push({ column: routed.basisColumn, value: routed.value });
552
+ break;
553
+ }
554
+ case 'optional': {
555
+ noteTarget(routed.member.relationId, routed.basisColumn.toLowerCase(), asg.column);
556
+ let g = optional.get(routed.member.relationId);
557
+ if (!g) {
558
+ g = { member: routed.member, cells: [] };
559
+ optional.set(routed.member.relationId, g);
560
+ }
561
+ g.cells.push({ basisColumn: routed.basisColumn, value: routed.value, isNull: routed.isNull, kind: routed.valueKind });
562
+ break;
563
+ }
564
+ case 'eav': {
565
+ noteTarget(routed.member.relationId, `attr:${routed.attribute.toLowerCase()}`, asg.column);
566
+ let g = eav.get(routed.member.relationId);
567
+ if (!g) {
568
+ g = { member: routed.member, cells: [] };
569
+ eav.set(routed.member.relationId, g);
570
+ }
571
+ g.cells.push({ attribute: routed.attribute, value: routed.value, isNull: routed.isNull, kind: routed.valueKind });
572
+ break;
573
+ }
574
+ }
575
+ }
576
+ const ops = [];
577
+ const emit = (member) => {
578
+ const m = mandatory.get(member.relationId);
579
+ if (m && m.length > 0)
580
+ ops.push(memberUpdateOp(ctx, view, shape, member, m, pred, stmt));
581
+ const o = optional.get(member.relationId);
582
+ if (o)
583
+ emitOptionalMemberUpdate(ctx, view, shape, member, o.cells, pred, stmt, ops, registerCapturedExpr);
584
+ const e = eav.get(member.relationId);
585
+ if (e)
586
+ emitEavMemberUpdate(ctx, view, shape, member, e.cells, pred, stmt, ops, registerCapturedExpr);
587
+ };
588
+ for (const member of shape.storage.members) {
589
+ if (member.relationId === shape.anchor.relationId)
590
+ continue;
591
+ emit(member);
592
+ }
593
+ emit(shape.anchor); // anchor last
594
+ return ops;
595
+ }
596
+ /**
597
+ * Resolve one `set <col> = <value>` to its backing member off the threaded backward
598
+ * lineage ({@link classifyColumn}). A mandatory, non-EAV identity member is the legacy
599
+ * value-write; an **optional columnar** member and an **EAV pivot** member are now
600
+ * routed to a per-row materialization transition (matched update / absent insert /
601
+ * emptied delete) instead of rejected. A shared-key (identity) / computed / unbacked
602
+ * target stays rejected with its precise diagnostic.
603
+ */
604
+ function routeAssignment(view, shape, declaredNames, asg, canCapture) {
605
+ const logical = asg.column.toLowerCase();
606
+ if (isSharedKeyColumn(shape, logical)) {
607
+ raiseMutationDiagnostic({
608
+ reason: 'unsupported-decomposition-update',
609
+ column: asg.column,
610
+ table: view.name,
611
+ message: `cannot update logical table '${view.name}': column '${asg.column}' is part of the decomposition shared key; an identity change is not a value write`,
612
+ });
613
+ }
614
+ rejectAuthoredDecompositionWrite(view, shape, logical, asg.column);
615
+ const route = classifyColumn(view, shape, logical);
616
+ switch (route.kind) {
617
+ case 'member': {
618
+ // An optional member is outer-joined (null-extended lineage): writing it is a
619
+ // per-row materialization transition (matched → update, absent → insert, all
620
+ // value columns null → delete), realized as anchor-keyed AST base ops below.
621
+ if (route.member.presence !== 'mandatory' || route.nullExtended) {
622
+ const { kind, value, isNull } = lowerMaterializedValue(view, shape, route.member, asg, canCapture);
623
+ return { kind: 'optional', member: route.member, basisColumn: route.baseColumn, value, isNull, valueKind: kind };
624
+ }
625
+ return { kind: 'mandatory', member: route.member, basisColumn: route.baseColumn, value: rewriteAssignedValue(view, shape, route.member, asg.value) };
626
+ }
627
+ case 'eav': {
628
+ // An EAV pivot backs its logical columns as attribute triples: a null deletes the
629
+ // triple, a constant/anchor value upserts it, and an arbitrary value (a self-reference,
630
+ // cross-member, or subquery — all of which lower to a subquery for EAV) rides the
631
+ // single-identity capture when the carrier is present (`canCapture`), else is rejected.
632
+ const { kind, value, isNull } = lowerMaterializedValue(view, shape, route.member, asg, canCapture);
633
+ return { kind: 'eav', member: route.member, attribute: declaredNames.get(logical) ?? asg.column, value, isNull, valueKind: kind };
634
+ }
635
+ case 'computed-mapping':
636
+ return raiseMutationDiagnostic({
637
+ reason: 'no-inverse',
638
+ column: asg.column,
639
+ table: view.name,
640
+ message: `cannot update logical table '${view.name}': column '${asg.column}' is a computed (non-invertible) decomposition mapping and is read-only`,
641
+ });
642
+ case 'unbacked':
643
+ return raiseMutationDiagnostic({
644
+ reason: 'no-inverse',
645
+ column: asg.column,
646
+ table: view.name,
647
+ message: `cannot update logical table '${view.name}': column '${asg.column}' is not backed by any decomposition member`,
648
+ });
649
+ }
650
+ }
651
+ /**
652
+ * Lower an optional/EAV-member assigned value to base terms and classify how it is
653
+ * scoped ({@link LoweredValue}). The matched write evaluates the value in the **member's**
654
+ * row scope while the materialize INSERT evaluates it over the **anchor** scan — so a
655
+ * non-constant value is only expressible when both branches can agree on it with **no new
656
+ * runtime substrate**. Three self-contained shapes qualify:
657
+ *
658
+ * - **constant** (no column ref, or a null literal) — survives both scopes trivially.
659
+ * - **anchor** — every leaf resolves to an anchor base column (`set c = a + 1`). The value
660
+ * is computed once over the anchor scan and the two branches are unified by an upsert
661
+ * ({@link buildOptionalMemberInsertSelect} `do update`), so the matched side reads the
662
+ * identical anchor-computed value via `excluded.<col>`.
663
+ * - **self** (columnar only) — every leaf is the owning member's own column (`set c = c + 1`,
664
+ * `set c = coalesce(c, 0) + 1`). Present rows take the matched UPDATE (their real prior value);
665
+ * absent rows take a materialize INSERT computing the self-expression with the owner's columns
666
+ * substituted to NULL, gated by a runtime non-empty filter. The matched and materialize branches
667
+ * read different scans (member vs null-substituted anchor), so they stay two distinct ops — not
668
+ * an upsert (see {@link emitOptionalMemberUpdate} / {@link buildSelfMaterializeInsertSelect}).
669
+ *
670
+ * Anything else — a subquery, a cross-member column, an unqualified ref, or a single value
671
+ * mixing anchor and self leaves — is **arbitrary**. With a capture carrier present (`canCapture`)
672
+ * it is admitted as `captured` on **both** an optional columnar member and an **EAV** member:
673
+ * lowered to base terms and (at emit) projected into the up-front `__vmupd_keys` set, read back by
674
+ * the stitch key in both branches — by the member key for columnar ({@link emitCapturedMemberUpdate}),
675
+ * by the entity column / anchor key for the EAV per-attribute triple pair ({@link emitEavMemberUpdate}).
676
+ * An EAV value column substitutes to a correlated subquery, so an EAV self-reference (`set p = p + 1`)
677
+ * lowers to a subquery and reaches here as `captured` (only the carrier-absent legacy
678
+ * `propagateDecomposition` path still rejects it). Classification reads the lowered value's
679
+ * column-ref **relationId qualifiers** (the synthesized body aliases each member by its
680
+ * relationId — the same qualifier {@link rewriteAssignedValue} keys cross-member rejection on);
681
+ * the owner is never the anchor (an optional/EAV member is mandatory-distinct), so `anchor` and
682
+ * `self` never collide.
683
+ */
684
+ function lowerMaterializedValue(view, shape, owner, asg, canCapture) {
685
+ const lowered = substituteViewColumns(asg.value, shape, view);
686
+ const isNull = isNullLiteral(lowered);
687
+ const { qualifiers, hasUnqualifiedColumn, hasSubquery } = collectValueScopes(lowered);
688
+ const ownerIsColumnar = owner.attributePivot === undefined;
689
+ if (!hasSubquery && !hasUnqualifiedColumn && qualifiers.size === 0) {
690
+ return { kind: 'constant', value: lowered, isNull };
691
+ }
692
+ if (!hasSubquery && !hasUnqualifiedColumn) {
693
+ const anchorId = shape.anchor.relationId;
694
+ if ([...qualifiers].every(q => q === anchorId))
695
+ return { kind: 'anchor', value: lowered, isNull };
696
+ if (ownerIsColumnar && [...qualifiers].every(q => q === owner.relationId))
697
+ return { kind: 'self', value: lowered, isNull };
698
+ }
699
+ // Arbitrary value. With a capture carrier present it is admitted as `captured` — for an optional
700
+ // **columnar** member (read back by the member key in both branches) and for an **EAV** member
701
+ // alike (read back by the entity column / anchor key into the per-attribute triple pair; an EAV
702
+ // value column substitutes to a correlated subquery, so any EAV self-reference reaches here). The
703
+ // cell just classifies `captured`; the value is registered + read back at emit. Only the
704
+ // carrier-absent legacy `propagateDecomposition` path stays rejected.
705
+ if (canCapture) {
706
+ return { kind: 'captured', value: lowered, isNull };
707
+ }
708
+ raiseMutationDiagnostic({
709
+ reason: 'unsupported-decomposition-update',
710
+ column: asg.column,
711
+ table: view.name,
712
+ message: `cannot update logical table '${view.name}': writing ${ownerIsColumnar ? 'optional-member' : 'EAV-member'} column '${asg.column}' with this value needs the per-row capture carrier to thread it across the matched-update and materialize branches, which the legacy non-build path does not supply`,
713
+ });
714
+ }
715
+ /**
716
+ * Walk a lowered (base-term) assigned value, collecting the distinct member-relationId
717
+ * qualifiers its column refs carry, whether any column ref is **unqualified**, and whether
718
+ * it embeds a subquery. {@link lowerMaterializedValue} classifies the value by whether every
719
+ * qualifier is the anchor's (`anchor`) or the owning member's (`self`); an unqualified ref or
720
+ * a subquery forces `arbitrary` (the user-term analogue of the predicate gate's
721
+ * {@link collectViewColumnRefs}, but keyed on the base qualifier rather than the logical name).
722
+ */
723
+ function collectValueScopes(expr) {
724
+ const qualifiers = new Set();
725
+ let hasUnqualifiedColumn = false;
726
+ let hasSubquery = false;
727
+ const walk = (node) => {
728
+ if (Array.isArray(node)) {
729
+ node.forEach(walk);
730
+ return;
731
+ }
732
+ if (!node || typeof node !== 'object' || !('type' in node))
733
+ return;
734
+ const n = node;
735
+ if (n.type === 'column') {
736
+ if (typeof n.table === 'string')
737
+ qualifiers.add(n.table);
738
+ else
739
+ hasUnqualifiedColumn = true;
740
+ return;
741
+ }
742
+ if (n.type === 'subquery' || n.type === 'select' || n.type === 'exists') {
743
+ hasSubquery = true;
744
+ return;
745
+ }
746
+ for (const v of Object.values(n))
747
+ walk(v);
748
+ };
749
+ walk(expr);
750
+ return { qualifiers, hasUnqualifiedColumn, hasSubquery };
751
+ }
752
+ /** `update <member> set <cols> where <memberKey> in (select <anchorKey> from <anchor> where <pred>)`. */
753
+ function memberUpdateOp(ctx, view, shape, member, assignments, pred, stmt) {
754
+ const memberKey = singleKeyColumn(view, shape, member);
755
+ const where = {
756
+ type: 'in',
757
+ expr: { type: 'column', name: memberKey },
758
+ subquery: anchorKeySubquery(shape, pred),
759
+ };
760
+ const statement = {
761
+ type: 'update',
762
+ table: memberIdentifier(member),
763
+ assignments: assignments.map(a => ({ column: a.column, value: a.value })),
764
+ where,
765
+ contextValues: stmt.contextValues,
766
+ schemaPath: stmt.schemaPath,
767
+ loc: stmt.loc,
768
+ };
769
+ return { table: resolveMemberTable(ctx, member), op: 'update', statement };
770
+ }
771
+ /**
772
+ * Rewrite an assigned value from logical terms into the owning member's base
773
+ * terms, then strip the member's own alias qualifier (the per-member UPDATE
774
+ * targets that table directly). A reference to a *different* member is a
775
+ * cross-source assignment a single-table SET cannot express — rejected.
776
+ */
777
+ function rewriteAssignedValue(view, shape, owner, value) {
778
+ const base = substituteViewColumns(value, shape, view);
779
+ return transformExpr(base, (col) => {
780
+ if (!col.table)
781
+ return undefined;
782
+ if (col.table === owner.relationId)
783
+ return { type: 'column', name: col.name };
784
+ raiseMutationDiagnostic({
785
+ reason: 'cross-source-assignment',
786
+ column: col.name,
787
+ table: view.name,
788
+ message: `cannot update logical table '${view.name}': an update value references column '${col.name}' on decomposition member '${col.table}', a different member than the column it assigns; cross-member assignment is not supported`,
789
+ });
790
+ });
791
+ }
792
+ // --- UPDATE materialization (optional columnar / EAV) ---------------------
793
+ /**
794
+ * Emit the per-row base ops for an optional (outer-joined) columnar member's update
795
+ * group, routed by the group's {@link ValueKind} composition (see the table in the file
796
+ * header):
797
+ *
798
+ * - **has a `captured` cell, OR mixes `anchor` and `self`** → the whole group rides the
799
+ * single-identity per-row capture ({@link emitCapturedMemberUpdate}): EVERY cell's value
800
+ * (including any anchor/self/constant sibling) is materialized once over the planned body
801
+ * under a `srcN`, the matched UPDATE reads each back by the member key, and a filtered
802
+ * materialize INSERT reads each back by the anchor key (gated on a runtime non-null). This
803
+ * subsumes the retired same-statement anchor+self reject — a mixed value, like any arbitrary
804
+ * one, is now a captured value. Requires the capture carrier (the build path); absent it the
805
+ * defensive legacy reject fires.
806
+ * - **has an `anchor` cell** (no `self`/`captured`) → a single **upsert** unifies the matched
807
+ * UPDATE and the absent materialize INSERT: the value is computed once over the anchor scan and
808
+ * both branches read it (insert directly, matched via `excluded.<col>`). Constant cells
809
+ * fold in as literal projections / `do update set col = excluded.col`.
810
+ * - **has a `self` cell** (no `anchor`) → present rows take the matched UPDATE (their real
811
+ * prior member value, owner qualifier stripped); absent rows take a materialize INSERT
812
+ * ({@link buildSelfMaterializeInsertSelect}) that evaluates the self-expression with the
813
+ * owner's own columns substituted to NULL — an absent row's prior value is null. A runtime
814
+ * non-empty filter gates it: a **null-propagating** self-expression (`c + 1` → null) is
815
+ * constant-false and materializes no phantom row, while one that maps null → non-null
816
+ * (`coalesce(c, 0) + 1`) is constant-true and materializes the new value. The two ops stay
817
+ * distinct (they **cannot** collapse into an upsert: the matched value is computed over the
818
+ * member scan, the materialize value over the null-substituted anchor scan — they disagree
819
+ * row-for-row by construction), and `on conflict (<memberKey>) do nothing` cedes matched
820
+ * rows to the UPDATE. Constant cells ride along in both branches (the matched UPDATE applies
821
+ * them; the materialize INSERT projects them, and a non-null constant makes the filter true).
822
+ * - **all `constant`** → the legacy fast lane: all-value-columns-null → base DELETE; else the
823
+ * matched UPDATE plus, when ≥1 value is non-null, the absent materialize INSERT.
824
+ */
825
+ function emitOptionalMemberUpdate(ctx, view, shape, member, cells, pred, stmt, ops, registerCapturedExpr) {
826
+ const hasCaptured = cells.some(c => c.kind === 'captured');
827
+ const hasAnchor = cells.some(c => c.kind === 'anchor');
828
+ const hasSelf = cells.some(c => c.kind === 'self');
829
+ // A group with ≥1 arbitrary (`captured`) cell — or a mixed anchor+self group, which no
830
+ // single-branch fast lane expresses — rides the single-identity per-row capture: every cell's
831
+ // value is materialized once over the planned body and read back by the stitch key in both
832
+ // branches. A `captured` cell can only exist when the carrier is present (the classifier gated
833
+ // it), so the guard only ever fires for the legacy carrier-absent anchor+self case.
834
+ if (hasCaptured || (hasAnchor && hasSelf)) {
835
+ if (!registerCapturedExpr) {
836
+ raiseMutationDiagnostic({
837
+ reason: 'unsupported-decomposition-update',
838
+ table: view.name,
839
+ message: `cannot update logical table '${view.name}': the update of optional member '${member.relationId}' mixes an anchor-resolvable value and a member self-reference in one statement; threading both across the matched-update and materialize branches needs the per-row capture carrier, which the legacy non-build path does not supply`,
840
+ });
841
+ }
842
+ emitCapturedMemberUpdate(ctx, view, shape, member, cells, pred, stmt, ops, registerCapturedExpr);
843
+ return;
844
+ }
845
+ if (hasAnchor) {
846
+ // Anchor-resolvable group → one upsert replaces both the matched UPDATE and the
847
+ // materialize INSERT (the value agrees row-for-row across both branches by construction).
848
+ ops.push(buildOptionalMemberInsertSelect(ctx, view, shape, member, cells, pred, stmt, 'update'));
849
+ return;
850
+ }
851
+ if (hasSelf) {
852
+ // Self-reference group → the matched UPDATE for present rows over the owner-qualifier-stripped
853
+ // values (their real prior member value), plus — only when the materialize is statically
854
+ // **live** — a materialize INSERT for absent rows that evaluates the self-expression with the
855
+ // owner's own columns substituted to NULL (an absent row's prior value is null), gated by a
856
+ // runtime non-empty filter: a null-propagating self-expression (`c + 1` → null) materializes
857
+ // nothing, while a null→non-null one (`coalesce(c, 0) + 1`) does.
858
+ //
859
+ // When that null-substituted non-empty filter folds **constant-false** at plan time (every
860
+ // self cell null-propagates and no non-null constant sibling keeps it alive), no absent row
861
+ // can ever materialize — so we emit ONLY the matched UPDATE (present-rows-only) and skip the
862
+ // INSERT. Because both soundness gates live inside {@link buildSelfMaterializeInsertSelect},
863
+ // skipping the call skips them with it: sound, since a gate is only a plan-time proxy for "a
864
+ // materialized row would violate", and no row materializes. A non-foldable / volatile /
865
+ // parameterized value cannot be proven dead, so it stays live (emit + gate) — conservative,
866
+ // matching the always-emit behavior. The UPDATE must precede the INSERT so the matched rows
867
+ // are settled before the `do nothing` materialize cedes them (see the doc above for why these
868
+ // cannot collapse into a single upsert).
869
+ ops.push(memberUpdateOp(ctx, view, shape, member, cells.map(c => ({ column: c.basisColumn, value: stripMemberQualifier(c.value, member) })), pred, stmt));
870
+ if (!foldsConstantFalse(ctx, selfMaterializeNonEmptyFilter(cells, member))) {
871
+ ops.push(buildSelfMaterializeInsertSelect(ctx, view, shape, member, cells, pred, stmt));
872
+ }
873
+ return;
874
+ }
875
+ // Pure-constant group: the legacy fast lane.
876
+ const valueBasisCols = optionalValueColumns(view, shape, member);
877
+ const assignedBasis = new Set(cells.map(c => c.basisColumn.toLowerCase()));
878
+ const allValueColsAssigned = valueBasisCols.every(bc => assignedBasis.has(bc.toLowerCase()));
879
+ const allAssignedNull = cells.every(c => c.isNull);
880
+ if (allAssignedNull && allValueColsAssigned) {
881
+ // The component row is emptied across every value column → delete it. Reuse the
882
+ // member-delete builder via a metadata-only synthetic DeleteStmt (it rebuilds the
883
+ // `<memberKey> in (<anchor subquery>)` predicate, and a no-WHERE update truncates
884
+ // the member — correct, since every logical row's value columns become null).
885
+ const asDelete = {
886
+ type: 'delete',
887
+ table: memberIdentifier(member),
888
+ contextValues: stmt.contextValues,
889
+ schemaPath: stmt.schemaPath,
890
+ loc: stmt.loc,
891
+ };
892
+ ops.push(memberDeleteOp(ctx, view, shape, member, pred, asDelete));
893
+ return;
894
+ }
895
+ // Matched rows: the legacy member UPDATE over the assigned (constant) values.
896
+ ops.push(memberUpdateOp(ctx, view, shape, member, cells.map(c => ({ column: c.basisColumn, value: cloneExpr(c.value) })), pred, stmt));
897
+ // Absent rows: materialize only when at least one assigned value is non-null (an
898
+ // all-null partial assignment leaves the absent row absent — nothing to create).
899
+ if (cells.some(c => !c.isNull)) {
900
+ ops.push(buildOptionalMemberInsertSelect(ctx, view, shape, member, cells, pred, stmt, 'nothing'));
901
+ }
902
+ }
903
+ /**
904
+ * Emit the captured two-op path for an optional columnar member group that contains an
905
+ * arbitrary (`captured`) cell — or that mixes an anchor and a self cell (which no single-branch
906
+ * fast lane expresses). EVERY cell's already-lowered base-term value is projected into the
907
+ * up-front `__vmupd_keys` capture under a stable `srcN` alias (a `captured` cell's arbitrary
908
+ * value, but also any anchor/self/constant sibling — uniform, harmless: the capture over the
909
+ * planned body null-extends an absent optional member, so a self/cross-member value over an
910
+ * absent row already captures null). Then:
911
+ *
912
+ * - the **matched UPDATE** (unfiltered over the present rows) sets each value from the capture,
913
+ * read back by the **member key** (`(select srcN from __vmupd_keys k where k.k0_0 = <memberKey>)`),
914
+ * so a present row gets its real pre-mutation value; a captured-null on a matched row writes the
915
+ * column null (a benign physical divergence — it reads identically to absent, never a widen).
916
+ * - the **materialize INSERT** ({@link buildCapturedMaterializeInsert}) over the absent rows reads
917
+ * each value back by the **anchor key**, gated on ≥1 captured read-back being non-null at runtime
918
+ * (so a self/cross-member value that captured null springs no phantom row) with
919
+ * `on conflict (<memberKey>) do nothing` to cede matched rows to the UPDATE.
920
+ *
921
+ * The two ops cannot collapse into an upsert: the filter must suppress the absent branch without
922
+ * suppressing the matched write. The UPDATE precedes the INSERT so the matched rows are settled
923
+ * before the `do nothing` materialize cedes them. The capture is materialized pre-mutation
924
+ * (regardless of base-op order), so a both-sides write (`set c = b + 1, b = b + 100`) reads `c`
925
+ * from the pre-mutation `b`. Reuses {@link assertNoUnassignedValueColumnWiden} (raised here, before
926
+ * either op, so a widen reject stays atomic) + {@link assertNoMissingNotNull} (in the builder).
927
+ */
928
+ function emitCapturedMemberUpdate(ctx, view, shape, member, cells, pred, stmt, ops, registerCapturedExpr) {
929
+ const ref = resolveMemberTable(ctx, member);
930
+ const schema = ref.tableSchema;
931
+ const memberKey = singleKeyColumn(view, shape, member);
932
+ // View-soundness gate (atomic, before either op): an unassigned value column that would not
933
+ // land null cannot be materialized. Shared with the constant / self materialize builders.
934
+ assertNoUnassignedValueColumnWiden(view, shape, member, schema, cells);
935
+ // Register every cell's lowered value into the capture (one `srcN` per cell), keyed by its
936
+ // basis column so the two read-back sites (member key, anchor key) bind the same alias.
937
+ const srcByCell = cells.map(c => registerCapturedExpr(`cap:${member.relationId.toLowerCase()}:${c.basisColumn.toLowerCase()}`, cloneExpr(c.value)));
938
+ // Matched UPDATE (unfiltered over the present rows): each value read back by the member key.
939
+ ops.push(memberUpdateOp(ctx, view, shape, member, cells.map((c, i) => ({ column: c.basisColumn, value: capturedValueSubquery(srcByCell[i], 0, [memberKey]) })), pred, stmt));
940
+ // Materialize INSERT (filtered over the absent rows): each value read back by the anchor key.
941
+ ops.push(buildCapturedMaterializeInsert(ctx, view, shape, member, cells, srcByCell, pred, stmt));
942
+ }
943
+ /**
944
+ * Build the absent-row materialize INSERT for a captured optional-member group, modeled on
945
+ * {@link buildSelfMaterializeInsertSelect} (`'nothing'` flavour) but sourcing each value from the
946
+ * up-front `__vmupd_keys` capture rather than a null-substituted self-expression. The select is
947
+ * over the anchor; each projected value is `capturedValueSubquery(srcN, 0, [anchorKey])` — the
948
+ * captured read-back correlated by the anchor key (which holds the shared stitch-key value). The
949
+ * WHERE conjoins the user predicate with an OR-chain `<each captured read-back> is not null`, so a
950
+ * row whose every captured value is null (a self/cross-member value over an absent member, or a
951
+ * subquery returning null) materializes nothing — the **runtime** analogue of the self path's
952
+ * plan-time `foldsConstantFalse`, since a captured value is data-dependent and cannot be folded.
953
+ * `on conflict (<memberKey>) do nothing` cedes the matched rows to the UPDATE (emitted first).
954
+ * Always emitted (the filter decides per row). Runs {@link assertNoMissingNotNull} on the target
955
+ * columns (the widen gate already fired in the caller).
956
+ */
957
+ function buildCapturedMaterializeInsert(ctx, view, shape, member, cells, srcByCell, pred, stmt) {
958
+ const ref = resolveMemberTable(ctx, member);
959
+ const schema = ref.tableSchema;
960
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
961
+ const memberKey = singleKeyColumn(view, shape, member);
962
+ const targetColumns = [memberKey];
963
+ const projections = [
964
+ { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
965
+ ];
966
+ cells.forEach((c, i) => {
967
+ targetColumns.push(c.basisColumn);
968
+ projections.push({ type: 'column', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKey]) });
969
+ });
970
+ assertNoMissingNotNull(view, schema, targetColumns.map((baseColumn) => ({ baseColumn })));
971
+ // Non-empty image filter: OR over each captured value (read back by the anchor key) being
972
+ // non-null. A fresh `capturedValueSubquery` per disjunct (distinct AST nodes from the
973
+ // projections') keeps the filter self-contained.
974
+ const nonEmpty = cells
975
+ .map((_, i) => ({ type: 'unary', operator: 'IS NOT NULL', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKey]) }))
976
+ .reduce((acc, e) => acc ? { type: 'binary', operator: 'OR', left: acc, right: e } : e);
977
+ const where = combineAnd(pred ? cloneExpr(pred) : undefined, nonEmpty);
978
+ const select = {
979
+ type: 'select',
980
+ columns: projections,
981
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
982
+ where,
983
+ };
984
+ const statement = {
985
+ type: 'insert',
986
+ table: memberIdentifier(member),
987
+ columns: targetColumns,
988
+ source: select,
989
+ upsertClauses: [{ type: 'upsert', conflictTarget: [memberKey], action: 'nothing' }],
990
+ contextValues: stmt.contextValues,
991
+ schemaPath: stmt.schemaPath,
992
+ loc: stmt.loc,
993
+ };
994
+ return { table: ref, op: 'insert', statement };
995
+ }
996
+ /**
997
+ * Build the single-identity (anchor-key) per-row value capture for a decomposition columnar
998
+ * UPDATE — the decomposition dual of `buildMultiSourceKeyCapture` (multi-source.ts), reusing the
999
+ * shared `__vmupd_keys` substrate ({@link MultiSourceKeyCapture} / `makeMultiSourceKeyRef` /
1000
+ * `keyColumnName`). Built as plan nodes directly over the **already-planned get body**
1001
+ * (`shape.bodySource` + `shape.bodyScope`) — `Project_{k0_0, srcN…}(Filter_{anchorPred}(body))` —
1002
+ * so the body is planned once and the captured value already encodes per-row presence (the body
1003
+ * null-extends an absent optional member).
1004
+ *
1005
+ * - `k0_0` (the only identity column) := the anchor key, read back by every base op (by the
1006
+ * member key in the matched UPDATE, the anchor key in the materialize INSERT — both correlate
1007
+ * `k.k0_0`, which holds the shared stitch-key value, since each member is keyed 1:1 to the
1008
+ * anchor).
1009
+ * - one `srcN` per {@link CapturedDecompValue}, in registration (= read-back) order, so the
1010
+ * flattened `keyColumns` align positionally with the projections every reader scans back.
1011
+ *
1012
+ * The identity predicate is the same anchor-resolvable lowering the base ops route on
1013
+ * ({@link anchorPredicate}, re-validated here — idempotent); a non-anchor WHERE has already
1014
+ * thrown in `decomposeUpdate` before any capture is requested (atomic).
1015
+ */
1016
+ export function buildDecompositionKeyCapture(ctx, view, shape, where, capturedValues) {
1017
+ const scope = shape.bodyScope;
1018
+ if (!scope) {
1019
+ raiseMutationDiagnostic({
1020
+ reason: 'no-base-lineage',
1021
+ table: view.name,
1022
+ message: `cannot write through logical table '${view.name}': the planned decomposition body did not expose a resolvable column scope for the value capture`,
1023
+ });
1024
+ }
1025
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1026
+ // The identifying predicate (anchor-resolvable user WHERE → base terms), built as a
1027
+ // ScalarPlanNode over the body scope, then a Filter over the planned body source.
1028
+ const predAst = anchorPredicate(view, shape, where);
1029
+ const predicate = predAst ? buildExpression({ ...ctx, scope }, predAst) : undefined;
1030
+ const filtered = predicate
1031
+ ? new FilterNode(scope, shape.bodySource, predicate)
1032
+ : shape.bodySource;
1033
+ // k0_0 := the anchor key, then one srcN per captured value (lowered over the body scope).
1034
+ const keyColumns = [];
1035
+ const projections = [];
1036
+ const anchorKeyNode = buildExpression({ ...ctx, scope }, { type: 'column', name: anchorKey, table: shape.anchor.relationId });
1037
+ keyColumns.push({ name: keyColumnName(0, 0), type: anchorKeyNode.getType() });
1038
+ projections.push({ node: anchorKeyNode, alias: keyColumnName(0, 0) });
1039
+ for (const cv of capturedValues) {
1040
+ const node = buildExpression({ ...ctx, scope }, cv.expr);
1041
+ keyColumns.push({ name: cv.alias, type: node.getType() });
1042
+ projections.push({ node, alias: cv.alias });
1043
+ }
1044
+ const source = new ProjectNode(scope, filtered, projections, undefined, undefined, false);
1045
+ return { source, descriptor: {}, keyColumns };
1046
+ }
1047
+ /**
1048
+ * Build the anchor-keyed insert-select that materializes / unifies an optional columnar
1049
+ * member's update, in one of two `action` flavours sharing the identical select + soundness
1050
+ * gates:
1051
+ *
1052
+ * - **`'nothing'`** (constant group, absent branch) — `insert into <member> (<memberKey>,
1053
+ * <cols…>) select <anchorKey>, <values…> from <anchor> where <pred> on conflict (<memberKey>)
1054
+ * do nothing`. The matched anchors (whose member row exists) conflict and are ceded to the
1055
+ * separate matched UPDATE; only the absent rows materialize. The insert source never scans
1056
+ * its own target (which the planner cannot assign an access path to).
1057
+ * - **`'update'`** (anchor-resolvable group) — the same select, but `on conflict (<memberKey>)
1058
+ * do update set <col> = excluded.<col>, …`. This **replaces both** the matched UPDATE and the
1059
+ * materialize INSERT: the value is computed once over the anchor scan, absent rows insert it,
1060
+ * and matched rows read the identical proposed-insert value via `excluded.<col>` — so the two
1061
+ * branches agree row-for-row (the round-trip oracle holds by construction).
1062
+ *
1063
+ * The conflict target is the member's stitch key, which `validatePrimaryAdvertisement`
1064
+ * (lens-compiler.ts) guarantees at deploy time to be a declared PRIMARY KEY / non-partial
1065
+ * UNIQUE on the member basis. That deploy guard is what makes the partition sound: the runtime
1066
+ * `on conflict` fires only on a declared PK/UNIQUE violation, so a non-unique stitch key would
1067
+ * double-insert the matched rows instead of ceding/updating them — and could not deploy.
1068
+ *
1069
+ * Two soundness gates (data-independent, plan-time), enforced on **both** flavours:
1070
+ * - a member **value** column the statement does not assign must materialize as null
1071
+ * (nullable + no declared default), else its base default would silently widen the
1072
+ * absent row's logical image — rejected `unsupported-decomposition-update`;
1073
+ * - a NOT NULL base column with no default that no value covers cannot be created —
1074
+ * rejected via {@link assertNoMissingNotNull} (the decomposition create-conflict).
1075
+ */
1076
+ function buildOptionalMemberInsertSelect(ctx, view, shape, member, cells, pred, stmt, action) {
1077
+ const ref = resolveMemberTable(ctx, member);
1078
+ const schema = ref.tableSchema;
1079
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1080
+ const memberKey = singleKeyColumn(view, shape, member);
1081
+ assertNoUnassignedValueColumnWiden(view, shape, member, schema, cells);
1082
+ const targetColumns = [memberKey];
1083
+ const projections = [
1084
+ { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
1085
+ ];
1086
+ for (const c of cells) {
1087
+ targetColumns.push(c.basisColumn);
1088
+ projections.push({ type: 'column', expr: cloneExpr(c.value) });
1089
+ }
1090
+ assertNoMissingNotNull(view, schema, targetColumns.map((baseColumn) => ({ baseColumn })));
1091
+ const select = {
1092
+ type: 'select',
1093
+ columns: projections,
1094
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1095
+ where: pred ? cloneExpr(pred) : undefined,
1096
+ };
1097
+ const upsert = action === 'nothing'
1098
+ ? { type: 'upsert', conflictTarget: [memberKey], action: 'nothing' }
1099
+ : { type: 'upsert', conflictTarget: [memberKey], action: 'update', assignments: cells.map(c => ({ column: c.basisColumn, value: excludedColumn(c.basisColumn) })) };
1100
+ const statement = {
1101
+ type: 'insert',
1102
+ table: memberIdentifier(member),
1103
+ columns: targetColumns,
1104
+ source: select,
1105
+ upsertClauses: [upsert],
1106
+ contextValues: stmt.contextValues,
1107
+ schemaPath: stmt.schemaPath,
1108
+ loc: stmt.loc,
1109
+ };
1110
+ return { table: ref, op: 'insert', statement };
1111
+ }
1112
+ /**
1113
+ * Build the absent-row materialize INSERT for a **member self-reference** update group
1114
+ * (`set c = c + 1`, `set c = coalesce(c, 0) + 1`). Modeled on {@link buildOptionalMemberInsertSelect}
1115
+ * (`'nothing'` flavour) and sharing its two soundness gates, but the projected value is the
1116
+ * self-expression with the owner's own column refs **substituted to NULL** — an absent row's
1117
+ * prior member value is null ({@link substituteOwnerColumnsWithNull}). Every leaf of a `self`
1118
+ * cell is the owner's own column (the classifier proved it), so after substitution the value is a
1119
+ * constant expression evaluable over the anchor scan; a constant sibling cell passes through.
1120
+ *
1121
+ * The select is additionally **filtered to a non-empty materialized image**:
1122
+ * `… where <pred> and (<v1> is not null or <v2> is not null or …)` over the null-substituted
1123
+ * values. This is what makes the always-emit sound: a **null-propagating** self-expression
1124
+ * (`c + 1` → `null + 1` → null) yields a constant-false filter and materializes **no** phantom
1125
+ * row, while one that maps null → non-null (`coalesce(c, 0) + 1` → `1`) is constant-true and
1126
+ * materializes. `on conflict (<memberKey>) do nothing` cedes present rows to the matched UPDATE
1127
+ * (which runs first), so only genuinely-absent rows are created — never an upsert (the matched and
1128
+ * materialize values are computed over different scans and disagree row-for-row by construction).
1129
+ *
1130
+ * The caller emits this builder (and therefore runs the two soundness gates) **only when the
1131
+ * materialize is statically live** — when the null-substituted non-empty filter cannot be proven
1132
+ * constant-false at plan time ({@link foldsConstantFalse}). A provably-dead materialize is skipped
1133
+ * entirely, taking both gates with it (sound: no row materializes, so neither gate can be violated).
1134
+ */
1135
+ function buildSelfMaterializeInsertSelect(ctx, view, shape, member, cells, pred, stmt) {
1136
+ const ref = resolveMemberTable(ctx, member);
1137
+ const schema = ref.tableSchema;
1138
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1139
+ const memberKey = singleKeyColumn(view, shape, member);
1140
+ assertNoUnassignedValueColumnWiden(view, shape, member, schema, cells);
1141
+ // An absent row's prior member value is null, so the materialized image is the self-expression
1142
+ // with the owner's own columns nulled out (a self cell collapses to a constant; a constant cell
1143
+ // is unchanged).
1144
+ const nulled = cells.map(c => ({ basisColumn: c.basisColumn, value: substituteOwnerColumnsWithNull(c.value, member) }));
1145
+ const targetColumns = [memberKey];
1146
+ const projections = [
1147
+ { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
1148
+ ];
1149
+ for (const n of nulled) {
1150
+ targetColumns.push(n.basisColumn);
1151
+ projections.push({ type: 'column', expr: cloneExpr(n.value) });
1152
+ }
1153
+ assertNoMissingNotNull(view, schema, targetColumns.map((baseColumn) => ({ baseColumn })));
1154
+ // Non-empty image filter (the shared {@link selfMaterializeNonEmptyFilter} OR-chain
1155
+ // `<nulledValue> is not null`) so a null-propagating self-expression creates no phantom row
1156
+ // (constant-false), conjoined with the user predicate. The identical filter, folded WITHOUT the
1157
+ // user predicate, is the caller's static dead-materialize check ({@link foldsConstantFalse}); the
1158
+ // shared helper keeps the two from drifting.
1159
+ const where = combineAnd(pred ? cloneExpr(pred) : undefined, selfMaterializeNonEmptyFilter(cells, member));
1160
+ const select = {
1161
+ type: 'select',
1162
+ columns: projections,
1163
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1164
+ where,
1165
+ };
1166
+ const statement = {
1167
+ type: 'insert',
1168
+ table: memberIdentifier(member),
1169
+ columns: targetColumns,
1170
+ source: select,
1171
+ upsertClauses: [{ type: 'upsert', conflictTarget: [memberKey], action: 'nothing' }],
1172
+ contextValues: stmt.contextValues,
1173
+ schemaPath: stmt.schemaPath,
1174
+ loc: stmt.loc,
1175
+ };
1176
+ return { table: ref, op: 'insert', statement };
1177
+ }
1178
+ /**
1179
+ * The self-materialize non-empty image filter: the OR-chain `<nulledValue> is not null` over each
1180
+ * cell's value with the owner's own columns substituted to NULL ({@link substituteOwnerColumnsWithNull}).
1181
+ * Shared by {@link buildSelfMaterializeInsertSelect} (where it is conjoined with the user predicate as
1182
+ * the emitted INSERT's WHERE) and the `hasSelf` branch's static dead-materialize check (folded on its
1183
+ * own, {@link foldsConstantFalse}) so the two can never drift. `is not null` is total, so for a
1184
+ * null-propagating self group with no non-null constant sibling every disjunct is `null is not null`
1185
+ * → the whole chain folds constant-false.
1186
+ */
1187
+ function selfMaterializeNonEmptyFilter(cells, member) {
1188
+ return cells
1189
+ .map((c) => ({ type: 'unary', operator: 'IS NOT NULL', expr: substituteOwnerColumnsWithNull(c.value, member) }))
1190
+ .reduce((acc, e) => acc ? { type: 'binary', operator: 'OR', left: acc, right: e } : e);
1191
+ }
1192
+ /**
1193
+ * True when `expr` provably folds **constant-false** at plan time — used by the `hasSelf` branch to
1194
+ * decide whether the self-materialize is dead (no absent row can materialize). We reuse the engine's
1195
+ * own constant folding rather than hand-rolling an evaluator: build the expression to a scalar plan
1196
+ * node ({@link buildExpression}) and run it through {@link createRuntimeExpressionEvaluator}. The
1197
+ * input is the {@link selfMaterializeNonEmptyFilter} OR-chain (column-ref-free after null
1198
+ * substitution — every self leaf is the owner's own column, a constant sibling carries none), so the
1199
+ * fold is a deterministic plan-time constant. `is not null` never yields NULL, so a dead materialize
1200
+ * folds to boolean `false` (defensively also `0` / `0n`). Anything else — a non-null fold, a Promise
1201
+ * (async / non-constant), or a throw (unbound parameter) — is NOT provably dead, so we stay
1202
+ * conservative and return `false` (emit the materialize + its gates).
1203
+ *
1204
+ * A **non-deterministic** leaf (a function the registry flags non-`DETERMINISTIC` — `random()` or a
1205
+ * volatile UDF — or a subquery, though a self cell carries none) short-circuits to `false` *before*
1206
+ * the fold: a single plan-time evaluation is an unsound proxy for the per-row runtime filter when the
1207
+ * value can vary, and a nullable volatile inside `coalesce(c, …)` could fold NULL (dead) at plan time
1208
+ * yet yield non-null per row at runtime — dropping an absent row the always-emit path would have
1209
+ * materialized. Keeping volatiles on the emit path matches the ticket's `set c = c + random()`
1210
+ * edge-case and the `hasSelf` branch's own contract (only a *foldable* dead materialize is skipped).
1211
+ */
1212
+ function foldsConstantFalse(ctx, expr) {
1213
+ const isDeterministic = (name, argc) => {
1214
+ const fn = ctx.schemaManager.findFunction(name, argc) ?? ctx.schemaManager.findFunction(name, -1);
1215
+ return fn ? (fn.flags & FunctionFlags.DETERMINISTIC) !== 0 : true;
1216
+ };
1217
+ if (containsNonDeterministicCall(expr, isDeterministic))
1218
+ return false;
1219
+ try {
1220
+ const node = buildExpression(ctx, expr);
1221
+ const value = createRuntimeExpressionEvaluator(ctx.db)(node);
1222
+ return value === false || value === 0 || value === 0n;
1223
+ }
1224
+ catch {
1225
+ return false;
1226
+ }
1227
+ }
1228
+ /**
1229
+ * View-soundness gate shared by every optional-member materialize insert-select
1230
+ * ({@link buildOptionalMemberInsertSelect} and {@link buildSelfMaterializeInsertSelect}): an
1231
+ * unassigned member **value** column that would not land null (it is NOT NULL, or carries a
1232
+ * declared default) cannot be materialized without changing the absent row's logical image.
1233
+ * Reject conservatively rather than silently widen the view. (The companion gate —
1234
+ * {@link assertNoMissingNotNull} — is called at each builder's target-column site.)
1235
+ */
1236
+ function assertNoUnassignedValueColumnWiden(view, shape, member, schema, cells) {
1237
+ const assignedBasis = new Set(cells.map(c => c.basisColumn.toLowerCase()));
1238
+ for (const bc of optionalValueColumns(view, shape, member)) {
1239
+ if (assignedBasis.has(bc.toLowerCase()))
1240
+ continue;
1241
+ const col = columnByName(view, schema, bc);
1242
+ if (col.notNull || col.defaultValue !== null) {
1243
+ raiseMutationDiagnostic({
1244
+ reason: 'unsupported-decomposition-update',
1245
+ column: bc,
1246
+ table: view.name,
1247
+ message: `cannot update logical table '${view.name}': materializing an absent row of optional member '${member.relationId}' would leave value column '${bc}' to a base default (it is NOT NULL or declares a default), silently widening the row; assign every value column of the member in this statement, or restrict the update to present rows`,
1248
+ });
1249
+ }
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Emit the per-attribute base ops for an EAV pivot member's update group. Each attribute is
1254
+ * an independent triple, routed by the cell's null-ness and {@link ValueKind}:
1255
+ *
1256
+ * - **null** value → delete its triple for the matched entities.
1257
+ * - **`anchor`** value (`set p = id * 2`) → one **upsert** (`do update`) unifies the matched
1258
+ * UPDATE and the absent materialize INSERT, keyed on `(entity, attribute)` — the value is
1259
+ * computed once over the anchor scan and matched entities read it via `excluded.<valCol>`.
1260
+ * - **`constant`** value → matched UPDATE of the value column + `on conflict (entity, attr)
1261
+ * do nothing` materialize INSERT for entities lacking the triple.
1262
+ * - **`captured`** value (an EAV self-reference `set p = p + 1`, a cross-member read, or an
1263
+ * embedded subquery — all of which an EAV value column lowers to a subquery for) → the
1264
+ * single-identity capture analogue ({@link emitEavCapturedAttr}): the value is materialized once
1265
+ * over the planned body under a `srcN`, the matched UPDATE reads it back by the **entity** column,
1266
+ * and a filtered materialize INSERT reads it back by the **anchor key** (non-null gated). Requires
1267
+ * the capture carrier (the build path).
1268
+ *
1269
+ * A `self` value cannot occur here: an EAV value column substitutes to a correlated subquery, so a
1270
+ * self-reference lowers to a subquery and classifies `captured`, not `self`.
1271
+ */
1272
+ function emitEavMemberUpdate(ctx, view, shape, member, cells, pred, stmt, ops, registerCapturedExpr) {
1273
+ const pivot = member.attributePivot;
1274
+ for (const cell of cells) {
1275
+ if (cell.isNull) {
1276
+ ops.push(buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, 'delete'));
1277
+ }
1278
+ else if (cell.kind === 'anchor') {
1279
+ ops.push(buildEavInsertSelect(ctx, view, shape, member, pivot, cell, pred, stmt, 'update'));
1280
+ }
1281
+ else if (cell.kind === 'captured') {
1282
+ emitEavCapturedAttr(ctx, view, shape, member, pivot, cell, pred, stmt, ops, registerCapturedExpr);
1283
+ }
1284
+ else {
1285
+ ops.push(buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, 'update'));
1286
+ ops.push(buildEavInsertSelect(ctx, view, shape, member, pivot, cell, pred, stmt, 'nothing'));
1287
+ }
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Emit the captured triple pair for one arbitrary EAV attribute cell — the per-attribute analogue
1292
+ * of {@link emitCapturedMemberUpdate} (columnar). The cell's already-lowered base-term value (a
1293
+ * self-reference / cross-member / subquery value over the planned body) is projected into the
1294
+ * up-front `__vmupd_keys` capture under a stable `srcN` alias, then:
1295
+ *
1296
+ * - the **matched UPDATE** ({@link buildEavAttrOp} `'update'`, value overridden by the read-back)
1297
+ * sets the value column of every existing `<attr>` triple for the matched entities to
1298
+ * `(select srcN from __vmupd_keys k where k.k0_0 = <entity>)` — the captured value correlated by
1299
+ * the entity column (which holds the anchor key, the shared stitch value). Unfiltered: a captured
1300
+ * null on a matched triple writes `val = null`, which reads identically to an absent triple
1301
+ * through the get-body subquery (a benign physical divergence from the explicit `set p = null`
1302
+ * delete — never a widen).
1303
+ * - the **materialize INSERT** ({@link buildEavCapturedInsert}) creates one `<attr>` triple per
1304
+ * matched entity that lacks it, the value read back by the **anchor key**, gated on a runtime
1305
+ * non-null (so an entity whose captured value is null — e.g. incrementing an attribute it does
1306
+ * not have — springs no phantom triple) with `on conflict (entity, attr) do nothing` to cede the
1307
+ * entities that already have the triple to the matched UPDATE (emitted first).
1308
+ *
1309
+ * Requires the capture carrier (the build path); absent it the defensive legacy reject fires (an
1310
+ * arbitrary EAV value cannot be threaded without the `__vmupd_keys` substrate).
1311
+ */
1312
+ function emitEavCapturedAttr(ctx, view, shape, member, pivot, cell, pred, stmt, ops, registerCapturedExpr) {
1313
+ if (!registerCapturedExpr) {
1314
+ raiseMutationDiagnostic({
1315
+ reason: 'unsupported-decomposition-update',
1316
+ column: cell.attribute,
1317
+ table: view.name,
1318
+ message: `cannot update logical table '${view.name}': writing EAV attribute '${cell.attribute}' with this value needs the per-row capture carrier to thread it across the matched-update and materialize-insert triple pair, which the legacy non-build path does not supply`,
1319
+ });
1320
+ }
1321
+ // Register the lowered value into the capture (one `srcN` per attribute), keyed by the attribute
1322
+ // so both read-back sites (entity column, anchor key) bind the same alias.
1323
+ const srcAlias = registerCapturedExpr(`cap:${member.relationId.toLowerCase()}:attr:${cell.attribute.toLowerCase()}`, cloneExpr(cell.value));
1324
+ // Matched UPDATE: existing triples read the captured value back by the entity column.
1325
+ ops.push(buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, 'update', capturedValueSubquery(srcAlias, 0, [pivot.entityColumn])));
1326
+ // Materialize INSERT: entities lacking the triple, value read back by the anchor key, non-null filtered.
1327
+ ops.push(buildEavCapturedInsert(ctx, view, shape, member, pivot, cell, srcAlias, pred, stmt));
1328
+ }
1329
+ /**
1330
+ * One matched EAV op for an attribute: `update <pivot> set <valCol> = <value>` (upsert
1331
+ * value branch) or `delete from <pivot>` (null branch), each scoped
1332
+ * `where <attrCol> = '<attribute>' and <entityCol> in (<anchor subquery>)`. `valueOverride`
1333
+ * supplies the matched-UPDATE value for a `captured` cell (the `__vmupd_keys` read-back correlated
1334
+ * by the entity column); absent, the cell's own lowered value is used (the `constant` branch).
1335
+ */
1336
+ function buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, op, valueOverride) {
1337
+ const where = combineAnd(eavAttrEquals(pivot, cell.attribute), {
1338
+ type: 'in',
1339
+ expr: { type: 'column', name: pivot.entityColumn },
1340
+ subquery: anchorKeySubquery(shape, pred),
1341
+ });
1342
+ const table = memberIdentifier(member);
1343
+ const resolved = resolveMemberTable(ctx, member);
1344
+ if (op === 'delete') {
1345
+ const statement = {
1346
+ type: 'delete', table, where,
1347
+ contextValues: stmt.contextValues, schemaPath: stmt.schemaPath, loc: stmt.loc,
1348
+ };
1349
+ return { table: resolved, op: 'delete', statement };
1350
+ }
1351
+ const statement = {
1352
+ type: 'update', table,
1353
+ assignments: [{ column: pivot.valueColumn, value: valueOverride ?? cloneExpr(cell.value) }],
1354
+ where,
1355
+ contextValues: stmt.contextValues, schemaPath: stmt.schemaPath, loc: stmt.loc,
1356
+ };
1357
+ return { table: resolved, op: 'update', statement };
1358
+ }
1359
+ /**
1360
+ * Build the anchor-keyed EAV triple insert-select for one attribute, in one of two `action`
1361
+ * flavours sharing the identical select: `insert into <pivot> (<entity>, <attr>, <val>) select
1362
+ * <anchorKey>, '<attribute>', <value> from <anchor> where <pred>` followed by
1363
+ *
1364
+ * - **`'nothing'`** (constant cell, absent branch) — `on conflict (<entity>, <attr>) do nothing`:
1365
+ * one new triple per matched entity that lacks this attribute (entities whose triple exists
1366
+ * conflict and are ceded to the separate matched UPDATE).
1367
+ * - **`'update'`** (anchor-resolvable cell) — `on conflict (<entity>, <attr>) do update set
1368
+ * <valCol> = excluded.<valCol>`: **replaces both** the matched UPDATE and the materialize
1369
+ * INSERT, the value computed once over the anchor scan and read by matched entities via
1370
+ * `excluded.<valCol>`.
1371
+ *
1372
+ * The conflict target `(entity, attribute)` — NOT the stitch key (`entity` alone, which is
1373
+ * intentionally one-to-many) — is guaranteed a declared PRIMARY KEY / non-partial UNIQUE on
1374
+ * the pivot basis by `validatePrimaryAdvertisement` (lens-compiler.ts) at deploy time. That
1375
+ * is what keeps both the matched/materialize partition here and the get-side correlated
1376
+ * subquery single-valued; a non-unique `(entity, attr)` could not deploy.
1377
+ */
1378
+ function buildEavInsertSelect(ctx, view, shape, member, pivot, cell, pred, stmt, action) {
1379
+ const ref = resolveMemberTable(ctx, member);
1380
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1381
+ const projections = [
1382
+ { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
1383
+ { type: 'column', expr: { type: 'literal', value: cell.attribute } },
1384
+ { type: 'column', expr: cloneExpr(cell.value) },
1385
+ ];
1386
+ const select = {
1387
+ type: 'select',
1388
+ columns: projections,
1389
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1390
+ where: pred ? cloneExpr(pred) : undefined,
1391
+ };
1392
+ const upsert = action === 'nothing'
1393
+ ? { type: 'upsert', conflictTarget: [pivot.entityColumn, pivot.attributeColumn], action: 'nothing' }
1394
+ : { type: 'upsert', conflictTarget: [pivot.entityColumn, pivot.attributeColumn], action: 'update', assignments: [{ column: pivot.valueColumn, value: excludedColumn(pivot.valueColumn) }] };
1395
+ const statement = {
1396
+ type: 'insert',
1397
+ table: memberIdentifier(member),
1398
+ columns: [pivot.entityColumn, pivot.attributeColumn, pivot.valueColumn],
1399
+ source: select,
1400
+ upsertClauses: [upsert],
1401
+ contextValues: stmt.contextValues,
1402
+ schemaPath: stmt.schemaPath,
1403
+ loc: stmt.loc,
1404
+ };
1405
+ return { table: ref, op: 'insert', statement };
1406
+ }
1407
+ /**
1408
+ * Build the absent-entity materialize INSERT for a `captured` EAV attribute cell — the triple
1409
+ * analogue of {@link buildCapturedMaterializeInsert} (columnar). `insert into <pivot> (<entity>,
1410
+ * <attr>, <val>) select <anchorKey>, '<attribute>', (select srcN from __vmupd_keys k where k.k0_0
1411
+ * = <anchorKey>) from <anchor> where <pred> and (select srcN …) is not null on conflict (<entity>,
1412
+ * <attr>) do nothing`. The value and the non-null filter both read the captured `srcN` back by the
1413
+ * anchor key. That non-null filter is the **runtime** analogue of the columnar self path's
1414
+ * plan-time fold: a captured EAV value is data-dependent (a self-reference over an entity that
1415
+ * lacks the attribute captures `null + …` = null), so a per-row filter — not a plan-time decision
1416
+ * — gates whether the triple materializes, leaving no phantom null triple. `on conflict (entity,
1417
+ * attr) do nothing` cedes entities that already have the triple to the matched UPDATE (emitted
1418
+ * first); the `(entity, attr)` conflict target is the deploy-guaranteed pivot PK / non-partial
1419
+ * UNIQUE (as in {@link buildEavInsertSelect}). Always emitted (the filter decides per row).
1420
+ */
1421
+ function buildEavCapturedInsert(ctx, view, shape, member, pivot, cell, srcAlias, pred, stmt) {
1422
+ const ref = resolveMemberTable(ctx, member);
1423
+ const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1424
+ // A fresh read-back subquery per use (projection + filter) — distinct AST nodes.
1425
+ const capturedVal = () => capturedValueSubquery(srcAlias, 0, [anchorKey]);
1426
+ const projections = [
1427
+ { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
1428
+ { type: 'column', expr: { type: 'literal', value: cell.attribute } },
1429
+ { type: 'column', expr: capturedVal() },
1430
+ ];
1431
+ const nonNull = { type: 'unary', operator: 'IS NOT NULL', expr: capturedVal() };
1432
+ const where = combineAnd(pred ? cloneExpr(pred) : undefined, nonNull);
1433
+ const select = {
1434
+ type: 'select',
1435
+ columns: projections,
1436
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1437
+ where,
1438
+ };
1439
+ const statement = {
1440
+ type: 'insert',
1441
+ table: memberIdentifier(member),
1442
+ columns: [pivot.entityColumn, pivot.attributeColumn, pivot.valueColumn],
1443
+ source: select,
1444
+ upsertClauses: [{ type: 'upsert', conflictTarget: [pivot.entityColumn, pivot.attributeColumn], action: 'nothing' }],
1445
+ contextValues: stmt.contextValues,
1446
+ schemaPath: stmt.schemaPath,
1447
+ loc: stmt.loc,
1448
+ };
1449
+ return { table: ref, op: 'insert', statement };
1450
+ }
1451
+ /** `<attrCol> = '<attribute>'` — the pivot attribute selector (matched by value). */
1452
+ function eavAttrEquals(pivot, attribute) {
1453
+ return { type: 'binary', operator: '=', left: { type: 'column', name: pivot.attributeColumn }, right: { type: 'literal', value: attribute } };
1454
+ }
1455
+ /**
1456
+ * The optional member's **value** basis columns (its non-key logical mappings) — the
1457
+ * set the all-null delete gate ranges over. The shared key is threaded separately and
1458
+ * is never a value column.
1459
+ */
1460
+ function optionalValueColumns(view, shape, member) {
1461
+ const keyLower = singleKeyColumn(view, shape, member).toLowerCase();
1462
+ const out = [];
1463
+ for (const m of member.columns) {
1464
+ const expr = m.basisExpr;
1465
+ if (expr.type !== 'column')
1466
+ continue; // a computed mapping is read-only, not a value column
1467
+ if (expr.name.toLowerCase() === keyLower)
1468
+ continue;
1469
+ out.push(expr.name);
1470
+ }
1471
+ return out;
1472
+ }
1473
+ /** True for a syntactic null literal (the delete / absent-no-op trigger). */
1474
+ function isNullLiteral(expr) {
1475
+ return expr.type === 'literal' && expr.value === null;
1476
+ }
1477
+ /**
1478
+ * `excluded.<col>` — the proposed-insert value of an upsert's `do update set` assignment
1479
+ * (registered in the upsert scope by `building/insert.ts`; `NEW.<col>` is the equivalent
1480
+ * alias). An anchor-resolvable upsert assigns each matched row `<col> = excluded.<col>` so
1481
+ * it reads the identical anchor-computed value the absent rows insert.
1482
+ */
1483
+ function excludedColumn(col) {
1484
+ return { type: 'column', name: col, table: 'excluded' };
1485
+ }
1486
+ /**
1487
+ * Strip the owning member's relationId qualifier from a lowered self-reference value, so a
1488
+ * per-member UPDATE over it (`<member>.c + 1`) targets that table directly (`c + 1`). The
1489
+ * classifier proved every column ref is the owner's, so a plain strip suffices (a constant
1490
+ * sibling carries no ref — a no-op). Mirrors {@link rewriteAssignedValue}'s strip without
1491
+ * its cross-member reject (the classifier already excluded a foreign qualifier).
1492
+ */
1493
+ function stripMemberQualifier(value, owner) {
1494
+ return transformExpr(value, (col) => (col.table === owner.relationId ? { type: 'column', name: col.name } : undefined));
1495
+ }
1496
+ /**
1497
+ * Substitute the owning member's own column refs in a lowered self-reference value with a NULL
1498
+ * literal — an absent row has no member row, so its prior value is null. So `c + 1` lowers to
1499
+ * `null + 1` (→ null, filtered out as a phantom row) and `coalesce(c, 0) + 1` to `coalesce(null, 0)
1500
+ * + 1` (→ 1, materializes). The classifier proved every column ref of a `self` cell is the owner's,
1501
+ * so substituting only the owner qualifier suffices; a constant sibling carries no owner ref and is
1502
+ * left unchanged. Mirrors {@link stripMemberQualifier} but maps the owner's columns to NULL rather
1503
+ * than to a bare member-scoped reference (the materialize evaluates over the anchor, not the member).
1504
+ */
1505
+ function substituteOwnerColumnsWithNull(value, owner) {
1506
+ return transformExpr(value, (col) => (col.table === owner.relationId ? { type: 'literal', value: null } : undefined));
1507
+ }
1508
+ // --- predicate / subquery construction ------------------------------------
1509
+ /**
1510
+ * The user WHERE rewritten from logical columns into the get body's base terms,
1511
+ * after the anchor-resolvable gate ({@link assertAnchorScoped}) — so each member's
1512
+ * identifying set can be read from the anchor alone (see the file header). The gate
1513
+ * admits an anchor identity column **and** a computed mapping whose basis is the
1514
+ * anchor (`bumped = 11` → `a + 1 = 11`): both substitute into a predicate over the
1515
+ * anchor's own base columns. A predicate touching a non-anchor member (or an EAV
1516
+ * pivot / a subquery) is deferred onto the snapshot-consistent substrate. The
1517
+ * substitution into base terms is the AST construction the anchor subquery rides; the
1518
+ * **gate decision** is read off the threaded backward lineage, not a base-qualifier
1519
+ * scan of the substituted expression.
1520
+ */
1521
+ function anchorPredicate(view, shape, where) {
1522
+ if (!where)
1523
+ return undefined;
1524
+ assertAnchorScoped(view, shape, where);
1525
+ return substituteViewColumns(where, shape, view);
1526
+ }
1527
+ /**
1528
+ * Gate a user WHERE to **anchor-resolvable** references via the threaded backward
1529
+ * lineage: every logical column the predicate names must resolve entirely to the
1530
+ * anchor member's own base terms — an identity base column on the anchor
1531
+ * ({@link classifyColumn} → `member` whose relation is the anchor), *or* a computed
1532
+ * mapping whose basis lives on the anchor ({@link classifyColumn} → `computed-mapping`
1533
+ * whose member is the anchor, e.g. `bumped = a + 1` → `a + 1 = 11`). Either substitutes
1534
+ * into a predicate over the anchor's base columns, which the `anchorKeySubquery`
1535
+ * already evaluates, so no new substrate is needed.
1536
+ *
1537
+ * A column backed by a genuine **non-anchor member**, an **EAV pivot**, or an embedded
1538
+ * **subquery** defers onto the snapshot-consistent multi-member substrate — each with
1539
+ * its own accurate message ({@link nonAnchorPredicateDiagnostic}), since an EAV /
1540
+ * unbacked / subquery predicate is not a "non-anchor member" and must not be
1541
+ * misattributed as one. A name that is not a logical column of the table at all is an
1542
+ * encapsulation leak, rejected as `unknown-view-column` (consistent with the
1543
+ * single-source / multi-source `assertTopLevelViewColumns` guard — a typo'd /
1544
+ * projected-away name is a user error, not a deferred multi-member shape). (Replaces
1545
+ * the retired `collectColumnQualifiers` base-qualifier scan — the anchor decision now
1546
+ * reads `updateLineage`, the same backward walk the multi-source path consumes.)
1547
+ */
1548
+ function assertAnchorScoped(view, shape, where) {
1549
+ const refs = collectViewColumnRefs(where);
1550
+ // Encapsulation-leak guard first: a name the logical table does not expose is an
1551
+ // unknown view column (it would otherwise be mislabeled a "non-anchor member" below).
1552
+ for (const name of refs.names) {
1553
+ if (!shape.columns.some(c => c.name === name)) {
1554
+ raiseMutationDiagnostic({
1555
+ reason: 'unknown-view-column',
1556
+ column: name,
1557
+ table: view.name,
1558
+ message: `cannot write through logical table '${view.name}': '${name}' is not a column of the logical table`,
1559
+ suggestion: `logical table '${view.name}' exposes: ${shape.columns.map(c => c.displayName).join(', ')}.`,
1560
+ });
1561
+ }
1562
+ }
1563
+ // A subquery defers regardless of which columns it names (it may name none) — its
1564
+ // multi-member fan-out needs the snapshot-consistent substrate. Checked first so the
1565
+ // message is subquery-specific rather than a misattributed "non-anchor member".
1566
+ if (refs.hasSubquery) {
1567
+ raiseMutationDiagnostic({
1568
+ reason: 'unsupported-decomposition-predicate',
1569
+ table: view.name,
1570
+ message: `cannot write through logical table '${view.name}': the WHERE embeds a subquery; a predicate-honest multi-member fan-out needs snapshot-consistent base-op execution (deferred — filter only on anchor base columns)`,
1571
+ });
1572
+ }
1573
+ const anchorId = shape.anchor.relationId;
1574
+ for (const name of refs.names) {
1575
+ const route = classifyColumn(view, shape, name);
1576
+ // Anchor-resolvable — an identity base column on the anchor, OR a computed mapping
1577
+ // whose basis is the anchor. Both `member` and `computed-mapping` carry `member`,
1578
+ // so the union narrows correctly.
1579
+ if ((route.kind === 'member' || route.kind === 'computed-mapping') && route.member.relationId === anchorId) {
1580
+ continue;
1581
+ }
1582
+ raiseMutationDiagnostic(nonAnchorPredicateDiagnostic(view, name, route));
1583
+ }
1584
+ }
1585
+ /**
1586
+ * The deferral diagnostic for a WHERE column that is **not** anchor-resolvable — a
1587
+ * non-anchor member, an EAV pivot, or a name backed by no member. Each keeps the
1588
+ * `unsupported-decomposition-predicate` reason (the structured contract is unchanged)
1589
+ * and differs only in the human message, so the misattribution the support fix removes
1590
+ * does not recur: an EAV / unbacked column is not a "non-anchor member". The
1591
+ * genuine-non-anchor case preserves the `non-anchor decomposition member` substring the
1592
+ * deferral test pins.
1593
+ */
1594
+ function nonAnchorPredicateDiagnostic(view, name, route) {
1595
+ const head = `cannot write through logical table '${view.name}': the WHERE references column '${name}',`;
1596
+ const need = `a predicate-honest multi-member fan-out needs snapshot-consistent base-op execution`;
1597
+ switch (route.kind) {
1598
+ case 'eav':
1599
+ return {
1600
+ reason: 'unsupported-decomposition-predicate', column: name, table: view.name,
1601
+ message: `${head} backed by an EAV pivot member; ${need} (deferred — filter only on anchor base columns)`,
1602
+ };
1603
+ case 'unbacked':
1604
+ return {
1605
+ reason: 'unsupported-decomposition-predicate', column: name, table: view.name,
1606
+ message: `${head} which is not backed by any decomposition member; ${need} (deferred — filter only on anchor base columns)`,
1607
+ };
1608
+ default: // 'member' / 'computed-mapping' on a non-anchor member
1609
+ return {
1610
+ reason: 'unsupported-decomposition-predicate', column: name, table: view.name,
1611
+ message: `${head} backed by a non-anchor decomposition member; ${need} (deferred — filter only on the anchor / shared key, or pin the rows via the anchor)`,
1612
+ };
1613
+ }
1614
+ }
1615
+ /** `select <anchorKey> from <anchorTable> <anchorAlias> [where <pred>]` — the shared identifying set. */
1616
+ function anchorKeySubquery(shape, pred) {
1617
+ const anchorKey = singleKeyColumn(undefined, shape, shape.anchor);
1618
+ return {
1619
+ type: 'select',
1620
+ columns: [{ type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } }],
1621
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1622
+ where: pred ? cloneExpr(pred) : undefined,
1623
+ };
1624
+ }
1625
+ /**
1626
+ * Substitute references to logical columns (unqualified, or qualified by the
1627
+ * logical table's own name) with their backing get-body expression. Base-member-
1628
+ * qualified references are left untouched.
1629
+ */
1630
+ function substituteViewColumns(expr, shape, view) {
1631
+ const viewName = view.name.toLowerCase();
1632
+ return transformExpr(expr, (col) => {
1633
+ if (col.table && col.table.toLowerCase() !== viewName)
1634
+ return undefined;
1635
+ const repl = shape.viewColToBaseRef.get(col.name.toLowerCase());
1636
+ return repl ? cloneExpr(repl) : undefined;
1637
+ });
1638
+ }
1639
+ /** Strip the anchor's alias qualifier so a predicate targets the bare anchor UPDATE/DELETE. */
1640
+ function stripAnchorQualifier(expr, shape) {
1641
+ return transformExpr(expr, (col) => (col.table === shape.anchor.relationId ? { type: 'column', name: col.name } : undefined));
1642
+ }
1643
+ // --- shape helpers --------------------------------------------------------
1644
+ /** True when `logical` (lowercased) is one of the anchor's shared-key columns. */
1645
+ function isSharedKeyColumn(shape, logical) {
1646
+ const keys = shape.storage.sharedKey.keyColumnsByRelation.get(shape.anchor.relationId) ?? [];
1647
+ return keys.some(k => k.toLowerCase() === logical);
1648
+ }
1649
+ /**
1650
+ * The single shared-key column for a member. v1 threads a single-column key
1651
+ * (mirrors `multi-source.ts`' single-column-PK boundary); a composite/absent key
1652
+ * is deferred. `view` is optional purely so the deferral message can name the
1653
+ * logical table (the anchor-subquery call site has none in scope).
1654
+ */
1655
+ function singleKeyColumn(view, shape, member) {
1656
+ const keys = shape.storage.sharedKey.keyColumnsByRelation.get(member.relationId) ?? [];
1657
+ if (keys.length !== 1) {
1658
+ raiseMutationDiagnostic({
1659
+ reason: 'unsupported-decomposition-key',
1660
+ table: view?.name,
1661
+ message: `cannot write through a decomposition with a ${keys.length === 0 ? 'missing' : 'composite'} shared key on member '${member.relationId}': v1 fan-out threads a single-column key`,
1662
+ });
1663
+ }
1664
+ return keys[0];
1665
+ }
1666
+ function memberIdentifier(member) {
1667
+ return { type: 'identifier', name: member.relation.table, schema: member.relation.schema };
1668
+ }
1669
+ function memberIdentifierSource(member) {
1670
+ return { type: 'table', table: memberIdentifier(member) };
1671
+ }
1672
+ function resolveMemberTable(ctx, member) {
1673
+ return buildTableReference(memberIdentifierSource(member), ctx).tableRef;
1674
+ }
1675
+ /**
1676
+ * Collect the **logical column names** a user predicate references (lowercased,
1677
+ * ignoring any view-name qualifier) and whether it embeds a subquery. The anchor
1678
+ * gate ({@link assertAnchorScoped}) maps each collected name to its owning member
1679
+ * via the threaded backward lineage — so the gate decision is lineage-driven; this
1680
+ * walk only enumerates which columns to check (the user-term analogue of the retired
1681
+ * `collectColumnQualifiers`, which scanned base-table qualifiers on the substituted
1682
+ * expression).
1683
+ */
1684
+ function collectViewColumnRefs(expr) {
1685
+ const names = new Set();
1686
+ let hasSubquery = false;
1687
+ const walk = (node) => {
1688
+ if (Array.isArray(node)) {
1689
+ node.forEach(walk);
1690
+ return;
1691
+ }
1692
+ if (!node || typeof node !== 'object' || !('type' in node))
1693
+ return;
1694
+ const n = node;
1695
+ if (n.type === 'column') {
1696
+ if (typeof n.name === 'string')
1697
+ names.add(n.name.toLowerCase());
1698
+ return;
1699
+ }
1700
+ if (n.type === 'subquery' || n.type === 'select' || n.type === 'exists') {
1701
+ hasSubquery = true;
1702
+ return;
1703
+ }
1704
+ for (const v of Object.values(n))
1705
+ walk(v);
1706
+ };
1707
+ walk(expr);
1708
+ return { names, hasSubquery };
1709
+ }
1710
+ function rejectReturning(view, returning) {
1711
+ if (returning && returning.length > 0) {
1712
+ raiseMutationDiagnostic({
1713
+ reason: 'returning-through-view',
1714
+ table: view.name,
1715
+ message: `RETURNING through logical table '${view.name}' is not yet supported`,
1716
+ });
1717
+ }
1718
+ }
1719
+ //# sourceMappingURL=decomposition.js.map