@quereus/quereus 3.3.0 → 4.1.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 +204 -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 +716 -27
  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 +13 -0
  80. package/dist/src/func/registration.d.ts.map +1 -1
  81. package/dist/src/func/registration.js +5 -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 +20 -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 +17 -16
@@ -0,0 +1,1988 @@
1
+ import { resolvePkDefaultConflict, findDeclaredKey, findGoverningBasisKeys } from './table.js';
2
+ import { addFd, superkeyToFd } from '../planner/util/fd-utils.js';
3
+ import { proveEffectiveKeyUnique } from '../planner/analysis/coverage-prover.js';
4
+ import { resolveBaseSite } from '../planner/analysis/update-lineage.js';
5
+ import { viewComplement } from '../planner/analysis/view-complement.js';
6
+ import { getTrustedCheckExtraction, containsNonDeterministicCall } from '../planner/analysis/check-extraction.js';
7
+ import { createRuntimeExpressionEvaluator } from '../planner/analysis/const-evaluator.js';
8
+ import { classifyViewBody } from '../planner/mutation/propagate.js';
9
+ import { substituteNewRefs, transformExpr } from '../planner/mutation/scope-transform.js';
10
+ import { ProjectNode } from '../planner/nodes/project-node.js';
11
+ import { PlanNodeType } from '../planner/nodes/plan-node-type.js';
12
+ import { astToString, expressionToString } from '../emit/ast-stringify.js';
13
+ import { PhysicalType } from '../types/logical-type.js';
14
+ import { getReservedTagByTemplate, LENS_WRITABLE_INTENT_TAG } from './reserved-tags.js';
15
+ import { createLogger } from '../common/logger.js';
16
+ import { ConflictResolution, FunctionFlags } from '../common/constants.js';
17
+ import { compareSqlValues } from '../util/comparison.js';
18
+ const log = createLogger('schema:lens-prover');
19
+ /**
20
+ * The warning-severity advisory codes that flow through ack/escalation
21
+ * governance (`lens-ack.ts`). This is the single authoritative source of the
22
+ * governable vocabulary: a policy (`error-on` / `require-ack`) may legitimately
23
+ * name any of these, and only these. Keeping the list here means it cannot drift
24
+ * from what the prover actually emits.
25
+ */
26
+ const ADVISORY_CODE_LIST = [
27
+ 'lens.pk-not-reconstructible',
28
+ 'lens.no-backing-index',
29
+ 'lens.no-answering-structure',
30
+ 'lens.partial-override',
31
+ 'lens.getput-lossy',
32
+ 'lens.over-restrictive-basis-key',
33
+ ];
34
+ /** The advisory codes a policy may escalate, as a runtime set (drift-locked by a unit test). */
35
+ export const ACKNOWLEDGEABLE_ADVISORY_CODES = new Set(ADVISORY_CODE_LIST);
36
+ /**
37
+ * Proves one lens slot and classifies its constraints. Pure analysis — does not
38
+ * mutate the slot or the catalog; the caller (`lens-compiler.ts`) records the
39
+ * `obligations` / `readOnly` on the slot and aggregates the diagnostics into the
40
+ * deploy report.
41
+ */
42
+ export function proveLens(slot, db) {
43
+ const ctx = buildProveContext(slot, db);
44
+ const errors = [];
45
+ const warnings = [];
46
+ checkColumnCoverage(ctx, errors);
47
+ checkTypeAndNullability(ctx, errors);
48
+ // Run the round-trip enumeration ONCE, up front: it produces both the
49
+ // proven-bijection verdict per authored column (the same `{proved, injective}`
50
+ // that suppresses `lens.getput-lossy`) and the cached enumeration the
51
+ // round-trip diagnostics consume. Hoisting it ahead of key reconstructibility
52
+ // is what lets a bijective authored PK column count as reconstructible
53
+ // (writable) instead of read-only — a bijection maps a written logical key to
54
+ // exactly one basis key and back. {@link emitRoundTrip} consumes the cache; it
55
+ // never re-runs the enumeration.
56
+ const rt = analyzeRoundTrip(ctx);
57
+ const bijectiveAuthored = bijectiveAuthoredColumns(rt);
58
+ const readOnly = checkKeyReconstructibility(ctx, bijectiveAuthored, warnings);
59
+ emitRoundTrip(ctx, rt, readOnly, errors, warnings);
60
+ const obligations = classifyObligations(ctx, readOnly, bijectiveAuthored, errors, warnings);
61
+ checkAnsweringStructures(ctx, warnings);
62
+ checkPartialOverride(ctx, warnings);
63
+ return { errors, warnings, obligations, readOnly };
64
+ }
65
+ function buildProveContext(slot, db) {
66
+ const table = slot.logicalTable;
67
+ const logicalColIndex = new Map();
68
+ table.columns.forEach((c, i) => logicalColIndex.set(c.name.toLowerCase(), i));
69
+ const { outputIndex, outputColumns } = buildOutputIndex(slot);
70
+ const basisSchemaName = slot.defaultBasis.schemaName;
71
+ return {
72
+ slot,
73
+ db,
74
+ table,
75
+ logicalColIndex,
76
+ outputColumns,
77
+ outputIndex,
78
+ root: planBody(db, slot.compiledBody),
79
+ basisSource: resolveSingleBasisSource(db.schemaManager, slot.compiledBody, basisSchemaName),
80
+ basisSchemaName,
81
+ };
82
+ }
83
+ /**
84
+ * The output-index map for a lens slot: logical column name (lower) →
85
+ * body-output column index, plus the columns in declaration order. The single
86
+ * source of truth for the body's output-column-index space — shared by
87
+ * {@link buildProveContext} and {@link computeLensAssertedKeyFds} so the two can
88
+ * never drift (the FD-contribution columns must land in exactly the space the
89
+ * prover proved its keys in).
90
+ */
91
+ function buildOutputIndex(slot) {
92
+ const outputColumns = [];
93
+ const outputIndex = new Map();
94
+ for (const p of slot.columnProvenance) {
95
+ outputIndex.set(p.logicalColumn.toLowerCase(), outputColumns.length);
96
+ outputColumns.push(p.logicalColumn);
97
+ }
98
+ return { outputIndex, outputColumns };
99
+ }
100
+ /**
101
+ * Plans + optimizes the compiled body so `physical.fds` and output column types
102
+ * are available. Returns undefined (graceful) if planning throws — the body the
103
+ * compiler produced should plan, but a prover that itself crashes the deploy is
104
+ * worse than one that skips plan-derived checks.
105
+ */
106
+ function planBody(db, body) {
107
+ try {
108
+ return db.getPlan(astToString(body)).getRelations()[0];
109
+ }
110
+ catch (e) {
111
+ log('lens-prover: body failed to plan, skipping plan-derived checks: %O', e);
112
+ return undefined;
113
+ }
114
+ }
115
+ /** The single basis `table` source of a body, or undefined for a multi-source / opaque FROM. */
116
+ function resolveSingleBasisSource(schemaManager, body, basisSchemaName) {
117
+ const from = body.from;
118
+ if (!from || from.length !== 1)
119
+ return undefined;
120
+ const node = from[0];
121
+ if (node.type !== 'table')
122
+ return undefined;
123
+ const schemaName = node.table.schema ?? basisSchemaName;
124
+ return schemaManager.getSchema(schemaName)?.getTable(node.table.name);
125
+ }
126
+ /**
127
+ * The single basis `table` source of a lens slot's compiled body, or undefined for
128
+ * a multi-source / opaque FROM — the exported slot-level entry point. Reused by the
129
+ * lens FK-redundancy detector (`planner/mutation/lens-enforcement.ts`) so it walks
130
+ * the same single-source `from` the prover does, resolving a bare table name against
131
+ * the slot's own default basis schema. Reads only the catalog, so it is safe over a
132
+ * lightweight (un-planned) caller.
133
+ */
134
+ export function resolveSlotBasisSource(slot, schemaManager) {
135
+ return resolveSingleBasisSource(schemaManager, slot.compiledBody, slot.defaultBasis.schemaName);
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Error: column coverage (lens.uncovered-column)
139
+ // ---------------------------------------------------------------------------
140
+ /**
141
+ * Every logical column resolves to a basis expression. The compiler's gap-fill
142
+ * path already errors on an uncovered column before the prover runs, so this is
143
+ * the formal restatement / backstop: a provenance entry must exist for every
144
+ * column and must be `override` or `default`.
145
+ */
146
+ function checkColumnCoverage(ctx, errors) {
147
+ const provByName = new Map(ctx.slot.columnProvenance.map(p => [p.logicalColumn.toLowerCase(), p]));
148
+ for (const col of ctx.table.columns) {
149
+ const p = provByName.get(col.name.toLowerCase());
150
+ if (!p) {
151
+ errors.push({
152
+ code: 'lens.uncovered-column',
153
+ severity: 'error',
154
+ site: { table: ctx.table.name, column: col.name },
155
+ message: `lens: logical column '${ctx.table.schemaName}.${ctx.table.name}.${col.name}' is not covered by the compiled body (no override mapping and no default gap-fill)`,
156
+ });
157
+ }
158
+ }
159
+ }
160
+ function physicalFamily(pt) {
161
+ switch (pt) {
162
+ case PhysicalType.INTEGER:
163
+ case PhysicalType.REAL: return 'numeric';
164
+ case PhysicalType.TEXT: return 'text';
165
+ case PhysicalType.BLOB: return 'blob';
166
+ case PhysicalType.BOOLEAN: return 'boolean';
167
+ default: return 'other'; // NULL / OBJECT — permissive
168
+ }
169
+ }
170
+ /**
171
+ * Conservative cross-family compatibility. `other` (NULL/OBJECT) is compatible
172
+ * with anything; numeric and boolean are mutually compatible (SQLite stores
173
+ * booleans as integers). Only a clear cross-family mismatch (numeric↔text,
174
+ * text↔blob, …) is reported, so a faithfully-aligned basis never false-errors.
175
+ */
176
+ function familiesCompatible(a, b) {
177
+ if (a === b)
178
+ return true;
179
+ if (a === 'other' || b === 'other')
180
+ return true;
181
+ const numericish = (f) => f === 'numeric' || f === 'boolean';
182
+ return numericish(a) && numericish(b);
183
+ }
184
+ /**
185
+ * Each mapped column's basis-derived type & nullability satisfy the logical
186
+ * declaration. A nullable basis expression under a `not null` logical column
187
+ * errors unless the logical column supplies a total default. Read off the
188
+ * optimized body's output relation; skipped when the body did not plan.
189
+ */
190
+ function checkTypeAndNullability(ctx, errors) {
191
+ if (!ctx.root)
192
+ return;
193
+ const outCols = ctx.root.getType().columns;
194
+ for (const col of ctx.table.columns) {
195
+ const oi = ctx.outputIndex.get(col.name.toLowerCase());
196
+ if (oi === undefined)
197
+ continue; // defensive: column absent from the body output
198
+ const outCol = outCols[oi];
199
+ if (!outCol)
200
+ continue;
201
+ const outType = outCol.type;
202
+ if (outType.typeClass !== 'scalar')
203
+ continue;
204
+ const declared = physicalFamily(col.logicalType.physicalType);
205
+ const basis = physicalFamily(outType.logicalType.physicalType);
206
+ if (!familiesCompatible(declared, basis)) {
207
+ errors.push({
208
+ code: 'lens.type-mismatch',
209
+ severity: 'error',
210
+ site: { table: ctx.table.name, column: col.name },
211
+ message: `lens: logical column '${ctx.table.name}.${col.name}' declares type '${col.logicalType.name}' but its basis-derived expression has type '${outType.logicalType.name}' (incompatible storage affinities)`,
212
+ });
213
+ continue;
214
+ }
215
+ // Nullability: not-null logical column over a nullable basis expression with
216
+ // no total default is unsound (a NULL could be read into a not-null column).
217
+ if (col.notNull && outType.nullable === true && col.defaultValue === null) {
218
+ errors.push({
219
+ code: 'lens.nullability-mismatch',
220
+ severity: 'error',
221
+ site: { table: ctx.table.name, column: col.name },
222
+ message: `lens: logical column '${ctx.table.name}.${col.name}' is declared NOT NULL but its basis-derived expression is nullable and no default supplies a value`,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ // ---------------------------------------------------------------------------
228
+ // Error→read-only: key reconstructibility (lens.pk-not-reconstructible)
229
+ // ---------------------------------------------------------------------------
230
+ /**
231
+ * For a writable logical table the logical PK must be reconstructible at the lens
232
+ * boundary — each PK column either maps to a plain (invertible) basis column
233
+ * projection ({@link isReconstructibleColumn}) **or** is an authored
234
+ * (`with inverse`) column whose forward/inverse pair the round-trip enumeration
235
+ * proved a **bijection** (`bijectiveAuthored`). A bijective key is fully
236
+ * reconstructible: a written logical key maps to exactly one basis key and back.
237
+ * When a PK column is neither (a computed / aggregated / non-injective authored
238
+ * column), the table is **read-only**: reads still work, but any mutation errors
239
+ * at the lens (`planner/mutation/single-source.ts` `analyzeView` raises). This is
240
+ * not a deploy-blocking error — the table deploys read-only — so it surfaces as a
241
+ * warning, and `readOnly` is set on the slot. The empty (singleton) PK is
242
+ * vacuously reconstructible.
243
+ *
244
+ * @returns whether the table is read-only.
245
+ */
246
+ function checkKeyReconstructibility(ctx, bijectiveAuthored, warnings) {
247
+ const pk = ctx.table.primaryKeyDefinition;
248
+ if (pk.length === 0)
249
+ return false; // singleton — 0-or-1 row, vacuously reconstructible
250
+ const unreconstructible = [];
251
+ for (const pkCol of pk) {
252
+ const name = ctx.table.columns[pkCol.index]?.name;
253
+ if (name === undefined || !(isReconstructibleColumn(ctx, name) || bijectiveAuthored.has(name.toLowerCase()))) {
254
+ unreconstructible.push(name ?? `#${pkCol.index}`);
255
+ }
256
+ }
257
+ if (unreconstructible.length === 0)
258
+ return false;
259
+ warnings.push({
260
+ code: 'lens.pk-not-reconstructible',
261
+ severity: 'warning',
262
+ site: { table: ctx.table.name },
263
+ message: `lens: logical table '${ctx.table.name}' is read-only — its primary key is not reconstructible at the lens boundary (column(s) ${unreconstructible.map(c => `'${c}'`).join(', ')} have no invertible basis write path); mutations against it will error`,
264
+ });
265
+ return true;
266
+ }
267
+ /**
268
+ * A logical column is **bare-reconstructible** iff its body-output projection term
269
+ * is a plain column reference (so a written value maps straight back to a basis
270
+ * column). A computed (non-column) projection has no bare write path — but an
271
+ * authored (`with inverse`) computed column may still be key-reconstructible by a
272
+ * proven bijection; that case is handled by the `bijectiveAuthored` set, not here.
273
+ */
274
+ function isReconstructibleColumn(ctx, columnName) {
275
+ const oi = ctx.outputIndex.get(columnName.toLowerCase());
276
+ if (oi === undefined)
277
+ return false; // not in the body output — not writable through the lens
278
+ const rc = ctx.slot.compiledBody.columns[oi];
279
+ return rc?.type === 'column' && rc.expr.type === 'column';
280
+ }
281
+ /**
282
+ * Round-trip (GetPut / PutGet) over the writable fragment, **computed at deploy**
283
+ * from the predicate-honest complement ({@link viewComplement}). Because Quereus
284
+ * resolves the Bancilhon–Spyratos ambiguity by predicate-honest fan-out, the
285
+ * complement is *determined, not chosen*, which makes the two laws decidable over
286
+ * the single-source projection-and-filter fragment with no theorem prover:
287
+ *
288
+ * - **GetPut** ("read a row, write the same values back ⇒ base unchanged") holds
289
+ * iff `put` leaves the complement **fixed**: no writable column's backward
290
+ * write path targets a base column the complement lists as *hidden*.
291
+ * - **PutGet** ("write a value through the view, read it back ⇒ get the written
292
+ * value") holds iff, for every column the lens presents as **writable**, the
293
+ * composed `get ∘ put` is the identity on the writable value, and any `domain`
294
+ * restriction the column's inverse carries is **entailed** by the residual
295
+ * predicate.
296
+ *
297
+ * This is the *analysis* half, split from the diagnostic emission
298
+ * ({@link emitRoundTrip}) so the per-authored-column enumeration runs **exactly
299
+ * once**: each authored verdict's {@link provePutGetByEnumeration} result is cached
300
+ * keyed by the logical column (lowercased), and both the bijection-set consumer and
301
+ * the emitter read the cache. The split is a pure refactor — the cached enumeration
302
+ * is byte-identical to what the formerly-inline authored-inverse check computed.
303
+ *
304
+ * Degrade-to-safe: yields no verdicts (and an empty enum map) whenever the
305
+ * complement cannot characterize the body (out of the single-source projection-and-
306
+ * filter fragment, lineage not threaded, or a non-negation-free residual) — today's
307
+ * behaviour, the mutation-time and key-reconstructibility nets still govern. The
308
+ * body is planned **logically** ({@link planLogicalBody}, not `ctx.root`) so the
309
+ * Project/Filter/TableReference operator tree threading `updateLineage` survives.
310
+ * See `docs/lens.md` § Round-trip and `docs/view-updateability.md`
311
+ * § The predicate-honest complement.
312
+ */
313
+ function analyzeRoundTrip(ctx) {
314
+ const authoredEnum = new Map();
315
+ const root = planLogicalBody(ctx);
316
+ if (!root)
317
+ return { authoredEnum }; // body failed to plan logically → safe verdict
318
+ const verdicts = computeRoundTrip(root);
319
+ if (!verdicts)
320
+ return { authoredEnum }; // out of fragment / indeterminate complement → safe verdict
321
+ verdicts.forEach((v, i) => {
322
+ if (!v.authored)
323
+ return;
324
+ // Site at the *logical* column (the contract spelling), positionally aligned
325
+ // with the body output — the same space {@link emitRoundTrip} re-derives it in.
326
+ const column = ctx.outputColumns[i] ?? v.name;
327
+ authoredEnum.set(column.toLowerCase(), provePutGetByEnumeration(ctx, column, v.authored, v.forward));
328
+ });
329
+ return { verdicts, authoredEnum };
330
+ }
331
+ /**
332
+ * The authored logical columns (lowercased) the round-trip enumeration proved a
333
+ * **bijection** (`{kind:'proved', injective:true}`) — the same verdict that
334
+ * suppresses `lens.getput-lossy`. A bijective authored column is key-reconstructible
335
+ * (a written logical key maps to exactly one basis key and back) and its logical key
336
+ * is intrinsically unique, so this set gates both {@link checkKeyReconstructibility}
337
+ * and the bijection-transport `proved` classification in {@link classifyKeyConstraint}.
338
+ * A column outside the set (non-injective, indeterminate, or a violation) confers no
339
+ * reconstructibility.
340
+ */
341
+ function bijectiveAuthoredColumns(rt) {
342
+ const set = new Set();
343
+ for (const [column, enumResult] of rt.authoredEnum) {
344
+ if (enumResult.kind === 'proved' && enumResult.injective)
345
+ set.add(column);
346
+ }
347
+ return set;
348
+ }
349
+ /**
350
+ * Emits the round-trip diagnostics from the cached {@link RoundTripAnalysis} — the
351
+ * *diagnostic* half, split from {@link analyzeRoundTrip} (which already ran the
352
+ * enumeration). The firing rule has three branches:
353
+ * 1. a column the lens presents as writable (a `base` {@link ResolvedBaseSite})
354
+ * whose round-trip the analysis cannot prove faithful (`v.writable &&
355
+ * !v.faithful`) — the original rule, unchanged.
356
+ * 2. a `computed`/opaque output column (`!v.writable`) the author *declared*
357
+ * writable via the `quereus.lens.writable = true` intent tag
358
+ * ({@link intentWritable}): the round-trip law's stronger reading makes this
359
+ * an authoring error, not a derived column.
360
+ * 3. an **authored** (`with inverse`) column ({@link emitAuthoredInverseDiagnostics}):
361
+ * writable by construction (it satisfies the writable intent exactly as an
362
+ * inferred inverse does — branch 2 never fires for it). PutGet is checked by
363
+ * *composition* (the cached enumeration): a value that fails to reproduce is the
364
+ * hard `lens.putget-violation` error; GetPut is surrendered by design for a
365
+ * non-injective forward and surfaces as the acknowledgeable `lens.getput-lossy`
366
+ * advisory — suppressed only when the enumeration also proves the forward
367
+ * bijective over the basis domain.
368
+ *
369
+ * An opaque column carrying no intent tag (or `= false`) is *not* a deploy error
370
+ * — it is an intentional read-only/derived column (its write reds `no-inverse` at
371
+ * mutation time, as today), per the prover's soundness-over-completeness principle
372
+ * and the no-over-block requirement (`docs/lens.md` § Computed and Generated
373
+ * Columns). The intent is a deploy-policy input, not a property of the body's
374
+ * complement, so it lives here in the diagnostic wrapper — `computeRoundTrip` and
375
+ * `roundTripObstruction` are untouched. The branch keys off the round-trip
376
+ * verdict's `v.writable` (which admits an invertible *composed* expression like
377
+ * `(speed + 1) - 2`), NOT `isReconstructibleColumn` (the bare-column test, which
378
+ * would false-fire on such a chain).
379
+ *
380
+ * Degrade-to-safe: emits nothing when {@link analyzeRoundTrip} produced no verdicts —
381
+ * so an out-of-fragment opaque column tagged writable does not deploy-block; it still
382
+ * reds `no-inverse` at mutation time. This completeness gap is intentional.
383
+ */
384
+ function emitRoundTrip(ctx, rt, readOnly, errors, warnings) {
385
+ if (!rt.verdicts)
386
+ return; // degrade-to-safe: no per-column verdicts to emit
387
+ rt.verdicts.forEach((v, i) => {
388
+ // Site at the *logical* column (the contract spelling), positionally aligned
389
+ // with the body output — the same space {@link analyzeRoundTrip} cached it in.
390
+ const column = ctx.outputColumns[i] ?? v.name;
391
+ // (3) An authored (`with inverse`) column: writable by construction (the
392
+ // intent branch below never fires), PutGet checked by composition, GetPut
393
+ // surrendered into the `lens.getput-lossy` advisory. Consumes the cached
394
+ // enumeration (always present for an authored verdict — same iteration).
395
+ if (v.authored) {
396
+ const enumResult = rt.authoredEnum.get(column.toLowerCase());
397
+ if (enumResult)
398
+ emitAuthoredInverseDiagnostics(ctx, column, enumResult, readOnly, errors, warnings);
399
+ return;
400
+ }
401
+ // (1) A column the lens presents as writable whose round-trip cannot be
402
+ // proved faithful — the original firing rule, unchanged.
403
+ if (v.writable && !v.faithful) {
404
+ errors.push({
405
+ code: 'lens.non-invertible',
406
+ severity: 'error',
407
+ site: { table: ctx.table.name, column },
408
+ message: `lens: writable column '${ctx.table.name}.${column}' is not faithfully invertible at the lens boundary (${v.obstruction}); its GetPut/PutGet round-trip cannot be proved, so the declared write path is unsound`,
409
+ });
410
+ return;
411
+ }
412
+ // (2) An opaque / read-only column the author declared writable via the
413
+ // `quereus.lens.writable = true` intent tag. Today it would be silently
414
+ // admitted read-only; the asserted intent turns that into an authoring error.
415
+ if (!v.writable && intentWritable(ctx, column)) {
416
+ errors.push({
417
+ code: 'lens.non-invertible',
418
+ severity: 'error',
419
+ site: { table: ctx.table.name, column },
420
+ message: `lens: column '${ctx.table.name}.${column}' is declared writable ('${LENS_WRITABLE_INTENT_TAG}' = true) but its lens body is computed/opaque with no invertible write path; the round-trip law cannot be satisfied, so the declared writable intent is unsound (map it to an invertible basis expression, or drop the tag to deploy it read-only)`,
421
+ });
422
+ }
423
+ });
424
+ }
425
+ /**
426
+ * Whether the logical column named `column` carries the writable-intent signal
427
+ * (`quereus.lens.writable = true`). Resolves the logical column case-insensitively
428
+ * via the same `logicalColIndex` the rest of the prover uses, then reads the tag
429
+ * as a real boolean (`=== true`): `validateReservedTags` has already rejected a
430
+ * non-boolean value at deploy, so a surviving non-`true` value is `false`/absent.
431
+ */
432
+ function intentWritable(ctx, column) {
433
+ const li = ctx.logicalColIndex.get(column.toLowerCase());
434
+ if (li === undefined)
435
+ return false;
436
+ return ctx.table.columns[li]?.tags?.[LENS_WRITABLE_INTENT_TAG] === true;
437
+ }
438
+ /**
439
+ * Plan the lens body **logically** (the `view_info`/`column_info` and mutation
440
+ * substrate path via `_buildPlan`, *not* the optimized `ctx.root`), so the clean
441
+ * Project/Filter/TableReference operator tree — and the `updateLineage` it threads
442
+ * — survives (the optimizer degrades a structure-rewriting node's lineage to
443
+ * `computed`; `docs/view-updateability.md` § surface authority). Guarded with the
444
+ * same graceful-degradation `try/catch` as {@link planBody}.
445
+ */
446
+ function planLogicalBody(ctx) {
447
+ try {
448
+ const { plan } = ctx.db._buildPlan([ctx.slot.compiledBody]);
449
+ return plan.getRelations()[0];
450
+ }
451
+ catch (e) {
452
+ log('lens-prover: round-trip body failed to plan logically, degrading to safe: %O', e);
453
+ return undefined;
454
+ }
455
+ }
456
+ /**
457
+ * The computed per-column GetPut/PutGet verdict over a planned **logical** body —
458
+ * the deploy-time predicate {@link analyzeRoundTrip} / {@link emitRoundTrip} consume,
459
+ * exported so the operational round-trip harness (`test/property.spec.ts` § View
460
+ * Round-Trip Laws) can assert it agrees with the operational law per column.
461
+ *
462
+ * Returns `undefined` (the degrade-to-safe signal) for any body the complement
463
+ * does not characterize: not single-source projection-and-filter (multi-source /
464
+ * join / aggregate / set-op / VALUES / recursive-CTE / LIMIT / OFFSET / DISTINCT),
465
+ * `updateLineage` not threaded, or a residual predicate that is not negation-free.
466
+ * Otherwise one verdict per output column, in attribute order.
467
+ *
468
+ * Each writable site is read through {@link resolveBaseSite} — the same n-way
469
+ * reader the single-source, join, and decomposition put paths share — so the
470
+ * GetPut hidden-column and PutGet inverse-domain checks already generalize past
471
+ * single-source when the complement is later defined on the join/decomposition
472
+ * fragment (`view-write-through-shape-gaps`); only the fragment gate here is
473
+ * single-source-only.
474
+ */
475
+ export function computeRoundTrip(root) {
476
+ if (!isSingleSourceProjectionFilter(root))
477
+ return undefined;
478
+ const lineage = root.physical.updateLineage;
479
+ if (!lineage)
480
+ return undefined; // lineage not threaded ⇒ complement cannot be characterized
481
+ const complement = viewComplement(root);
482
+ if (complement.residualPredicate && !isNegationFree(complement.residualPredicate)) {
483
+ return undefined; // a non-negation-free residual signals the complement is not honestly determined
484
+ }
485
+ const hidden = new Set(complement.hiddenColumns.map(h => h.column.toLowerCase()));
486
+ const forwardByAttr = collectForwardExprs(root);
487
+ return root.getAttributes().map((attr) => {
488
+ const site = resolveBaseSite(lineage.get(attr.id));
489
+ if (!site.writable) {
490
+ return { attrId: attr.id, name: attr.name, writable: false, faithful: false };
491
+ }
492
+ // An authored (`with inverse`) put: the structural obstructions below are
493
+ // inapplicable (no single verbatim base column, no registry inverse) — the
494
+ // verdict is writable+faithful with the authored payload attached for the
495
+ // prover's dedicated law treatment (enumeration + lossy advisory).
496
+ if (site.authored) {
497
+ return { attrId: attr.id, name: attr.name, writable: true, faithful: true, authored: site.authored, forward: forwardByAttr.get(attr.id) };
498
+ }
499
+ const obstruction = roundTripObstruction(site, hidden, forwardByAttr.get(attr.id), complement.residualPredicate);
500
+ return { attrId: attr.id, name: attr.name, writable: true, faithful: obstruction === undefined, obstruction };
501
+ });
502
+ }
503
+ /**
504
+ * The GetPut / PutGet obstruction for a writable column, or `undefined` when its
505
+ * round-trip is proved faithful:
506
+ * - **GetPut** — `put` leaves the complement fixed: the writable base column is
507
+ * not one the complement lists as hidden (holds structurally over the single-
508
+ * source fragment — a guard that reds the day a shape violates it).
509
+ * - **PutGet** — `get ∘ put` reproduces the written value ({@link getPutComposesToIdentity},
510
+ * over the closed registry vocabulary), and any inverse `domain` is entailed by
511
+ * the residual. The shipped registry is faithful with unrestricted domains, so
512
+ * this returns `undefined` for it — the seam stays correct as the registry
513
+ * grows a domain-restricted or composed profile.
514
+ */
515
+ function roundTripObstruction(site, hidden, forward, residual) {
516
+ if (site.baseColumn !== undefined && hidden.has(site.baseColumn.toLowerCase())) {
517
+ return `GetPut: the write-back targets base column '${site.baseColumn}', which the view-complement holds fixed`;
518
+ }
519
+ // `get ∘ put = id` is verifiable only with the forward `get` expression; if it is
520
+ // unavailable (no Project node found) degrade to safe — the shipped registry is
521
+ // faithful by construction, so a missing forward never masks a real violation.
522
+ if (site.inverse !== undefined && forward !== undefined && !getPutComposesToIdentity(forward, site.inverse)) {
523
+ return `PutGet: the 'put' inverse does not reproduce the written value back through 'get'`;
524
+ }
525
+ if (site.domain !== undefined && !domainEntailedBy(site.domain, residual)) {
526
+ return `PutGet: the inverse's domain restriction is not entailed by the view predicate`;
527
+ }
528
+ return undefined;
529
+ }
530
+ /**
531
+ * The single-source projection-and-filter fragment gate. Reuses {@link classifyViewBody}
532
+ * (the substrate's shape classifier) to reject multi-source / join / aggregate /
533
+ * set-op / VALUES / recursive-CTE bodies, then additionally rejects LIMIT / OFFSET /
534
+ * DISTINCT — which that classifier tolerates as pass-through (so its walk can reach
535
+ * the base table) but the complement does not characterize.
536
+ */
537
+ function isSingleSourceProjectionFilter(root) {
538
+ if (classifyViewBody(root).kind !== 'single-source')
539
+ return false;
540
+ let windowed = false;
541
+ const visit = (n) => {
542
+ if (n.nodeType === PlanNodeType.LimitOffset || n.nodeType === PlanNodeType.Distinct)
543
+ windowed = true;
544
+ for (const child of n.getRelations())
545
+ visit(child);
546
+ };
547
+ visit(root);
548
+ return !windowed;
549
+ }
550
+ /**
551
+ * The forward `get` expression per output attribute, read off the topmost
552
+ * {@link ProjectNode}'s projection list (which the planner aligns 1:1 with output
553
+ * attributes, expanding `select *`). The `get` half of the round-trip; the `put`
554
+ * half is the site's `inverse`.
555
+ */
556
+ function collectForwardExprs(root) {
557
+ const map = new Map();
558
+ const project = findProjectNode(root);
559
+ if (project) {
560
+ for (const p of project.getProjections())
561
+ map.set(p.attributeId, p.node.expression);
562
+ }
563
+ return map;
564
+ }
565
+ /** The topmost {@link ProjectNode} in a planned body's relational spine, or undefined. */
566
+ function findProjectNode(node) {
567
+ if (node instanceof ProjectNode)
568
+ return node;
569
+ for (const child of node.getRelations()) {
570
+ const found = findProjectNode(child);
571
+ if (found)
572
+ return found;
573
+ }
574
+ return undefined;
575
+ }
576
+ /** Numeric probes for the `get ∘ put = id` check — distinct points pin any affine map. */
577
+ const ROUND_TRIP_PROBES = [7, 13, -5];
578
+ /**
579
+ * PutGet identity probe: is the composed `get ∘ put` the identity on the writable
580
+ * value? For each probe `w`, lowers it through the `put` inverse to a base value
581
+ * and re-applies the forward `get`, requiring `get(put(w)) === w`. Sound because a
582
+ * writable column's `get` and the inverse's `put` are built **only** from the
583
+ * law-gated invertibility registry's closed vocabulary (column ref, `± k`, no-op
584
+ * cast, collate), which {@link evalClosed} evaluates exactly; an expression outside
585
+ * it yields `undefined` and reds (the analysis cannot prove faithfulness).
586
+ *
587
+ * Exported as the pure core the operational harness's injected-violation self-test
588
+ * drives (an unfaithful forward/inverse pair must red), mirroring the harness's
589
+ * `injected-widening` / `injected-getput` cores.
590
+ */
591
+ export function getPutComposesToIdentity(forward, inverse) {
592
+ for (const w of ROUND_TRIP_PROBES) {
593
+ const baseVal = evalClosed(inverse({ type: 'literal', value: w }), undefined); // put: written → base
594
+ if (baseVal === undefined)
595
+ return false;
596
+ const got = evalClosed(forward, baseVal); // get: base → written
597
+ if (got === undefined || got !== w)
598
+ return false;
599
+ }
600
+ return true;
601
+ }
602
+ /**
603
+ * Synchronous evaluator over the **closed** invertibility-registry vocabulary —
604
+ * literal (int/real), the bound column (`columnValue`), `+`/`-`/`*` binary, unary
605
+ * `±`, and the value-preserving `cast`/`collate` wrappers. Returns `undefined` for
606
+ * anything outside that set (the signal that the expression is not a registry
607
+ * round-trip term). Total and side-effect-free — NOT a general expression
608
+ * interpreter; the writable fragment never contains anything else.
609
+ */
610
+ function evalClosed(expr, columnValue) {
611
+ switch (expr.type) {
612
+ case 'literal': {
613
+ const v = expr.value;
614
+ if (typeof v === 'number')
615
+ return v;
616
+ if (typeof v === 'bigint')
617
+ return Number(v);
618
+ return undefined;
619
+ }
620
+ case 'column':
621
+ return columnValue;
622
+ case 'cast':
623
+ return evalClosed(expr.expr, columnValue);
624
+ case 'collate':
625
+ return evalClosed(expr.expr, columnValue);
626
+ case 'unary': {
627
+ const u = expr;
628
+ const o = evalClosed(u.expr, columnValue);
629
+ if (o === undefined)
630
+ return undefined;
631
+ if (u.operator === '-')
632
+ return -o;
633
+ if (u.operator === '+')
634
+ return o;
635
+ return undefined;
636
+ }
637
+ case 'binary': {
638
+ const b = expr;
639
+ const l = evalClosed(b.left, columnValue);
640
+ const r = evalClosed(b.right, columnValue);
641
+ if (l === undefined || r === undefined)
642
+ return undefined;
643
+ switch (b.operator) {
644
+ case '+': return l + r;
645
+ case '-': return l - r;
646
+ case '*': return l * r;
647
+ default: return undefined;
648
+ }
649
+ }
650
+ default:
651
+ return undefined;
652
+ }
653
+ }
654
+ /**
655
+ * Whether a residual predicate is negation-free — `viewComplement` carries the σ
656
+ * conjuncts verbatim, so the presence of `not` / `is not null` / `!=` (`<>`) /
657
+ * `not between` means the complement is **not** honestly determined and the
658
+ * round-trip check must degrade to the safe verdict. A reflective walk mirroring
659
+ * {@link collectColumnRefNames}.
660
+ */
661
+ function isNegationFree(expr) {
662
+ const stack = [expr];
663
+ while (stack.length > 0) {
664
+ const node = stack.pop();
665
+ if (node.type === 'unary') {
666
+ const op = node.operator;
667
+ if (op === 'NOT' || op === 'IS NOT NULL')
668
+ return false;
669
+ }
670
+ if (node.type === 'binary' && (node.operator === '!=' || node.operator === '<>')) {
671
+ return false;
672
+ }
673
+ if (node.type === 'between' && node.not === true)
674
+ return false;
675
+ for (const key of Object.keys(node)) {
676
+ const value = node[key];
677
+ if (!value)
678
+ continue;
679
+ if (Array.isArray(value)) {
680
+ for (const item of value) {
681
+ if (item && typeof item === 'object' && 'type' in item)
682
+ stack.push(item);
683
+ }
684
+ }
685
+ else if (typeof value === 'object' && 'type' in value) {
686
+ stack.push(value);
687
+ }
688
+ }
689
+ }
690
+ return true;
691
+ }
692
+ /**
693
+ * Best-effort structural entailment of an inverse's `domain` by the residual
694
+ * predicate — the residual's AND-conjuncts include the domain verbatim. Unreachable
695
+ * today (the shipped registry's profiles carry no `domain`); the conservative seam
696
+ * for a future domain-restricted profile (an un-entailed domain ⇒ a value the view
697
+ * admits could be stored that `get` cannot reproduce ⇒ red).
698
+ */
699
+ function domainEntailedBy(domain, residual) {
700
+ if (residual === undefined)
701
+ return false;
702
+ const conjuncts = [];
703
+ const split = (e) => {
704
+ if (e.type === 'binary' && e.operator === 'AND') {
705
+ split(e.left);
706
+ split(e.right);
707
+ }
708
+ else {
709
+ conjuncts.push(e);
710
+ }
711
+ };
712
+ split(residual);
713
+ const target = expressionToString(domain);
714
+ return conjuncts.some(c => expressionToString(c) === target);
715
+ }
716
+ /** Enumeration cap — a CHECK `in (...)` domain larger than this degrades to safe. */
717
+ const ENUM_DOMAIN_CAP = 64;
718
+ /**
719
+ * Emits the law-treatment diagnostics for one authored-inverse column (firing-rule
720
+ * branch 3) from its **cached** {@link PutGetEnumeration} (computed once in
721
+ * {@link analyzeRoundTrip}, never re-run here):
722
+ *
723
+ * - **PutGet** (`forward(inverse(v)) ≡ v`) — proved by composition over the
724
+ * logical column's enumerable CHECK `in (...)` domain
725
+ * ({@link provePutGetByEnumeration}). A value that fails to reproduce is the
726
+ * hard `lens.putget-violation` error (a put that loses the written value is
727
+ * never acceptable), sited at the column and naming the offending value. No
728
+ * enumerable domain / non-const-foldable composition → degrade to safe
729
+ * (admit; mutation-time behavior governs — the prover's usual posture, no
730
+ * advisory for the unverified case).
731
+ * - **GetPut** — surrendered by design for a non-injective forward (a
732
+ * write-through normalizes the base value to the inverse's representative):
733
+ * the acknowledgeable `lens.getput-lossy` advisory, suppressed only when the
734
+ * enumeration also proves the forward bijective
735
+ * ({@link proveForwardInjective}). Suppressed wholesale on a read-only table
736
+ * (mutations never run the put — same gate as `lens.no-backing-index`); the
737
+ * PutGet error is NOT read-only-gated, mirroring branch (1)'s posture that a
738
+ * provably unsound declared write path is an authoring error regardless.
739
+ *
740
+ * The advisory's fingerprint carries the rendered domain values, so a CHECK
741
+ * list change (the domain gains a value) re-surfaces an acknowledgment.
742
+ */
743
+ function emitAuthoredInverseDiagnostics(ctx, column, result, readOnly, errors, warnings) {
744
+ if (result.kind === 'violation') {
745
+ errors.push({
746
+ code: 'lens.putget-violation',
747
+ severity: 'error',
748
+ site: { table: ctx.table.name, column },
749
+ message: `lens: authored inverse on '${ctx.table.name}.${column}' violates PutGet — writing ${renderSqlValue(result.value)} stores a basis image that reads back as ${renderSqlValue(result.got)}; forward(inverse(v)) must reproduce every value of the column's CHECK domain (fix the 'with inverse' expression or the forward mapping)`,
750
+ });
751
+ return;
752
+ }
753
+ if (result.kind === 'proved' && result.injective)
754
+ return; // bijective over the enumerated domains ⇒ GetPut holds too
755
+ if (readOnly)
756
+ return; // the put never runs on a read-only table — the lossy advisory is moot
757
+ warnings.push({
758
+ code: 'lens.getput-lossy',
759
+ severity: 'warning',
760
+ site: { table: ctx.table.name, column },
761
+ message: `lens: column '${ctx.table.name}.${column}' writes through an authored inverse whose forward mapping is not proven injective — GetPut is surrendered (a write-through normalizes the stored basis value to the inverse's representative). Acknowledge with 'quereus.lens.ack.getput-lossy:${column.toLowerCase()}' if intentional, or make the forward bijective over enumerable CHECK domains.`,
762
+ fingerprintInputs: {
763
+ ...buildFingerprint(ctx, [column], false),
764
+ ...(result.domain ? { domainValues: result.domain.map(renderSqlValue).sort() } : {}),
765
+ },
766
+ });
767
+ }
768
+ /**
769
+ * PutGet by composition: per logical-domain value `v`, lower `v` through every
770
+ * authored put (substituting the `new.<col>` refs with the literal), then re-read
771
+ * it through the forward `get` with each referenced base column bound to its put
772
+ * image — requiring `forward(inverse(v)) ≡ v` under SQL value equality.
773
+ *
774
+ * Preconditions (any miss ⇒ `indeterminate`, the degrade-to-safe signal):
775
+ * a forward expression resolved off the projection; a single basis source (the
776
+ * forward's column refs name-match against put targets — ambiguous past
777
+ * single-source); an inverse that is a function of the written column alone
778
+ * (every `new.*` ref resolves to this column); a deterministic, subquery-free
779
+ * forward + puts ({@link constEvaluable} — the composition is evaluated with the
780
+ * const evaluator only, never a vtab read); and every forward column ref covered
781
+ * by a put target. A definite per-value violation wins over another value's
782
+ * evaluation failure (it is a proven law break either way).
783
+ */
784
+ function provePutGetByEnumeration(ctx, column, authored, forward) {
785
+ const li = ctx.logicalColIndex.get(column.toLowerCase());
786
+ // Trusted accessor: a module declaring `permitsGrandfatheredCheckViolators`
787
+ // yields no enumerable domain (a logical table carries no module, so this is
788
+ // a no-op gate here today — but the accessor keeps every prover read of CHECK
789
+ // facts behind the capability gate).
790
+ const domain = li !== undefined ? enumerableDomain(getTrustedCheckExtraction(ctx.table), li) : undefined;
791
+ if (!domain)
792
+ return { kind: 'indeterminate' };
793
+ const oi = ctx.outputIndex.get(column.toLowerCase());
794
+ if (forward === undefined || ctx.basisSource === undefined || oi === undefined)
795
+ return { kind: 'indeterminate', domain };
796
+ for (const refIdx of authored.newRefIndex.values()) {
797
+ if (refIdx !== oi)
798
+ return { kind: 'indeterminate', domain };
799
+ }
800
+ if (!constEvaluable(ctx.db, forward) || authored.puts.some(p => !constEvaluable(ctx.db, p.expr))) {
801
+ return { kind: 'indeterminate', domain };
802
+ }
803
+ const putTargets = new Set(authored.puts.map(p => p.baseColumn.toLowerCase()));
804
+ if (collectColumnRefNames(forward).some(n => !putTargets.has(n.toLowerCase()))) {
805
+ return { kind: 'indeterminate', domain }; // the forward reads a base column the inverse does not determine
806
+ }
807
+ // `forward(inverse(v))` plus the inverse's basis image, or undefined when any
808
+ // step is not const-evaluable.
809
+ const composition = (v) => {
810
+ const base = new Map();
811
+ for (const p of authored.puts) {
812
+ const bv = evalDeployConstant(ctx.db, substituteNewRefs(p.expr, () => ({ type: 'literal', value: v })));
813
+ if (bv === undefined)
814
+ return undefined;
815
+ base.set(p.baseColumn.toLowerCase(), bv);
816
+ }
817
+ const got = evalDeployConstant(ctx.db, substituteBaseRefs(forward, base));
818
+ return got === undefined ? undefined : { got, base };
819
+ };
820
+ let indeterminate = false;
821
+ const putImages = [];
822
+ for (const v of domain) {
823
+ const r = composition(v);
824
+ if (r === undefined) {
825
+ indeterminate = true;
826
+ continue;
827
+ }
828
+ if (!sqlValueEquals(r.got, v))
829
+ return { kind: 'violation', value: v, got: r.got, domain };
830
+ putImages.push(r.base);
831
+ }
832
+ if (indeterminate)
833
+ return { kind: 'indeterminate', domain };
834
+ return { kind: 'proved', injective: proveForwardInjective(ctx, authored, forward, domain, putImages), domain };
835
+ }
836
+ /**
837
+ * Forward injectivity over the basis column's own enumerable CHECK domain. With
838
+ * PutGet proved over the logical domain, an injective forward whose image stays
839
+ * *inside* that logical domain — **and** an inverse whose images stay inside the
840
+ * basis domain (`putImages`, recorded by the PutGet enumeration) — makes the
841
+ * pair bijective between the two enumerated domains, so GetPut holds
842
+ * (`put(get(b)) = b` for every basis value) and the lossy advisory is
843
+ * suppressed. The put-image membership check is load-bearing: PutGet forces the
844
+ * inverse injective into the basis domain (|logical| ≤ |basis|), forward
845
+ * injectivity forces |basis| ≤ |logical|, so both are bijections and the
846
+ * inverse is exactly forward⁻¹ — without it, an inverse image *outside* the
847
+ * basis domain (which PutGet can still pass through the forward's catch-all
848
+ * arm) breaks the counting and GetPut fails for a real stored value.
849
+ * Conservative: requires a single put target backed by a NOT NULL basis column
850
+ * (a nullable basis admits a value outside the enumeration) and a
851
+ * const-evaluable, never-NULL forward image at every basis value. The forward's
852
+ * refs ⊆ put targets was already established by the caller, so the single
853
+ * binding covers every ref.
854
+ */
855
+ function proveForwardInjective(ctx, authored, forward, logicalDomain, putImages) {
856
+ const basis = ctx.basisSource;
857
+ if (!basis || authored.puts.length !== 1)
858
+ return false;
859
+ const put = authored.puts[0];
860
+ const bi = basis.columnIndexMap.get(put.baseColumn.toLowerCase());
861
+ if (bi === undefined || !basis.columns[bi]?.notNull)
862
+ return false;
863
+ // Trusted accessor: when the basis module declares
864
+ // `permitsGrandfatheredCheckViolators`, stored basis rows may violate the
865
+ // declared CHECK, so its enum domain cannot witness the bijection — the
866
+ // extraction is empty and the injectivity proof conservatively fails
867
+ // (matching the gate on `TableReferenceNode.computePhysical`).
868
+ const basisDomain = enumerableDomain(getTrustedCheckExtraction(basis), bi);
869
+ if (!basisDomain)
870
+ return false;
871
+ for (const image of putImages) {
872
+ const bv = image.get(put.baseColumn.toLowerCase());
873
+ if (bv === undefined || !basisDomain.some(b => sqlValueEquals(b, bv))) {
874
+ return false; // the inverse writes outside the basis domain — not a bijection witness
875
+ }
876
+ }
877
+ const seen = [];
878
+ for (const b of basisDomain) {
879
+ const got = evalDeployConstant(ctx.db, substituteBaseRefs(forward, new Map([[put.baseColumn.toLowerCase(), b]])));
880
+ if (got === undefined || got === null)
881
+ return false;
882
+ if (!logicalDomain.some(v => sqlValueEquals(v, got)))
883
+ return false; // image escapes the PutGet-proved domain
884
+ if (seen.some(s => sqlValueEquals(s, got)))
885
+ return false; // two basis values collapse — not injective
886
+ seen.push(got);
887
+ }
888
+ return true;
889
+ }
890
+ /**
891
+ * The enumerable CHECK domain of one column: the literal `in (...)` value list,
892
+ * intersected across multiple enum CHECKs and filtered through any recognized
893
+ * range CHECK on the same column — the enumeration must never include a value
894
+ * the declared CHECK surface already excludes, since a false
895
+ * `lens.putget-violation` would block a sound deploy. NULLs are dropped (an
896
+ * `in` list never admits one). Undefined when no enum constraint exists, the
897
+ * filtered domain is empty, or it exceeds {@link ENUM_DOMAIN_CAP}.
898
+ */
899
+ function enumerableDomain(extraction, columnIndex) {
900
+ let values;
901
+ for (const dc of extraction.domainConstraints) {
902
+ if (dc.column !== columnIndex || dc.kind !== 'enum')
903
+ continue;
904
+ const list = dc.values.filter(v => v !== null);
905
+ values = values === undefined ? list : values.filter(v => list.some(w => sqlValueEquals(v, w)));
906
+ }
907
+ if (values === undefined)
908
+ return undefined;
909
+ for (const dc of extraction.domainConstraints) {
910
+ if (dc.column !== columnIndex || dc.kind !== 'range')
911
+ continue;
912
+ values = values.filter(v => withinRange(v, dc));
913
+ }
914
+ if (values.length === 0 || values.length > ENUM_DOMAIN_CAP)
915
+ return undefined;
916
+ return values;
917
+ }
918
+ /** Whether `v` satisfies a recognized range domain constraint. */
919
+ function withinRange(v, r) {
920
+ if (r.min !== undefined) {
921
+ const c = compareSqlValues(v, r.min);
922
+ if (r.minInclusive ? c < 0 : c <= 0)
923
+ return false;
924
+ }
925
+ if (r.max !== undefined) {
926
+ const c = compareSqlValues(v, r.max);
927
+ if (r.maxInclusive ? c > 0 : c >= 0)
928
+ return false;
929
+ }
930
+ return true;
931
+ }
932
+ /**
933
+ * Whether an expression is sound to fold at deploy with the const evaluator:
934
+ * deterministic functions only and no subquery —
935
+ * {@link containsNonDeterministicCall} flags both (a vtab read can never be a
936
+ * deploy-time constant). An unregistered function is treated deterministic; its
937
+ * evaluation failing falls through {@link evalDeployConstant}'s degrade anyway.
938
+ */
939
+ function constEvaluable(db, expr) {
940
+ const isDeterministic = (name, argc) => {
941
+ const fn = db.schemaManager.findFunction(name, argc) ?? db.schemaManager.findFunction(name, -1);
942
+ return fn ? (fn.flags & FunctionFlags.DETERMINISTIC) !== 0 : true;
943
+ };
944
+ return !containsNonDeterministicCall(expr, isDeterministic);
945
+ }
946
+ /** Replace each (qualifier-agnostic) base-column reference with its literal image. */
947
+ function substituteBaseRefs(expr, baseImage) {
948
+ return transformExpr(expr, col => {
949
+ const v = baseImage.get(col.name.toLowerCase());
950
+ return v === undefined ? undefined : { type: 'literal', value: v };
951
+ });
952
+ }
953
+ /**
954
+ * Evaluate a column-free scalar expression at deploy via the engine's own const
955
+ * evaluator (`createRuntimeExpressionEvaluator`) — never a vtab read; the
956
+ * expression is planned as a bare one-column SELECT so it builds in an empty
957
+ * scope. Returns undefined (the degrade-to-safe signal) when the expression
958
+ * fails to build, evaluates asynchronously (not a deploy-time constant), or
959
+ * throws.
960
+ */
961
+ function evalDeployConstant(db, expr) {
962
+ try {
963
+ const stmt = { type: 'select', columns: [{ type: 'column', expr }] };
964
+ const { plan } = db._buildPlan([stmt]);
965
+ const root = plan.getRelations()[0];
966
+ const node = root === undefined ? undefined : findProjectNode(root)?.getProjections()[0]?.node;
967
+ if (!node)
968
+ return undefined;
969
+ const value = createRuntimeExpressionEvaluator(db)(node);
970
+ if (value instanceof Promise) {
971
+ void value.catch(() => undefined); // async ⇒ not a deploy-time constant; never crash the deploy on it
972
+ return undefined;
973
+ }
974
+ return value;
975
+ }
976
+ catch (e) {
977
+ log('lens-prover: authored-inverse composition failed to const-evaluate, degrading to safe: %O', e);
978
+ return undefined;
979
+ }
980
+ }
981
+ /**
982
+ * Maps each authored-inverse logical column (lowercased) to its **forward** `get`
983
+ * expression (basis terms), when that forward is row-local-enforceable — a
984
+ * subquery-free scalar over basis column refs of a **single-source** body. This
985
+ * is the agreement predicate between the prover's CHECK realizability classifier
986
+ * ({@link classifyCheckConstraint}: a CHECK referencing an authored column is
987
+ * row-local exactly when this map has the column) and the write-time
988
+ * logical→basis rewrite (`planner/mutation/lens-enforcement.ts`), which
989
+ * substitutes the forward — `NEW.`-qualified — for the column ref so the CHECK
990
+ * evaluates over the basis write row's logical image. The two must accept the
991
+ * same set, or a deploy-admitted CHECK would crash at write plan time.
992
+ *
993
+ * The single-source condition is load-bearing: on a multi-source body the
994
+ * forward may read a column of a *different member* than the one the put
995
+ * writes, and the substituted `NEW.<col>` on that member's write row does not
996
+ * carry the value — the CHECK would pass vacuously (3VL unknown) instead of
997
+ * enforcing. The clause's put targets are bare column names, so the slot AST
998
+ * alone cannot prove every forward ref lives on the put member; single-source
999
+ * is the decidable conservative gate (the same posture as the prover's PutGet
1000
+ * enumeration). A multi-source CHECK over an authored column therefore reds
1001
+ * `lens.unrealizable-constraint` at deploy rather than deploying un-enforced.
1002
+ */
1003
+ export function authoredForwardMap(slot) {
1004
+ const map = new Map();
1005
+ const from = slot.compiledBody.from;
1006
+ if (!from || from.length !== 1 || from[0].type !== 'table')
1007
+ return map;
1008
+ slot.columnProvenance.forEach((p, i) => {
1009
+ const rc = slot.compiledBody.columns[i];
1010
+ if (rc && rc.type === 'column' && rc.inverse && rc.inverse.length > 0 && !containsRelationalOperand(rc.expr)) {
1011
+ map.set(p.logicalColumn.toLowerCase(), rc.expr);
1012
+ }
1013
+ });
1014
+ return map;
1015
+ }
1016
+ /** Reflective walk: does the expression contain a relational operand (subquery / exists / in-subquery)? */
1017
+ function containsRelationalOperand(expr) {
1018
+ const stack = [expr];
1019
+ while (stack.length > 0) {
1020
+ const node = stack.pop();
1021
+ if (node.type === 'subquery' || node.type === 'exists')
1022
+ return true;
1023
+ if (node.type === 'in' && node.subquery)
1024
+ return true;
1025
+ for (const key of Object.keys(node)) {
1026
+ const value = node[key];
1027
+ if (!value)
1028
+ continue;
1029
+ if (Array.isArray(value)) {
1030
+ for (const item of value) {
1031
+ if (item && typeof item === 'object' && 'type' in item)
1032
+ stack.push(item);
1033
+ }
1034
+ }
1035
+ else if (typeof value === 'object' && 'type' in value) {
1036
+ stack.push(value);
1037
+ }
1038
+ }
1039
+ }
1040
+ return false;
1041
+ }
1042
+ /** SQL value equality for the enumeration (NULL equals only NULL — identity, not three-valued `=`). */
1043
+ function sqlValueEquals(a, b) {
1044
+ if (a === null || b === null)
1045
+ return a === null && b === null;
1046
+ return compareSqlValues(a, b) === 0;
1047
+ }
1048
+ /** Render a domain value for a sited message / fingerprint (text quoted, so '1' ≠ 1). */
1049
+ function renderSqlValue(v) {
1050
+ if (v === null)
1051
+ return 'NULL';
1052
+ return typeof v === 'string' ? `'${v}'` : String(v);
1053
+ }
1054
+ // ---------------------------------------------------------------------------
1055
+ // Constraint realizability + obligation classification
1056
+ // ---------------------------------------------------------------------------
1057
+ /**
1058
+ * Classifies every attached logical constraint into a {@link ConstraintObligation}
1059
+ * and, in the process, performs the *constraint realizability* error check
1060
+ * (`lens.unrealizable-constraint`): a constraint referencing a column with no
1061
+ * write path (computed lineage) is neither provable nor attachable.
1062
+ * Set-level constraints with no covering structure emit `lens.no-backing-index`.
1063
+ */
1064
+ function classifyObligations(ctx, readOnly, bijectiveAuthored, errors, warnings) {
1065
+ const obligations = [];
1066
+ for (const c of ctx.slot.attachedConstraints) {
1067
+ obligations.push(classifyConstraint(ctx, c, readOnly, bijectiveAuthored, errors, warnings));
1068
+ }
1069
+ return obligations;
1070
+ }
1071
+ function classifyConstraint(ctx, constraint, readOnly, bijectiveAuthored, errors, warnings) {
1072
+ switch (constraint.kind) {
1073
+ case 'primaryKey':
1074
+ return classifyKeyConstraint(ctx, constraint, constraint.columns.map(c => c.index), 'primary key', true, readOnly, bijectiveAuthored, errors, warnings);
1075
+ case 'unique':
1076
+ return classifyKeyConstraint(ctx, constraint, constraint.constraint.columns, constraintLabel(constraint), false, readOnly, bijectiveAuthored, errors, warnings);
1077
+ case 'check':
1078
+ return classifyCheckConstraint(ctx, constraint, errors);
1079
+ case 'foreignKey':
1080
+ return { constraint, kind: 'enforced-fk' };
1081
+ }
1082
+ }
1083
+ /** A short human label for a constraint, for sited messages. */
1084
+ function constraintLabel(constraint) {
1085
+ switch (constraint.kind) {
1086
+ case 'primaryKey': return 'primary key';
1087
+ case 'check': return constraint.constraint.name ? `check '${constraint.constraint.name}'` : 'check';
1088
+ case 'unique': return constraint.constraint.name ? `unique '${constraint.constraint.name}'` : 'unique';
1089
+ case 'foreignKey': return constraint.constraint.name ? `foreign key '${constraint.constraint.name}'` : 'foreign key';
1090
+ }
1091
+ }
1092
+ /**
1093
+ * The effective constraint-level default conflict action a duplicate key would
1094
+ * resolve to absent a statement-level OR clause. A key declaring REPLACE / IGNORE
1095
+ * here is rejected at deploy when the realizing path cannot honor it: the
1096
+ * commit-time set-level scan can only ABORT (see {@link classifyKeyConstraint}),
1097
+ * and the row-time path honors the *basis* UC's action, not the logical key's
1098
+ * (see {@link rejectRowTimeConflictAction}) — both raise `lens.unenforceable-conflict-action`.
1099
+ *
1100
+ * - `unique` → the constraint's own `defaultConflict`.
1101
+ * - `primaryKey` → {@link resolvePkDefaultConflict}: table-level
1102
+ * `PRIMARY KEY (...) ON CONFLICT <action>` (`TableSchema.primaryKeyDefaultConflict`),
1103
+ * else the column-level `ColumnSchema.defaultConflict` on **any** PK column —
1104
+ * the precedence the runtime resolvers actually use, so the deploy-time check
1105
+ * agrees with what a duplicate would resolve to. A non-first PK column's
1106
+ * `not null on conflict replace` counts (it sets `defaultConflict` too); the
1107
+ * PK's action is NOT on the `LogicalConstraint` node, so it must come from `ctx.table`.
1108
+ *
1109
+ * Returns undefined when no action is declared (⇒ ABORT, which the scan honors).
1110
+ */
1111
+ function effectiveKeyDefaultConflict(ctx, constraint) {
1112
+ switch (constraint.kind) {
1113
+ case 'unique':
1114
+ return constraint.constraint.defaultConflict;
1115
+ case 'primaryKey':
1116
+ return resolvePkDefaultConflict(ctx.table);
1117
+ default:
1118
+ return undefined;
1119
+ }
1120
+ }
1121
+ /** Render a conflict action for a sited message; an absent action resolves to ABORT. */
1122
+ function conflictActionName(action) {
1123
+ return ConflictResolution[action ?? ConflictResolution.ABORT].toLowerCase();
1124
+ }
1125
+ /**
1126
+ * Classifies a key constraint (primary key / unique). Empty key ⇒ vacuous
1127
+ * (singleton).
1128
+ *
1129
+ * A column with no write path (computed lineage) is handled by class:
1130
+ * - a **unique** over such a column is `lens.unrealizable-constraint` (you
1131
+ * declared uniqueness on a value with no write path — it can be neither proved
1132
+ * nor enforced), *unless* the column is authored-**bijective**
1133
+ * (`bijectiveAuthored`): the proven bijection transports uniqueness to/from its
1134
+ * put target, so it has a sound write path and is admitted to the key (proved
1135
+ * via transport when the put target is a basis key, else the commit-time scan
1136
+ * over the forward image enforces it) — the same realizability a bijective PK
1137
+ * column enjoys;
1138
+ * - a **primary key** over such a column makes the whole table *read-only*
1139
+ * (owned by {@link checkKeyReconstructibility}, surfaced as the
1140
+ * `lens.pk-not-reconstructible` warning) — NOT a blocking error, because the
1141
+ * table still deploys for reads.
1142
+ *
1143
+ * Otherwise: proved by the body's effective key (`proveEffectiveKeyUnique`) **or**
1144
+ * by **bijection transport** ({@link proveKeyByBijectionTransport} — every key column
1145
+ * bare-reconstructible or authored-bijective, mapping to a declared basis key over
1146
+ * the put targets) ⇒ `proved`; else `enforced-set-level`, row-time when a basis
1147
+ * covering structure answers it, commit-time (+ `lens.no-backing-index` warning) when
1148
+ * none does. The warning is suppressed for a read-only table — its set-level
1149
+ * enforcement is moot.
1150
+ *
1151
+ * Whenever the key classifies `proved`, a **governing** basis key (if any) resolves a
1152
+ * write-through duplicate, not the logical key — so a logical `on conflict
1153
+ * replace`/`ignore` the governing key does not itself carry would be silently dropped.
1154
+ * That check ({@link rejectBasisGovernedConflictActionForProvedKey}) is **decoupled**
1155
+ * from the transport proof's exact-match/single-source gate: it identifies the governing
1156
+ * basis keys by a SUBSET search over the mapped basis columns
1157
+ * ({@link findGoverningBasisKeys}) — so a logical key that is a strict superkey of a
1158
+ * smaller basis key (body-proved, transport-undefined) is covered, not just an exact
1159
+ * transport match — and rejects conservatively when governance cannot be pinned (a
1160
+ * multi-source body, where the 1:1 column mapping and the superkey soundness argument do
1161
+ * not transfer). A genuinely basis-keyless body proof (a GROUP BY aggregate over plain
1162
+ * columns with no basis UC over the key, an FD-closure key, etc.) subsumes no declared
1163
+ * basis key, so its `on conflict` is vacuous and deploys clean. Mirrors the row-time and
1164
+ * commit-time arms.
1165
+ *
1166
+ * A strict superkey of a NOT-NULL basis key is *over*-enforced — distinct from the
1167
+ * conflict-action mismatch above. The logical `unique(a, b)` permits two rows differing
1168
+ * only in `b`, but the basis NOT-NULL `unique(a)` that body-proved it rejects them: the
1169
+ * basis enforces uniqueness on a *subset* of the logical key's columns, so it is stricter
1170
+ * than the logical declaration advertises. That is sound (every logical invariant still
1171
+ * holds) but surprising, so it surfaces as the acknowledgeable warning
1172
+ * `lens.over-restrictive-basis-key` ({@link warnOverRestrictiveBasisKey}), emitted from this
1173
+ * `proved` branch beside the conflict-action check (the only branch a `proved` logical key
1174
+ * can sit over a strict-subset NOT-NULL basis key — a nullable basis sub-key yields only a
1175
+ * guarded FD and never classifies `proved`). It is a warning, not an error: the schema is
1176
+ * sound, just enforced more tightly than declared.
1177
+ */
1178
+ function classifyKeyConstraint(ctx, constraint, logicalColumns, label, isPrimaryKey, readOnly, bijectiveAuthored, errors, warnings) {
1179
+ if (logicalColumns.length === 0) {
1180
+ return { constraint, kind: 'vacuous' };
1181
+ }
1182
+ const columnNames = logicalColumns.map(i => ctx.table.columns[i]?.name ?? `#${i}`);
1183
+ const outCols = [];
1184
+ for (const li of logicalColumns) {
1185
+ const name = ctx.table.columns[li]?.name;
1186
+ const oi = name !== undefined ? ctx.outputIndex.get(name.toLowerCase()) : undefined;
1187
+ const reachable = oi !== undefined && isReconstructibleColumn(ctx, name);
1188
+ // An authored-bijective column is not bare-reconstructible, but the proven
1189
+ // bijection transports uniqueness to/from its put target — so it has a sound
1190
+ // write path and is admitted to the key just like a bijective PK column (the
1191
+ // bijection-transport proof below maps it to its basis column, and absent a
1192
+ // basis key the commit-time scan over the forward image enforces it). A
1193
+ // non-bijective authored column (or a computed/opaque column) still has no
1194
+ // proven write path: uniqueness over it is neither provable nor enforceable.
1195
+ const authoredBijective = name !== undefined && bijectiveAuthored.has(name.toLowerCase());
1196
+ if (!reachable && !isPrimaryKey && !authoredBijective) {
1197
+ errors.push({
1198
+ code: 'lens.unrealizable-constraint',
1199
+ severity: 'error',
1200
+ site: { table: ctx.table.name, constraint: label, column: name },
1201
+ message: `lens: ${label} on '${ctx.table.name}' references column '${name ?? `#${li}`}', which has no write path at the lens boundary (computed lineage); the constraint can be neither proved nor enforced`,
1202
+ });
1203
+ return { constraint, kind: 'enforced-set-level', mode: 'commit-time' };
1204
+ }
1205
+ // A PK over an unreachable column (read-only, warned elsewhere) or an
1206
+ // authored-bijective key column: fall through to classify the obligation
1207
+ // without a blocking error.
1208
+ if (oi !== undefined)
1209
+ outCols.push(oi);
1210
+ }
1211
+ // A bijection-transport proof classifies the key `proved` via an authored bijection
1212
+ // onto an *exact* declared basis key (a strict superset does not prove the smaller
1213
+ // key's uniqueness, so exact-match is correct for the proof). It is NOT the gate for
1214
+ // the conflict-action check below — governance is identified independently by subset
1215
+ // (see {@link rejectBasisGovernedConflictActionForProvedKey}).
1216
+ const transport = proveKeyByBijectionTransport(ctx, logicalColumns, bijectiveAuthored);
1217
+ // Body proves it? (e.g. unique(x,y) over `group by x,y`, or a faithful projection
1218
+ // of a basis key.) Only when every column resolved to the output.
1219
+ const bodyProvesKey = ctx.root != null
1220
+ && outCols.length === logicalColumns.length
1221
+ && proveEffectiveKeyUnique(ctx.root, outCols).proved;
1222
+ // Proved by BODY or by BIJECTION TRANSPORT (every key column bare-reconstructible
1223
+ // or authored-bijective, mapping to a declared basis key). Both are `proved` —
1224
+ // zero runtime enforcement, a governing basis key forbids the colliding tuple.
1225
+ // Transport subsumes both the basis-PK and basis-UNIQUE cases, so an authored key
1226
+ // never needs the row-time/covering path.
1227
+ if (bodyProvesKey || transport) {
1228
+ // A governing basis key (a declared basis key whose columns ⊆ the logical key's
1229
+ // mapped basis columns) resolves a write-through duplicate, not the logical key —
1230
+ // so a logical `on conflict replace`/`ignore` it does not itself carry is silently
1231
+ // dropped. Reject the mismatch regardless of which arm proved the key (a populated
1232
+ // `errors` blocks the deploy regardless of the obligation kind, exactly as the
1233
+ // row-time arm does). A genuinely basis-keyless proof governs no basis key, so its
1234
+ // `on conflict` is vacuous and left untouched.
1235
+ rejectBasisGovernedConflictActionForProvedKey(ctx, constraint, logicalColumns, bijectiveAuthored, label, columnNames, readOnly, errors);
1236
+ // Diagnostic-only: a logical key proved over a strict-subset NOT-NULL basis key is
1237
+ // sound but over-enforced (the basis rejects writes the logical schema permits).
1238
+ // Surface the surprise as a warning; this does not alter the proved classification.
1239
+ warnOverRestrictiveBasisKey(ctx, logicalColumns, bijectiveAuthored, label, columnNames, readOnly, warnings);
1240
+ return { constraint, kind: 'proved' };
1241
+ }
1242
+ // Not proved → enforced set-level. Row-time iff a basis row-time covering
1243
+ // structure answers it (a non-stale covering MV); commit-time otherwise.
1244
+ const covering = findBasisCovering(ctx, logicalColumns);
1245
+ if (covering) {
1246
+ rejectRowTimeConflictAction(ctx, constraint, covering, label, columnNames, readOnly, errors);
1247
+ return { constraint, kind: 'enforced-set-level', mode: 'row-time', structure: covering.ref };
1248
+ }
1249
+ if (!readOnly) {
1250
+ warnings.push({
1251
+ code: 'lens.no-backing-index',
1252
+ severity: 'warning',
1253
+ site: { table: ctx.table.name, constraint: label },
1254
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) has no basis covering structure — it enforces via an O(n) commit-time scan. Add an explicit basis covering materialized view (order by the constraint columns) to upgrade to row-time enforcement; row-time conflict resolution (insert or replace / or ignore) requires that structure and is otherwise rejected.`,
1255
+ fingerprintInputs: buildFingerprint(ctx, columnNames, false),
1256
+ });
1257
+ // A commit-time scan can only ABORT. A constraint-level `on conflict
1258
+ // replace`/`ignore` (the PK's via `ctx.table`, a UNIQUE's via its own
1259
+ // `defaultConflict`) is an action the scan can never honor — an unsound
1260
+ // schema. Block it at deploy here rather than silently over-ABORTing per
1261
+ // write: the statement-level gate (`rejectLensSetLevelConflictResolution`)
1262
+ // only inspects `req.stmt.onConflict`, so the constraint-level channel never
1263
+ // reaches it. ABORT / FAIL / ROLLBACK (and no declared action) are fine.
1264
+ const effectiveConflict = effectiveKeyDefaultConflict(ctx, constraint);
1265
+ if (effectiveConflict === ConflictResolution.REPLACE || effectiveConflict === ConflictResolution.IGNORE) {
1266
+ errors.push({
1267
+ code: 'lens.unenforceable-conflict-action',
1268
+ severity: 'error',
1269
+ site: { table: ctx.table.name, constraint: label },
1270
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) declares 'on conflict replace/ignore' but has no basis covering structure, so the action cannot be honored (a commit-time scan can only ABORT). Add a basis covering materialized view (order by the key columns) to upgrade to row-time enforcement, or drop the conflict action.`,
1271
+ });
1272
+ }
1273
+ // Defensive (close-before-reachable): the commit-time count synthesis
1274
+ // (`synthesizeUniqueCountExpr`) counts ALL logical rows matching the key — it
1275
+ // does not scope by a partial-UNIQUE predicate, so a partial logical UNIQUE
1276
+ // would over-count and falsely ABORT an out-of-scope duplicate. A logical
1277
+ // declaration never sets `predicate` today (only `CREATE UNIQUE INDEX … WHERE`
1278
+ // does, a path the declaration surface never takes), so this guards an
1279
+ // invariant rather than a reachable case — reject loudly if it ever opens.
1280
+ if (constraint.kind === 'unique' && constraint.constraint.predicate !== undefined) {
1281
+ errors.push({
1282
+ code: 'lens.unrealizable-constraint',
1283
+ severity: 'error',
1284
+ site: { table: ctx.table.name, constraint: label },
1285
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) is a partial UNIQUE (declares a predicate), which is unsupported for commit-time set-level enforcement (the O(n) count scan cannot scope by the partial predicate). Add a basis covering materialized view to upgrade to row-time enforcement, or remove the partial predicate.`,
1286
+ });
1287
+ }
1288
+ }
1289
+ return { constraint, kind: 'enforced-set-level', mode: 'commit-time' };
1290
+ }
1291
+ /**
1292
+ * Shared core for the two basis-governed set-level conflict-action rejecters
1293
+ * (row-time covering UC, proved-transport basis key). A basis-governed key is
1294
+ * enforced by the **basis** key, whose conflict action resolves a duplicate as
1295
+ * `statement-OR ?? basis-key.defaultConflict ?? ABORT` — the *logical* key's own
1296
+ * `defaultConflict` is never consulted. So a logical `on conflict replace`/`ignore`
1297
+ * the basis key does NOT itself carry is silently dropped to ABORT at write time
1298
+ * (with no statement-level OR), violating the declared action. Reject it at deploy.
1299
+ *
1300
+ * Fires only when the logical effective action is REPLACE / IGNORE *and* differs
1301
+ * from the basis key's own action: when they match, the basis key already resolves
1302
+ * the declared action for free (the documented remediation), so there is nothing to
1303
+ * reject. ABORT / FAIL / ROLLBACK (and no declared action) never reject. Gated on
1304
+ * `!readOnly`: a read-only table never writes, so the action is moot. The two
1305
+ * callers differ only in `basis` — where the governing action and its label come
1306
+ * from — so both funnel into this one diagnostic.
1307
+ */
1308
+ function rejectBasisGovernedConflictAction(ctx, constraint, label, columnNames, readOnly, errors, basis) {
1309
+ if (readOnly)
1310
+ return;
1311
+ const eff = effectiveKeyDefaultConflict(ctx, constraint);
1312
+ if (eff !== ConflictResolution.REPLACE && eff !== ConflictResolution.IGNORE)
1313
+ return;
1314
+ if (eff === basis.conflict)
1315
+ return; // basis key honors it for free — the documented remediation
1316
+ errors.push({
1317
+ code: 'lens.unenforceable-conflict-action',
1318
+ severity: 'error',
1319
+ site: { table: ctx.table.name, constraint: label },
1320
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) declares 'on conflict ${conflictActionName(eff)}', but its backing ${basis.label} resolves a duplicate to '${conflictActionName(basis.conflict)}' — the write path honors the basis key's action, not the logical key's, so the declared action would be silently dropped. Declare the matching 'on conflict ${conflictActionName(eff)}' on the basis key, or drop the logical conflict action.`,
1321
+ });
1322
+ }
1323
+ /**
1324
+ * Row-time sibling of the commit-time `lens.unenforceable-conflict-action` block.
1325
+ * A row-time key is enforced by re-planning the lens write against the basis UC,
1326
+ * whose conflict action resolves as `statement-OR ?? basis-uc.defaultConflict ??
1327
+ * ABORT` (the memory / isolation / store resolvers all agree) — the *logical*
1328
+ * key's own `defaultConflict` is never consulted in that re-plan. So a logical
1329
+ * `on conflict replace`/`ignore` the backing basis UC does NOT itself carry is
1330
+ * silently dropped to ABORT at write time (with no statement-level OR), violating
1331
+ * the declared action. Delegates to {@link rejectBasisGovernedConflictAction} with
1332
+ * the covering structure's basis UC as the governing key.
1333
+ */
1334
+ function rejectRowTimeConflictAction(ctx, constraint, covering, label, columnNames, readOnly, errors) {
1335
+ rejectBasisGovernedConflictAction(ctx, constraint, label, columnNames, readOnly, errors, {
1336
+ conflict: covering.uc.defaultConflict,
1337
+ label: `covering structure '${covering.ref.name}'`,
1338
+ });
1339
+ }
1340
+ /**
1341
+ * The `proved`-key sibling of the row-time/commit-time `lens.unenforceable-conflict-action`
1342
+ * blocks, **decoupled** from the bijection-transport exact-match/single-source gate. A
1343
+ * `proved` key (by the body or by transport) is enforced for free by whatever declared
1344
+ * basis key stands behind the proof — never the logical key — so a logical `on conflict
1345
+ * replace`/`ignore` that governing key does not itself carry is silently dropped.
1346
+ *
1347
+ * Single-source: the governing basis keys are every declared basis key whose column set
1348
+ * is a **subset** of the logical key's mapped basis columns ({@link findGoverningBasisKeys}).
1349
+ * Soundness: the logical key ⊇ any such basis key K (as column sets, after the 1:1 mapping),
1350
+ * so two rows equal on the full logical key are equal on K — K fires on *every* logical-key
1351
+ * write-through duplicate. So:
1352
+ * - **no governing key** ⇒ genuinely basis-keyless (a GROUP BY aggregate, an FD-closure
1353
+ * key, …) ⇒ vacuous `on conflict`, deploy clean;
1354
+ * - **any governing key carries a different action** ⇒ that key fires first and drops the
1355
+ * declared action ⇒ reject. When several subset keys disagree, the basis enforcement
1356
+ * order that decides which fires first is not soundly pinnable at deploy, so reject the
1357
+ * moment the *first* mismatch surfaces (the ticket blesses this conservatism);
1358
+ * - **all governing keys carry the matching action** ⇒ honored for free ⇒ deploy clean.
1359
+ * The `notNull` gate that {@link proveKeyByBijectionTransport} carries is deliberately NOT
1360
+ * applied to the mapping here: that gate is a `proved`-classification concern (a nullable
1361
+ * basis key is NULL-skipping, so it cannot back an *unconditional* proved FD); governance
1362
+ * asks only which basis key fires on a *non-null* write-through duplicate, which a nullable
1363
+ * subset basis key still governs.
1364
+ *
1365
+ * Multi-source (the mapping is undefined — no single `basisSource`): the 1:1 logical→basis
1366
+ * column mapping the subset search needs does not exist, and the superkey soundness argument
1367
+ * does not transfer across a decomposition (the logical columns come from different basis
1368
+ * rows). Governance cannot be pinned soundly, so reject conservatively. A genuinely
1369
+ * basis-keyless multi-source REPLACE shape is over-rejected by this; it is niche (and
1370
+ * conflict resolution over a decomposition write is itself not clearly supported), so the
1371
+ * over-rejection is acceptable — the escape hatch is to drop the conflict action or declare
1372
+ * it on the basis key.
1373
+ *
1374
+ * Funnels every mismatch through {@link rejectBasisGovernedConflictAction} (the single
1375
+ * diagnostic emitter), passing the first mismatched governing key as `basis`.
1376
+ */
1377
+ function rejectBasisGovernedConflictActionForProvedKey(ctx, constraint, logicalColumns, bijectiveAuthored, label, columnNames, readOnly, errors) {
1378
+ if (readOnly)
1379
+ return;
1380
+ const eff = effectiveKeyDefaultConflict(ctx, constraint);
1381
+ if (eff !== ConflictResolution.REPLACE && eff !== ConflictResolution.IGNORE)
1382
+ return;
1383
+ const basis = ctx.basisSource;
1384
+ const basisCols = basis ? mapLogicalKeyToBasisColumns(ctx, logicalColumns, bijectiveAuthored) : undefined;
1385
+ if (!basis || basisCols === undefined) {
1386
+ // Multi-source / unmappable: governance cannot be pinned — reject conservatively.
1387
+ errors.push({
1388
+ code: 'lens.unenforceable-conflict-action',
1389
+ severity: 'error',
1390
+ site: { table: ctx.table.name, constraint: label },
1391
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) declares 'on conflict ${conflictActionName(eff)}', but its body has no single-source basis-column mapping (multi-source / decomposition), so the governing basis key whose action a write-through duplicate actually resolves to cannot be pinned at deploy. Declare the matching 'on conflict ${conflictActionName(eff)}' on the basis key, or drop the logical conflict action.`,
1392
+ });
1393
+ return;
1394
+ }
1395
+ // Reject the first governing basis key whose action differs from the declared one;
1396
+ // `rejectBasisGovernedConflictAction`'s `eff === basis.conflict` early-return makes
1397
+ // the all-match (and no-governing-key) cases deploy clean.
1398
+ const mismatched = findGoverningBasisKeys(basis, basisCols)
1399
+ .find(m => basisKeyDefaultConflict(basis, m) !== eff);
1400
+ if (!mismatched)
1401
+ return;
1402
+ rejectBasisGovernedConflictAction(ctx, constraint, label, columnNames, readOnly, errors, {
1403
+ conflict: basisKeyDefaultConflict(basis, mismatched),
1404
+ label: basisKeyLabel(basis, mismatched),
1405
+ });
1406
+ }
1407
+ /**
1408
+ * Warns when a `proved` logical key is a strict **superkey** of a NOT-NULL basis key —
1409
+ * the over-enforcement sibling of {@link rejectBasisGovernedConflictActionForProvedKey},
1410
+ * emitted from the same `proved` branch. A logical `unique(a, b)` whose body proof rests on
1411
+ * a basis NOT-NULL `unique(a)` (or basis PK `(a)`) is intrinsically unique — but the basis
1412
+ * enforces uniqueness on a *subset* of the logical key's columns, so it rejects two rows
1413
+ * differing only in `b` that the logical schema advertises as valid. The schema is sound
1414
+ * (every logical invariant still holds), just stricter than declared — so this is the
1415
+ * acknowledgeable warning `lens.over-restrictive-basis-key`, never an error.
1416
+ *
1417
+ * Fires when, after mapping the logical key 1:1 to basis columns
1418
+ * ({@link mapLogicalKeyToBasisColumns}), a governing basis key
1419
+ * ({@link findGoverningBasisKeys}) is a **strict** subset of the mapped columns
1420
+ * (`governingKey.length < mappedBasisCols.length`). The strict filter excludes an
1421
+ * exact-match basis key, which enforces exactly the logical key and is fully realizable —
1422
+ * it must not warn. One warning per logical key; when several strict-subset basis keys
1423
+ * exist, the first in {@link findGoverningBasisKeys} enumeration order (the basis PK if it
1424
+ * governs, else the first declared UNIQUE) names the message — not the smallest by arity.
1425
+ *
1426
+ * Scoped to single-source `proved` keys:
1427
+ * - `readOnly` ⇒ early return (a read-only table never writes, so the over-enforcement
1428
+ * never materializes — the same gate as `lens.no-backing-index`);
1429
+ * - multi-source / unmappable ⇒ {@link mapLogicalKeyToBasisColumns} is undefined ⇒ no
1430
+ * advisory (the 1:1 logical→basis mapping the subset search needs does not exist, and
1431
+ * the superkey argument does not transfer across a decomposition — a documented gap);
1432
+ * - a nullable basis sub-key contributes only a guarded FD, so the logical key never
1433
+ * classifies `proved` and this check never sees it (a documented gap).
1434
+ *
1435
+ * Diagnostic-only: it does not alter the proved classification or the returned obligation.
1436
+ */
1437
+ function warnOverRestrictiveBasisKey(ctx, logicalColumns, bijectiveAuthored, label, columnNames, readOnly, warnings) {
1438
+ if (readOnly)
1439
+ return;
1440
+ const basis = ctx.basisSource;
1441
+ const basisCols = basis ? mapLogicalKeyToBasisColumns(ctx, logicalColumns, bijectiveAuthored) : undefined;
1442
+ if (!basis || basisCols === undefined)
1443
+ return; // multi-source / unmappable: no sound subset relation to report
1444
+ // Strict-subset governing keys only — an exact-match basis key enforces exactly the
1445
+ // logical key (fully realizable) and must not warn.
1446
+ const strictSubset = findGoverningBasisKeys(basis, basisCols)
1447
+ .filter(m => basisKeyColumnIndices(basis, m).length < basisCols.length);
1448
+ if (strictSubset.length === 0)
1449
+ return;
1450
+ const governing = strictSubset[0];
1451
+ const basisKeyColumns = basisKeyColumnIndices(basis, governing)
1452
+ .map(c => basis.columns[c]?.name ?? `#${c}`)
1453
+ .sort((a, b) => a.localeCompare(b));
1454
+ warnings.push({
1455
+ code: 'lens.over-restrictive-basis-key',
1456
+ severity: 'warning',
1457
+ site: { table: ctx.table.name, constraint: label },
1458
+ message: `lens: ${label} on '${ctx.table.name}' (${columnNames.map(c => `'${c}'`).join(', ')}) is a strict superkey of ${basisKeyLabel(basis, governing)} — the basis enforces uniqueness on a subset of the logical key's columns, so it will reject writes the logical schema permits (two rows differing only outside the basis key's columns cannot coexist). This is sound but stricter than the logical declaration advertises; widen the basis key to match, or narrow the logical key, to make the logical contract faithful.`,
1459
+ fingerprintInputs: {
1460
+ constraintColumns: [...columnNames],
1461
+ basisRelation: `${basis.schemaName.toLowerCase()}.${basis.name.toLowerCase()}`,
1462
+ basisKeyColumns,
1463
+ },
1464
+ });
1465
+ }
1466
+ /** The basis-column indices a matched basis key covers — the PK definition or the UNIQUE's columns. */
1467
+ function basisKeyColumnIndices(basis, match) {
1468
+ return match.kind === 'primaryKey'
1469
+ ? basis.primaryKeyDefinition.map(p => p.index)
1470
+ : match.constraint.columns;
1471
+ }
1472
+ /**
1473
+ * Classifies a `check` constraint. A check referencing a column with no write
1474
+ * path (computed lineage) is unrealizable (error). Otherwise it is row-local —
1475
+ * evaluable on the projected row at the write boundary. (Vacuous-by-body-predicate
1476
+ * detection is deferred; a row-local check is always sound, just possibly redundant.)
1477
+ *
1478
+ * An **authored-inverse** column ({@link authoredForwardMap}) has a write path —
1479
+ * the put expressions — and its CHECK stays row-local: the write-time rewrite
1480
+ * substitutes the column's forward `get` (`NEW.`-qualified basis terms) for the
1481
+ * ref, so the CHECK evaluates over the written basis row's logical image. The
1482
+ * map already excludes a forward the rewrite cannot substitute (subquery-
1483
+ * bearing), keeping deploy acceptance and write-time enforceability in lockstep.
1484
+ */
1485
+ function classifyCheckConstraint(ctx, constraint, errors) {
1486
+ const label = constraintLabel(constraint);
1487
+ const authoredForwards = authoredForwardMap(ctx.slot);
1488
+ for (const ref of collectColumnRefNames(constraint.constraint.expr)) {
1489
+ const li = ctx.logicalColIndex.get(ref.toLowerCase());
1490
+ if (li === undefined)
1491
+ continue; // not a logical column of this table — leave to body resolution
1492
+ if (!isReconstructibleColumn(ctx, ref) && !authoredForwards.has(ref.toLowerCase())) {
1493
+ errors.push({
1494
+ code: 'lens.unrealizable-constraint',
1495
+ severity: 'error',
1496
+ site: { table: ctx.table.name, constraint: label, column: ref },
1497
+ message: `lens: ${label} on '${ctx.table.name}' references column '${ref}', which has computed lineage (no write path); a check over it cannot be enforced at the lens boundary`,
1498
+ });
1499
+ return { constraint, kind: 'enforced-row-local' };
1500
+ }
1501
+ }
1502
+ return { constraint, kind: 'enforced-row-local' };
1503
+ }
1504
+ /**
1505
+ * Resolves the basis covering structure AND the basis UNIQUE constraint backing a
1506
+ * logical key: maps each logical column → its basis column (via the single-source
1507
+ * body projection), finds a matching basis UNIQUE constraint, and returns a
1508
+ * row-time covering MV reference (`coveringStructureName` /
1509
+ * `_findRowTimeCoveringStructure`) together with that basis UC. Returning the UC
1510
+ * lets two callers inspect it: {@link classifyKeyConstraint}'s row-time
1511
+ * conflict-action check (via {@link rejectRowTimeConflictAction} — the basis UC's
1512
+ * `defaultConflict` is the action the write path actually honors) and the
1513
+ * plan-time FD re-validation ({@link computeLensAssertedKeyFds}, which re-confirms
1514
+ * currency and the basis UC's partial predicate against the *current* catalog).
1515
+ *
1516
+ * Conservative: a multi-source body, an unmapped column, or a missing basis
1517
+ * UC/structure all yield `undefined` (⇒ commit-time scan). The retired auto-index
1518
+ * is deliberately NOT consulted for a logical schema — the explicit covering MV is
1519
+ * the sole row-time structure (`docs/lens.md` § Constraint Attachment). Reads no
1520
+ * `ctx.root`, so it is safe over a lightweight (un-planned) context.
1521
+ */
1522
+ function findBasisCovering(ctx, logicalColumns) {
1523
+ const basis = ctx.basisSource;
1524
+ if (!basis)
1525
+ return undefined;
1526
+ const basisCols = [];
1527
+ for (const li of logicalColumns) {
1528
+ const name = ctx.table.columns[li]?.name;
1529
+ const bc = name !== undefined ? mappedBasisColumn(ctx, name, basis) : undefined;
1530
+ if (bc === undefined)
1531
+ return undefined;
1532
+ basisCols.push(bc);
1533
+ }
1534
+ const basisColSet = new Set(basisCols);
1535
+ const matching = (basis.uniqueConstraints ?? []).find(uc => uc.columns.length === basisCols.length && uc.columns.every(c => basisColSet.has(c)));
1536
+ if (!matching)
1537
+ return undefined;
1538
+ // Row-time iff a non-stale row-time covering MV answers the basis UC. A
1539
+ // merely *linked* (`coveringStructureName`) but stale / not-row-time-maintained
1540
+ // MV does NOT qualify — claiming row-time there would be unsound, so we fall
1541
+ // through to the commit-time scan.
1542
+ const rowTime = ctx.db._findRowTimeCoveringStructure(basis.schemaName, basis.name, matching);
1543
+ return rowTime ? { ref: { kind: 'materialized-view', name: rowTime.name }, uc: matching } : undefined;
1544
+ }
1545
+ /** The basis column index a logical column maps to under a single-source body, or undefined. */
1546
+ function mappedBasisColumn(ctx, logicalColumn, basis) {
1547
+ const oi = ctx.outputIndex.get(logicalColumn.toLowerCase());
1548
+ if (oi === undefined)
1549
+ return undefined;
1550
+ const rc = ctx.slot.compiledBody.columns[oi];
1551
+ if (rc?.type !== 'column' || rc.expr.type !== 'column')
1552
+ return undefined;
1553
+ return basis.columnIndexMap.get(rc.expr.name.toLowerCase());
1554
+ }
1555
+ /**
1556
+ * Proves a logical key `proved` by **transporting** it onto a declared basis key
1557
+ * through a bijection. Every key column must be either bare-reconstructible (maps
1558
+ * to its basis column via {@link mappedBasisColumn}) or authored-bijective (in
1559
+ * `bijectiveAuthored`, maps to its single put-target basis column via
1560
+ * {@link authoredPutTargetBasisColumn}); the resulting basis columns must exactly
1561
+ * form a declared basis key ({@link findDeclaredKey} — the basis PK or a non-partial
1562
+ * basis UNIQUE). Returns the matched basis key ({@link TransportProof}) on success,
1563
+ * or `undefined`.
1564
+ *
1565
+ * Soundness: a bijection is injective, so distinct logical keys map to distinct
1566
+ * basis keys; the basis key forbids two rows sharing that tuple, so the logical key
1567
+ * is intrinsically unique with zero runtime enforcement. This subsumes both the
1568
+ * basis-PK and basis-UNIQUE cases (a basis UNIQUE alone entails it via the
1569
+ * bijection — no covering MV required). Absent a declared basis key over the put
1570
+ * targets it returns `undefined` and the caller falls to the commit-time fallback
1571
+ * (the honest O(n) scan over the forward image), never an unsound `proved`.
1572
+ *
1573
+ * Every key column must additionally be declared **NOT NULL**: a basis key is
1574
+ * NULL-skipping (SQL UNIQUE permits multiple all-NULL rows), so a nullable key is
1575
+ * only *conditionally* unique — the existing row-time path classifies that with a
1576
+ * guarded FD (`key → others [guard: key IS NOT NULL]`), which the unconditional
1577
+ * `proved` FD would unsoundly override. A PK column is always NOT NULL; an
1578
+ * authored-bijective column whose logical column is NOT NULL has a non-null,
1579
+ * unconditionally-unique key. A nullable key column therefore defers to
1580
+ * row-time/commit-time rather than taking the transport shortcut.
1581
+ *
1582
+ * Conservative: a multi-source body (no `basisSource`), a key column that is
1583
+ * neither bare nor authored-bijective, a nullable key column, an authored column
1584
+ * with more than one put target / a put target that is not a single bare basis
1585
+ * column, or any unmapped column all yield `undefined`.
1586
+ */
1587
+ function proveKeyByBijectionTransport(ctx, logicalColumns, bijectiveAuthored) {
1588
+ const basis = ctx.basisSource;
1589
+ if (!basis)
1590
+ return undefined;
1591
+ // A nullable key is only conditionally unique over a NULL-skipping basis key; the
1592
+ // unconditional `proved` FD would be unsound. Defer to row-time/commit-time. This is
1593
+ // the proof's gate, NOT the governance mapper's ({@link mapLogicalKeyToBasisColumns}
1594
+ // omits it — a nullable subset basis key still governs a non-null duplicate).
1595
+ for (const li of logicalColumns) {
1596
+ if (!ctx.table.columns[li]?.notNull)
1597
+ return undefined;
1598
+ }
1599
+ const basisCols = mapLogicalKeyToBasisColumns(ctx, logicalColumns, bijectiveAuthored);
1600
+ if (basisCols === undefined)
1601
+ return undefined;
1602
+ const match = findDeclaredKey(basis, basisCols);
1603
+ return match ? { basis, match } : undefined;
1604
+ }
1605
+ /**
1606
+ * Maps a logical key's columns to their basis column indices under a single-source
1607
+ * body — each key column either bare-reconstructible (maps to its basis column via
1608
+ * {@link mappedBasisColumn}) or authored-bijective (maps to its single put-target basis
1609
+ * column via {@link authoredPutTargetBasisColumn}). The shared mapping loop behind both
1610
+ * the `proved` classification ({@link proveKeyByBijectionTransport}, which adds the
1611
+ * `notNull`/`findDeclaredKey` exact-match gates) and the conflict-action governance check
1612
+ * ({@link rejectBasisGovernedConflictActionForProvedKey}, which subset-searches the result).
1613
+ *
1614
+ * Deliberately carries NO `notNull` gate: that gate is a `proved`-classification concern
1615
+ * (a nullable basis key is NULL-skipping, so it cannot back an *unconditional* proved FD),
1616
+ * whereas governance asks only which basis key fires on a *non-null* write-through
1617
+ * duplicate — which a nullable subset basis key still governs.
1618
+ *
1619
+ * Returns `undefined` (the conservative signal) for a multi-source body (no `basisSource`),
1620
+ * a key column that is neither bare-reconstructible nor authored-bijective, an authored
1621
+ * column with more than one put target / a non-bare put target, or any unmapped column.
1622
+ */
1623
+ function mapLogicalKeyToBasisColumns(ctx, logicalColumns, bijectiveAuthored) {
1624
+ const basis = ctx.basisSource;
1625
+ if (!basis)
1626
+ return undefined;
1627
+ const basisCols = [];
1628
+ for (const li of logicalColumns) {
1629
+ const col = ctx.table.columns[li];
1630
+ if (!col)
1631
+ return undefined;
1632
+ const name = col.name;
1633
+ let bc;
1634
+ if (isReconstructibleColumn(ctx, name)) {
1635
+ bc = mappedBasisColumn(ctx, name, basis);
1636
+ }
1637
+ else if (bijectiveAuthored.has(name.toLowerCase())) {
1638
+ bc = authoredPutTargetBasisColumn(ctx, name, basis);
1639
+ }
1640
+ else {
1641
+ return undefined; // neither bare-reconstructible nor authored-bijective
1642
+ }
1643
+ if (bc === undefined)
1644
+ return undefined;
1645
+ basisCols.push(bc);
1646
+ }
1647
+ return basisCols;
1648
+ }
1649
+ /**
1650
+ * The governing conflict action of a matched basis key — the action a duplicate
1651
+ * actually resolves to. Mirrors {@link effectiveKeyDefaultConflict}'s two arms exactly:
1652
+ * a PK uses {@link resolvePkDefaultConflict} (which also folds in any column-level
1653
+ * `defaultConflict`); a UNIQUE uses its own `defaultConflict`. Takes `(basis, match)` —
1654
+ * the pair a {@link TransportProof} carries, but also any subset-matched governing key —
1655
+ * so the transport and governance callers share it.
1656
+ */
1657
+ function basisKeyDefaultConflict(basis, match) {
1658
+ return match.kind === 'primaryKey'
1659
+ ? resolvePkDefaultConflict(basis)
1660
+ : match.constraint.defaultConflict;
1661
+ }
1662
+ /**
1663
+ * A human-readable label for a matched basis key, for the conflict-action diagnostic.
1664
+ * There is no MV here (the proof/governance needs none), so name the basis key itself —
1665
+ * the PK / UNIQUE on the basis table, falling back to the column list when a UNIQUE is
1666
+ * unnamed (e.g. `basis unique (code)`).
1667
+ */
1668
+ function basisKeyLabel(basis, match) {
1669
+ if (match.kind === 'primaryKey')
1670
+ return `basis primary key on '${basis.name}'`;
1671
+ const uc = match.constraint;
1672
+ const ucName = uc.name !== undefined
1673
+ ? `'${uc.name}'`
1674
+ : `(${uc.columns.map(c => basis.columns[c]?.name ?? `#${c}`).join(', ')})`;
1675
+ return `basis unique ${ucName}`;
1676
+ }
1677
+ /**
1678
+ * The basis column an authored (`with inverse`) logical column writes to, when its
1679
+ * put is a **single** assignment to one bare basis column — the put-target the
1680
+ * bijection transports onto a basis key. Reads the compiled body's authored
1681
+ * `inverse` clause directly (the same `ResultColumnInverse[]` the lineage threads).
1682
+ * Returns undefined for an authored put with more than one target (a multi-column
1683
+ * fan-out cannot land on a single basis key column) or a target name that does not
1684
+ * resolve to a basis column.
1685
+ */
1686
+ function authoredPutTargetBasisColumn(ctx, logicalColumn, basis) {
1687
+ const oi = ctx.outputIndex.get(logicalColumn.toLowerCase());
1688
+ if (oi === undefined)
1689
+ return undefined;
1690
+ const rc = ctx.slot.compiledBody.columns[oi];
1691
+ if (rc?.type !== 'column' || !rc.inverse || rc.inverse.length !== 1)
1692
+ return undefined;
1693
+ return basis.columnIndexMap.get(rc.inverse[0].column.toLowerCase());
1694
+ }
1695
+ // ---------------------------------------------------------------------------
1696
+ // Read-side: declared-key FD contribution to the optimizer (the inlined-view
1697
+ // boundary). See docs/lens.md § Constraint Attachment and docs/optimizer.md
1698
+ // § Functional Dependency Tracking.
1699
+ // ---------------------------------------------------------------------------
1700
+ /**
1701
+ * The declared logical keys a lens *proves* or *actively enforces*, encoded as
1702
+ * physical functional dependencies in the body's **output**-column-index space,
1703
+ * for the optimizer to consume at the inlined-view boundary
1704
+ * (`planner/nodes/asserted-keys-node.ts`, wired in `planner/building/select.ts`).
1705
+ *
1706
+ * Soundness is gated by the prover's per-constraint {@link ConstraintObligation}
1707
+ * kind — a false key FD is a *correctness* defect (it can make
1708
+ * DISTINCT/join-elimination/order-by-pruning drop real rows), so the gate
1709
+ * under-claims exactly like every other FD-propagation rule:
1710
+ *
1711
+ * - `proved` — the body intrinsically guarantees the key (the same FD surface
1712
+ * the optimizer derives locally); contribute the **unconditional** key FD.
1713
+ * Redundant-but-harmless when local propagation already surfaces it (`addFd`
1714
+ * subsumes), load-bearing when the inlining context loses it.
1715
+ * - `vacuous` — the empty (singleton) key; contribute `∅ → all_cols` (≤1-row).
1716
+ * - `enforced-set-level` `row-time` — a covering structure enforces uniqueness
1717
+ * per row-write, but only over the **non-null** tuples a plain (NULL-skipping)
1718
+ * UNIQUE governs — SQL UNIQUE permits multiple all-/any-NULL rows, so the key
1719
+ * is conditionally unique. Contribute a **guarded** FD `key → others
1720
+ * [guard: key IS NOT NULL]` (the same shape a partial UNIQUE emits), and only
1721
+ * when the covering structure re-validates against the *current* catalog (it
1722
+ * can be dropped / go stale out-of-band between deploys) and the backing basis
1723
+ * UC is non-partial (so NULL-skip is the *entire* uniqueness scope).
1724
+ * - `enforced-set-level` `commit-time` — **excluded**. Detection-only at commit;
1725
+ * a duplicate can transiently exist mid-statement (read-own-writes / Halloween),
1726
+ * so assuming the FD mid-statement is unsound.
1727
+ * - `enforced-row-local` / `enforced-fk` — not uniqueness facts; excluded.
1728
+ *
1729
+ * Returns the (deduped) FD list, or `[]` when nothing is contributable — the
1730
+ * wiring site inlines no node for an empty list (plain views / MVs / unenforced
1731
+ * keys produce none).
1732
+ */
1733
+ export function computeLensAssertedKeyFds(slot, db) {
1734
+ if (!slot.obligations || slot.obligations.length === 0)
1735
+ return [];
1736
+ const { outputIndex, outputColumns } = buildOutputIndex(slot);
1737
+ const outColCount = outputColumns.length;
1738
+ if (outColCount === 0)
1739
+ return [];
1740
+ let fds = [];
1741
+ for (const ob of slot.obligations) {
1742
+ const fd = assertedFdForObligation(ob, slot, db, outputIndex, outColCount);
1743
+ if (fd)
1744
+ fds = addFd(fds, fd);
1745
+ }
1746
+ return fds;
1747
+ }
1748
+ /** The asserted key FD for one obligation, or undefined when it contributes none. */
1749
+ function assertedFdForObligation(ob, slot, db, outputIndex, outColCount) {
1750
+ const c = ob.constraint;
1751
+ if (c.kind !== 'primaryKey' && c.kind !== 'unique')
1752
+ return undefined; // not a key fact
1753
+ switch (ob.kind) {
1754
+ case 'vacuous':
1755
+ // The empty (singleton) key ⇒ `∅ → all_cols` (≤1-row). superkeyToFd([], n)
1756
+ // is the canonical encoding.
1757
+ return superkeyToFd([], outColCount);
1758
+ case 'proved':
1759
+ // The body unconditionally guarantees the key. Contribute it unconditionally.
1760
+ return encodeKeyFd(logicalKeyColumns(c), slot.logicalTable, outputIndex, outColCount, undefined);
1761
+ case 'enforced-set-level': {
1762
+ if (ob.mode !== 'row-time')
1763
+ return undefined; // commit-time gated out (unsound mid-statement)
1764
+ const logicalCols = logicalKeyColumns(c);
1765
+ // Re-validate the covering structure against the current catalog (currency)
1766
+ // and require a non-partial basis UC (so IS-NOT-NULL is the full scope).
1767
+ if (!revalidateRowTime(slot, db, logicalCols))
1768
+ return undefined;
1769
+ const guard = buildNotNullGuard(logicalCols, slot.logicalTable, outputIndex);
1770
+ if (!guard)
1771
+ return undefined; // no nullable key column ⇒ would be `proved`, not row-time; skip
1772
+ return encodeKeyFd(logicalCols, slot.logicalTable, outputIndex, outColCount, guard);
1773
+ }
1774
+ default:
1775
+ return undefined; // enforced-row-local / enforced-fk
1776
+ }
1777
+ }
1778
+ /** The logical column indices forming a primary-key / unique constraint. */
1779
+ function logicalKeyColumns(c) {
1780
+ return c.kind === 'primaryKey' ? c.columns.map(col => col.index) : c.constraint.columns;
1781
+ }
1782
+ /**
1783
+ * Encode `key → others` over the body's output columns. Maps each logical key
1784
+ * column → its output index; a key column with no output index
1785
+ * (not emitted) makes the key inexpressible (⇒ undefined). The
1786
+ * all-columns key has no non-trivial FD encoding (`superkeyToFd` ⇒ undefined) and
1787
+ * is skipped (v1). When `guard` is supplied the FD activates only under a
1788
+ * surrounding predicate that entails it (the nullable / partial-UNIQUE case).
1789
+ */
1790
+ function encodeKeyFd(logicalColumns, table, outputIndex, outColCount, guard) {
1791
+ const outCols = [];
1792
+ for (const li of logicalColumns) {
1793
+ const name = table.columns[li]?.name;
1794
+ const oi = name !== undefined ? outputIndex.get(name.toLowerCase()) : undefined;
1795
+ if (oi === undefined)
1796
+ return undefined; // not emitted ⇒ not in the readable relation
1797
+ outCols.push(oi);
1798
+ }
1799
+ const fd = superkeyToFd(outCols, outColCount);
1800
+ if (!fd)
1801
+ return undefined;
1802
+ return guard ? { ...fd, guard } : fd;
1803
+ }
1804
+ /**
1805
+ * The `key IS NOT NULL` guard for a conditionally-unique (NULL-skipping) key — one
1806
+ * `is-null negated:true` clause per **nullable** key column (a NOT-NULL column
1807
+ * needs none; the guard checker discharges it from type info). Returns undefined
1808
+ * when every key column is already NOT NULL — that key is unconditional and would
1809
+ * have classified `proved`, so a row-time obligation over it is skipped defensively.
1810
+ */
1811
+ function buildNotNullGuard(logicalColumns, table, outputIndex) {
1812
+ const clauses = [];
1813
+ for (const li of logicalColumns) {
1814
+ const col = table.columns[li];
1815
+ if (!col)
1816
+ return undefined;
1817
+ if (col.notNull)
1818
+ continue;
1819
+ const oi = outputIndex.get(col.name.toLowerCase());
1820
+ if (oi === undefined)
1821
+ return undefined;
1822
+ clauses.push({ kind: 'is-null', column: oi, negated: true });
1823
+ }
1824
+ return clauses.length > 0 ? { clauses } : undefined;
1825
+ }
1826
+ /**
1827
+ * Re-confirm at plan time that a row-time obligation's covering structure is
1828
+ * still valid: a covering MV can be dropped or go stale out-of-band between
1829
+ * deploys (the basis is a physical schema whose DDL does not re-run the prover),
1830
+ * so the deploy-time snapshot must be re-validated. Also requires the backing
1831
+ * basis UC to be **non-partial** — a partial UNIQUE (`… where P`) makes the
1832
+ * uniqueness scope `P`, not merely NULL-skip, which the IS-NOT-NULL guard would
1833
+ * not capture. Cheap: re-resolves the covering structure against the current
1834
+ * catalog without re-planning the body.
1835
+ */
1836
+ function revalidateRowTime(slot, db, logicalColumns) {
1837
+ const ctx = buildLiteProveContext(slot, db);
1838
+ const covering = findBasisCovering(ctx, logicalColumns);
1839
+ return covering !== undefined && covering.uc.predicate === undefined;
1840
+ }
1841
+ /**
1842
+ * A lightweight prove context for plan-time covering re-resolution — every field
1843
+ * {@link findBasisCovering} reads, with `root` left undefined (the body is NOT
1844
+ * re-planned; the covering resolver is plan-independent). Keeps the per-read cost
1845
+ * to a couple of map builds + a catalog lookup.
1846
+ */
1847
+ function buildLiteProveContext(slot, db) {
1848
+ const table = slot.logicalTable;
1849
+ const logicalColIndex = new Map();
1850
+ table.columns.forEach((c, i) => logicalColIndex.set(c.name.toLowerCase(), i));
1851
+ const { outputIndex, outputColumns } = buildOutputIndex(slot);
1852
+ const basisSchemaName = slot.defaultBasis.schemaName;
1853
+ return {
1854
+ slot,
1855
+ db,
1856
+ table,
1857
+ logicalColIndex,
1858
+ outputColumns,
1859
+ outputIndex,
1860
+ root: undefined,
1861
+ basisSource: resolveSingleBasisSource(db.schemaManager, slot.compiledBody, basisSchemaName),
1862
+ basisSchemaName,
1863
+ };
1864
+ }
1865
+ // ---------------------------------------------------------------------------
1866
+ // Warning: no answering structure for a declared access pattern
1867
+ // ---------------------------------------------------------------------------
1868
+ /**
1869
+ * `quereus.lens.access.<col>` declares an expected lookup/ordering. The advisory
1870
+ * fires when no basis ordering/index serves it. v1 best-effort: a single-source
1871
+ * body's basis table must carry an index whose leading column is the access
1872
+ * column (or have that column as the leading PK column); otherwise reads scan.
1873
+ */
1874
+ function checkAnsweringStructures(ctx, warnings) {
1875
+ const accessTags = getReservedTagByTemplate(ctx.table.tags, 'quereus.lens.access.<col>');
1876
+ if (accessTags.length === 0)
1877
+ return;
1878
+ const basis = ctx.basisSource;
1879
+ for (const tag of accessTags) {
1880
+ const col = tag.segment;
1881
+ if (basisServesAccess(ctx, basis, col))
1882
+ continue;
1883
+ warnings.push({
1884
+ code: 'lens.no-answering-structure',
1885
+ severity: 'warning',
1886
+ site: { table: ctx.table.name, column: col },
1887
+ message: `lens: declared access pattern 'quereus.lens.access.${col}' on '${ctx.table.name}' has no answering basis ordering or index — reads on '${col}' will scan. Add a basis index/covering materialized view ordered by '${col}'.`,
1888
+ fingerprintInputs: buildFingerprint(ctx, [col], false),
1889
+ });
1890
+ }
1891
+ }
1892
+ /** True iff the basis single source has an index / PK whose leading column is `accessCol`. */
1893
+ function basisServesAccess(ctx, basis, accessCol) {
1894
+ if (!basis)
1895
+ return false;
1896
+ const basisCol = mappedBasisColumn(ctx, accessCol, basis);
1897
+ if (basisCol === undefined)
1898
+ return false;
1899
+ if (basis.primaryKeyDefinition[0]?.index === basisCol)
1900
+ return true;
1901
+ for (const idx of basis.indexes ?? []) {
1902
+ if (idx.columns[0]?.index === basisCol)
1903
+ return true;
1904
+ }
1905
+ return false;
1906
+ }
1907
+ // ---------------------------------------------------------------------------
1908
+ // Warning: partial override (informational)
1909
+ // ---------------------------------------------------------------------------
1910
+ /**
1911
+ * When an override covers only some columns and the remainder took the default
1912
+ * alignment, list which columns were override-authored vs gap-filled, read
1913
+ * straight off the slot's `columnProvenance`.
1914
+ */
1915
+ function checkPartialOverride(ctx, warnings) {
1916
+ const authored = ctx.slot.columnProvenance.filter(p => p.source === 'override').map(p => p.logicalColumn);
1917
+ const gapFilled = ctx.slot.columnProvenance.filter(p => p.source === 'default').map(p => p.logicalColumn);
1918
+ if (authored.length === 0 || gapFilled.length === 0)
1919
+ return; // pure override or pure default — not partial
1920
+ warnings.push({
1921
+ code: 'lens.partial-override',
1922
+ severity: 'warning',
1923
+ site: { table: ctx.table.name },
1924
+ message: `lens: '${ctx.table.name}' is a partial override — override-authored column(s): ${authored.map(c => `'${c}'`).join(', ')}; default gap-filled column(s): ${gapFilled.map(c => `'${c}'`).join(', ')}`,
1925
+ });
1926
+ }
1927
+ // ---------------------------------------------------------------------------
1928
+ // Fingerprint inputs (consumed by the sibling acknowledgment ticket)
1929
+ // ---------------------------------------------------------------------------
1930
+ function buildFingerprint(ctx, columnNames, hasCoveringStructure) {
1931
+ const basis = ctx.basisSource;
1932
+ return {
1933
+ constraintColumns: [...columnNames].sort((a, b) => a.localeCompare(b)),
1934
+ hasCoveringStructure,
1935
+ cardinalityBand: cardinalityBand(basis?.estimatedRows),
1936
+ basisRelation: basis ? `${basis.schemaName.toLowerCase()}.${basis.name.toLowerCase()}` : undefined,
1937
+ };
1938
+ }
1939
+ /** Coarse cardinality band so an acknowledgment survives ordinary row-count churn. */
1940
+ function cardinalityBand(rows) {
1941
+ if (rows === undefined)
1942
+ return 'unknown';
1943
+ if (rows === 0)
1944
+ return 'empty';
1945
+ if (rows < 1_000)
1946
+ return 'small';
1947
+ if (rows < 1_000_000)
1948
+ return 'medium';
1949
+ return 'large';
1950
+ }
1951
+ // ---------------------------------------------------------------------------
1952
+ // Shared utility
1953
+ // ---------------------------------------------------------------------------
1954
+ /**
1955
+ * Collects every `column` reference name in an expression (best-effort reflective walk,
1956
+ * qualifier-stripped — returns each `column` node's `.name`). Shared with the lens write
1957
+ * side (`planner/mutation/lens-enforcement.ts`), which maps these refs through the slot's
1958
+ * logical→basis projection to derive a row-local CHECK's `referencedWriteRowColumns`
1959
+ * metadata — keeping the gate's notion of "write-row column" consistent with the prover's
1960
+ * notion of "row-local" (both use this walk + logical-column membership; see
1961
+ * {@link classifyCheckConstraint}).
1962
+ */
1963
+ export function collectColumnRefNames(expr) {
1964
+ const names = [];
1965
+ const stack = [expr];
1966
+ while (stack.length > 0) {
1967
+ const node = stack.pop();
1968
+ if (node.type === 'column' && typeof node.name === 'string') {
1969
+ names.push(node.name);
1970
+ }
1971
+ for (const key of Object.keys(node)) {
1972
+ const value = node[key];
1973
+ if (!value)
1974
+ continue;
1975
+ if (Array.isArray(value)) {
1976
+ for (const item of value) {
1977
+ if (item && typeof item === 'object' && 'type' in item)
1978
+ stack.push(item);
1979
+ }
1980
+ }
1981
+ else if (typeof value === 'object' && 'type' in value) {
1982
+ stack.push(value);
1983
+ }
1984
+ }
1985
+ }
1986
+ return names;
1987
+ }
1988
+ //# sourceMappingURL=lens-prover.js.map