@quereus/quereus 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (900) hide show
  1. package/README.md +7 -0
  2. package/dist/src/common/datatype.d.ts +12 -0
  3. package/dist/src/common/datatype.d.ts.map +1 -1
  4. package/dist/src/common/datatype.js.map +1 -1
  5. package/dist/src/common/types.d.ts +24 -0
  6. package/dist/src/common/types.d.ts.map +1 -1
  7. package/dist/src/common/types.js.map +1 -1
  8. package/dist/src/core/database-assertions.d.ts +37 -9
  9. package/dist/src/core/database-assertions.d.ts.map +1 -1
  10. package/dist/src/core/database-assertions.js +62 -110
  11. package/dist/src/core/database-assertions.js.map +1 -1
  12. package/dist/src/core/database-events.d.ts +163 -0
  13. package/dist/src/core/database-events.d.ts.map +1 -1
  14. package/dist/src/core/database-events.js +235 -21
  15. package/dist/src/core/database-events.js.map +1 -1
  16. package/dist/src/core/database-external-changes.d.ts +28 -0
  17. package/dist/src/core/database-external-changes.d.ts.map +1 -0
  18. package/dist/src/core/database-external-changes.js +242 -0
  19. package/dist/src/core/database-external-changes.js.map +1 -0
  20. package/dist/src/core/database-internal.d.ts +50 -1
  21. package/dist/src/core/database-internal.d.ts.map +1 -1
  22. package/dist/src/core/database-materialized-views.d.ts +1253 -0
  23. package/dist/src/core/database-materialized-views.d.ts.map +1 -0
  24. package/dist/src/core/database-materialized-views.js +3064 -0
  25. package/dist/src/core/database-materialized-views.js.map +1 -0
  26. package/dist/src/core/database-options.d.ts +4 -0
  27. package/dist/src/core/database-options.d.ts.map +1 -1
  28. package/dist/src/core/database-options.js +10 -0
  29. package/dist/src/core/database-options.js.map +1 -1
  30. package/dist/src/core/database-transaction.d.ts +19 -3
  31. package/dist/src/core/database-transaction.d.ts.map +1 -1
  32. package/dist/src/core/database-transaction.js +30 -3
  33. package/dist/src/core/database-transaction.js.map +1 -1
  34. package/dist/src/core/database-watchers.d.ts +19 -0
  35. package/dist/src/core/database-watchers.d.ts.map +1 -1
  36. package/dist/src/core/database-watchers.js +63 -3
  37. package/dist/src/core/database-watchers.js.map +1 -1
  38. package/dist/src/core/database.d.ts +203 -11
  39. package/dist/src/core/database.d.ts.map +1 -1
  40. package/dist/src/core/database.js +493 -29
  41. package/dist/src/core/database.js.map +1 -1
  42. package/dist/src/core/derived-row-validator.d.ts +137 -0
  43. package/dist/src/core/derived-row-validator.d.ts.map +1 -0
  44. package/dist/src/core/derived-row-validator.js +314 -0
  45. package/dist/src/core/derived-row-validator.js.map +1 -0
  46. package/dist/src/core/statement.d.ts.map +1 -1
  47. package/dist/src/core/statement.js +30 -9
  48. package/dist/src/core/statement.js.map +1 -1
  49. package/dist/src/emit/ast-stringify.d.ts +135 -1
  50. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  51. package/dist/src/emit/ast-stringify.js +793 -118
  52. package/dist/src/emit/ast-stringify.js.map +1 -1
  53. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  54. package/dist/src/func/builtins/aggregate.js +11 -10
  55. package/dist/src/func/builtins/aggregate.js.map +1 -1
  56. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  57. package/dist/src/func/builtins/builtin-window-functions.js +32 -0
  58. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  59. package/dist/src/func/builtins/explain.d.ts +3 -0
  60. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  61. package/dist/src/func/builtins/explain.js +229 -0
  62. package/dist/src/func/builtins/explain.js.map +1 -1
  63. package/dist/src/func/builtins/index.d.ts.map +1 -1
  64. package/dist/src/func/builtins/index.js +10 -2
  65. package/dist/src/func/builtins/index.js.map +1 -1
  66. package/dist/src/func/builtins/json.d.ts.map +1 -1
  67. package/dist/src/func/builtins/json.js +3 -2
  68. package/dist/src/func/builtins/json.js.map +1 -1
  69. package/dist/src/func/builtins/mutation.d.ts +2 -0
  70. package/dist/src/func/builtins/mutation.d.ts.map +1 -0
  71. package/dist/src/func/builtins/mutation.js +53 -0
  72. package/dist/src/func/builtins/mutation.js.map +1 -0
  73. package/dist/src/func/builtins/schema.d.ts +2 -0
  74. package/dist/src/func/builtins/schema.d.ts.map +1 -1
  75. package/dist/src/func/builtins/schema.js +713 -26
  76. package/dist/src/func/builtins/schema.js.map +1 -1
  77. package/dist/src/func/builtins/string.js +1 -1
  78. package/dist/src/func/builtins/string.js.map +1 -1
  79. package/dist/src/func/registration.d.ts +9 -0
  80. package/dist/src/func/registration.d.ts.map +1 -1
  81. package/dist/src/func/registration.js +4 -0
  82. package/dist/src/func/registration.js.map +1 -1
  83. package/dist/src/index.d.ts +25 -6
  84. package/dist/src/index.d.ts.map +1 -1
  85. package/dist/src/index.js +27 -3
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/parser/ast.d.ts +353 -21
  88. package/dist/src/parser/ast.d.ts.map +1 -1
  89. package/dist/src/parser/index.d.ts +14 -1
  90. package/dist/src/parser/index.d.ts.map +1 -1
  91. package/dist/src/parser/index.js +19 -0
  92. package/dist/src/parser/index.js.map +1 -1
  93. package/dist/src/parser/lexer.d.ts +9 -0
  94. package/dist/src/parser/lexer.d.ts.map +1 -1
  95. package/dist/src/parser/lexer.js +9 -0
  96. package/dist/src/parser/lexer.js.map +1 -1
  97. package/dist/src/parser/parser.d.ts +276 -7
  98. package/dist/src/parser/parser.d.ts.map +1 -1
  99. package/dist/src/parser/parser.js +1387 -469
  100. package/dist/src/parser/parser.js.map +1 -1
  101. package/dist/src/parser/visitor.d.ts.map +1 -1
  102. package/dist/src/parser/visitor.js +12 -8
  103. package/dist/src/parser/visitor.js.map +1 -1
  104. package/dist/src/planner/analysis/assertion-classifier.d.ts.map +1 -1
  105. package/dist/src/planner/analysis/assertion-classifier.js +4 -0
  106. package/dist/src/planner/analysis/assertion-classifier.js.map +1 -1
  107. package/dist/src/planner/analysis/assertion-hoist-cache.d.ts.map +1 -1
  108. package/dist/src/planner/analysis/assertion-hoist-cache.js +8 -4
  109. package/dist/src/planner/analysis/assertion-hoist-cache.js.map +1 -1
  110. package/dist/src/planner/analysis/authored-inverse.d.ts +22 -0
  111. package/dist/src/planner/analysis/authored-inverse.d.ts.map +1 -0
  112. package/dist/src/planner/analysis/authored-inverse.js +267 -0
  113. package/dist/src/planner/analysis/authored-inverse.js.map +1 -0
  114. package/dist/src/planner/analysis/change-scope.d.ts +34 -4
  115. package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
  116. package/dist/src/planner/analysis/change-scope.js +108 -7
  117. package/dist/src/planner/analysis/change-scope.js.map +1 -1
  118. package/dist/src/planner/analysis/check-extraction.d.ts +36 -2
  119. package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
  120. package/dist/src/planner/analysis/check-extraction.js +174 -46
  121. package/dist/src/planner/analysis/check-extraction.js.map +1 -1
  122. package/dist/src/planner/analysis/coarsened-key.d.ts +109 -0
  123. package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -0
  124. package/dist/src/planner/analysis/coarsened-key.js +228 -0
  125. package/dist/src/planner/analysis/coarsened-key.js.map +1 -0
  126. package/dist/src/planner/analysis/comparison-collation.d.ts +216 -0
  127. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -0
  128. package/dist/src/planner/analysis/comparison-collation.js +341 -0
  129. package/dist/src/planner/analysis/comparison-collation.js.map +1 -0
  130. package/dist/src/planner/analysis/constraint-extractor.d.ts +3 -1
  131. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  132. package/dist/src/planner/analysis/constraint-extractor.js +192 -9
  133. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  134. package/dist/src/planner/analysis/coverage-prover.d.ts +321 -0
  135. package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -0
  136. package/dist/src/planner/analysis/coverage-prover.js +1038 -0
  137. package/dist/src/planner/analysis/coverage-prover.js.map +1 -0
  138. package/dist/src/planner/analysis/key-filter.d.ts +22 -0
  139. package/dist/src/planner/analysis/key-filter.d.ts.map +1 -0
  140. package/dist/src/planner/analysis/key-filter.js +105 -0
  141. package/dist/src/planner/analysis/key-filter.js.map +1 -0
  142. package/dist/src/planner/analysis/partial-unique-extraction.d.ts +36 -1
  143. package/dist/src/planner/analysis/partial-unique-extraction.d.ts.map +1 -1
  144. package/dist/src/planner/analysis/partial-unique-extraction.js +148 -22
  145. package/dist/src/planner/analysis/partial-unique-extraction.js.map +1 -1
  146. package/dist/src/planner/analysis/predicate-normalizer.d.ts.map +1 -1
  147. package/dist/src/planner/analysis/predicate-normalizer.js +30 -1
  148. package/dist/src/planner/analysis/predicate-normalizer.js.map +1 -1
  149. package/dist/src/planner/analysis/predicate-shape.d.ts +36 -1
  150. package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
  151. package/dist/src/planner/analysis/predicate-shape.js +51 -13
  152. package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
  153. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +314 -0
  154. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -0
  155. package/dist/src/planner/analysis/query-rewrite-matcher.js +1081 -0
  156. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -0
  157. package/dist/src/planner/analysis/scalar-invertibility.d.ts +92 -0
  158. package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -0
  159. package/dist/src/planner/analysis/scalar-invertibility.js +129 -0
  160. package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -0
  161. package/dist/src/planner/analysis/update-lineage.d.ts +196 -0
  162. package/dist/src/planner/analysis/update-lineage.d.ts.map +1 -0
  163. package/dist/src/planner/analysis/update-lineage.js +322 -0
  164. package/dist/src/planner/analysis/update-lineage.js.map +1 -0
  165. package/dist/src/planner/analysis/view-complement.d.ts +42 -0
  166. package/dist/src/planner/analysis/view-complement.d.ts.map +1 -0
  167. package/dist/src/planner/analysis/view-complement.js +54 -0
  168. package/dist/src/planner/analysis/view-complement.js.map +1 -0
  169. package/dist/src/planner/building/alter-table.d.ts +1 -1
  170. package/dist/src/planner/building/alter-table.d.ts.map +1 -1
  171. package/dist/src/planner/building/alter-table.js +211 -2
  172. package/dist/src/planner/building/alter-table.js.map +1 -1
  173. package/dist/src/planner/building/block.d.ts.map +1 -1
  174. package/dist/src/planner/building/block.js +18 -1
  175. package/dist/src/planner/building/block.js.map +1 -1
  176. package/dist/src/planner/building/constraint-builder.d.ts +33 -5
  177. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  178. package/dist/src/planner/building/constraint-builder.js +63 -28
  179. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  180. package/dist/src/planner/building/create-view.d.ts +9 -0
  181. package/dist/src/planner/building/create-view.d.ts.map +1 -1
  182. package/dist/src/planner/building/create-view.js +41 -12
  183. package/dist/src/planner/building/create-view.js.map +1 -1
  184. package/dist/src/planner/building/ddl.d.ts.map +1 -1
  185. package/dist/src/planner/building/ddl.js +94 -0
  186. package/dist/src/planner/building/ddl.js.map +1 -1
  187. package/dist/src/planner/building/declare-schema.d.ts +1 -0
  188. package/dist/src/planner/building/declare-schema.d.ts.map +1 -1
  189. package/dist/src/planner/building/declare-schema.js +4 -1
  190. package/dist/src/planner/building/declare-schema.js.map +1 -1
  191. package/dist/src/planner/building/default-scope.d.ts +26 -0
  192. package/dist/src/planner/building/default-scope.d.ts.map +1 -0
  193. package/dist/src/planner/building/default-scope.js +41 -0
  194. package/dist/src/planner/building/default-scope.js.map +1 -0
  195. package/dist/src/planner/building/delete.d.ts +19 -1
  196. package/dist/src/planner/building/delete.d.ts.map +1 -1
  197. package/dist/src/planner/building/delete.js +109 -30
  198. package/dist/src/planner/building/delete.js.map +1 -1
  199. package/dist/src/planner/building/dml-target.d.ts +118 -0
  200. package/dist/src/planner/building/dml-target.d.ts.map +1 -0
  201. package/dist/src/planner/building/dml-target.js +282 -0
  202. package/dist/src/planner/building/dml-target.js.map +1 -0
  203. package/dist/src/planner/building/drop-index.d.ts.map +1 -1
  204. package/dist/src/planner/building/drop-index.js +4 -1
  205. package/dist/src/planner/building/drop-index.js.map +1 -1
  206. package/dist/src/planner/building/drop-view.d.ts.map +1 -1
  207. package/dist/src/planner/building/drop-view.js +4 -2
  208. package/dist/src/planner/building/drop-view.js.map +1 -1
  209. package/dist/src/planner/building/expression.d.ts.map +1 -1
  210. package/dist/src/planner/building/expression.js +60 -21
  211. package/dist/src/planner/building/expression.js.map +1 -1
  212. package/dist/src/planner/building/foreign-key-builder.d.ts +30 -0
  213. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  214. package/dist/src/planner/building/foreign-key-builder.js +160 -129
  215. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  216. package/dist/src/planner/building/insert.d.ts +45 -2
  217. package/dist/src/planner/building/insert.d.ts.map +1 -1
  218. package/dist/src/planner/building/insert.js +257 -88
  219. package/dist/src/planner/building/insert.js.map +1 -1
  220. package/dist/src/planner/building/lens-auxiliary-access.d.ts +22 -0
  221. package/dist/src/planner/building/lens-auxiliary-access.d.ts.map +1 -0
  222. package/dist/src/planner/building/lens-auxiliary-access.js +132 -0
  223. package/dist/src/planner/building/lens-auxiliary-access.js.map +1 -0
  224. package/dist/src/planner/building/materialized-view.d.ts +16 -0
  225. package/dist/src/planner/building/materialized-view.d.ts.map +1 -0
  226. package/dist/src/planner/building/materialized-view.js +57 -0
  227. package/dist/src/planner/building/materialized-view.js.map +1 -0
  228. package/dist/src/planner/building/returning-star.d.ts +32 -0
  229. package/dist/src/planner/building/returning-star.d.ts.map +1 -0
  230. package/dist/src/planner/building/returning-star.js +45 -0
  231. package/dist/src/planner/building/returning-star.js.map +1 -0
  232. package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
  233. package/dist/src/planner/building/select-aggregates.js +47 -0
  234. package/dist/src/planner/building/select-aggregates.js.map +1 -1
  235. package/dist/src/planner/building/select-compound.d.ts.map +1 -1
  236. package/dist/src/planner/building/select-compound.js +84 -11
  237. package/dist/src/planner/building/select-compound.js.map +1 -1
  238. package/dist/src/planner/building/select-context.d.ts +10 -2
  239. package/dist/src/planner/building/select-context.d.ts.map +1 -1
  240. package/dist/src/planner/building/select-context.js +7 -1
  241. package/dist/src/planner/building/select-context.js.map +1 -1
  242. package/dist/src/planner/building/select-modifiers.js +6 -0
  243. package/dist/src/planner/building/select-modifiers.js.map +1 -1
  244. package/dist/src/planner/building/select-ordinal.d.ts +18 -0
  245. package/dist/src/planner/building/select-ordinal.d.ts.map +1 -1
  246. package/dist/src/planner/building/select-ordinal.js +30 -0
  247. package/dist/src/planner/building/select-ordinal.js.map +1 -1
  248. package/dist/src/planner/building/select-projections.d.ts +8 -2
  249. package/dist/src/planner/building/select-projections.d.ts.map +1 -1
  250. package/dist/src/planner/building/select-projections.js +26 -4
  251. package/dist/src/planner/building/select-projections.js.map +1 -1
  252. package/dist/src/planner/building/select-window.d.ts.map +1 -1
  253. package/dist/src/planner/building/select-window.js +8 -5
  254. package/dist/src/planner/building/select-window.js.map +1 -1
  255. package/dist/src/planner/building/select.d.ts.map +1 -1
  256. package/dist/src/planner/building/select.js +164 -59
  257. package/dist/src/planner/building/select.js.map +1 -1
  258. package/dist/src/planner/building/set-object-tags.d.ts +7 -0
  259. package/dist/src/planner/building/set-object-tags.d.ts.map +1 -0
  260. package/dist/src/planner/building/set-object-tags.js +38 -0
  261. package/dist/src/planner/building/set-object-tags.js.map +1 -0
  262. package/dist/src/planner/building/tag-diagnostics.d.ts +27 -0
  263. package/dist/src/planner/building/tag-diagnostics.d.ts.map +1 -0
  264. package/dist/src/planner/building/tag-diagnostics.js +37 -0
  265. package/dist/src/planner/building/tag-diagnostics.js.map +1 -0
  266. package/dist/src/planner/building/update.d.ts +18 -1
  267. package/dist/src/planner/building/update.d.ts.map +1 -1
  268. package/dist/src/planner/building/update.js +134 -58
  269. package/dist/src/planner/building/update.js.map +1 -1
  270. package/dist/src/planner/building/view-mutation-builder.d.ts +15 -0
  271. package/dist/src/planner/building/view-mutation-builder.d.ts.map +1 -0
  272. package/dist/src/planner/building/view-mutation-builder.js +1158 -0
  273. package/dist/src/planner/building/view-mutation-builder.js.map +1 -0
  274. package/dist/src/planner/building/with.d.ts +11 -0
  275. package/dist/src/planner/building/with.d.ts.map +1 -1
  276. package/dist/src/planner/building/with.js +48 -10
  277. package/dist/src/planner/building/with.js.map +1 -1
  278. package/dist/src/planner/cost/index.d.ts +83 -0
  279. package/dist/src/planner/cost/index.d.ts.map +1 -1
  280. package/dist/src/planner/cost/index.js +114 -0
  281. package/dist/src/planner/cost/index.js.map +1 -1
  282. package/dist/src/planner/framework/characteristics.d.ts +38 -4
  283. package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
  284. package/dist/src/planner/framework/characteristics.js +50 -6
  285. package/dist/src/planner/framework/characteristics.js.map +1 -1
  286. package/dist/src/planner/framework/pass.d.ts.map +1 -1
  287. package/dist/src/planner/framework/pass.js +2 -1
  288. package/dist/src/planner/framework/pass.js.map +1 -1
  289. package/dist/src/planner/framework/registry.d.ts +39 -1
  290. package/dist/src/planner/framework/registry.d.ts.map +1 -1
  291. package/dist/src/planner/framework/registry.js +18 -2
  292. package/dist/src/planner/framework/registry.js.map +1 -1
  293. package/dist/src/planner/mutation/backward-body.d.ts +131 -0
  294. package/dist/src/planner/mutation/backward-body.d.ts.map +1 -0
  295. package/dist/src/planner/mutation/backward-body.js +135 -0
  296. package/dist/src/planner/mutation/backward-body.js.map +1 -0
  297. package/dist/src/planner/mutation/cte-flatten.d.ts +17 -0
  298. package/dist/src/planner/mutation/cte-flatten.d.ts.map +1 -0
  299. package/dist/src/planner/mutation/cte-flatten.js +364 -0
  300. package/dist/src/planner/mutation/cte-flatten.js.map +1 -0
  301. package/dist/src/planner/mutation/decomposition.d.ts +273 -0
  302. package/dist/src/planner/mutation/decomposition.d.ts.map +1 -0
  303. package/dist/src/planner/mutation/decomposition.js +1719 -0
  304. package/dist/src/planner/mutation/decomposition.js.map +1 -0
  305. package/dist/src/planner/mutation/lens-enforcement.d.ts +165 -0
  306. package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -0
  307. package/dist/src/planner/mutation/lens-enforcement.js +745 -0
  308. package/dist/src/planner/mutation/lens-enforcement.js.map +1 -0
  309. package/dist/src/planner/mutation/multi-source.d.ts +568 -0
  310. package/dist/src/planner/mutation/multi-source.d.ts.map +1 -0
  311. package/dist/src/planner/mutation/multi-source.js +2915 -0
  312. package/dist/src/planner/mutation/multi-source.js.map +1 -0
  313. package/dist/src/planner/mutation/mutation-diagnostic.d.ts +37 -0
  314. package/dist/src/planner/mutation/mutation-diagnostic.d.ts.map +1 -0
  315. package/dist/src/planner/mutation/mutation-diagnostic.js +24 -0
  316. package/dist/src/planner/mutation/mutation-diagnostic.js.map +1 -0
  317. package/dist/src/planner/mutation/mutation-tags.d.ts +33 -0
  318. package/dist/src/planner/mutation/mutation-tags.d.ts.map +1 -0
  319. package/dist/src/planner/mutation/mutation-tags.js +31 -0
  320. package/dist/src/planner/mutation/mutation-tags.js.map +1 -0
  321. package/dist/src/planner/mutation/propagate.d.ts +97 -0
  322. package/dist/src/planner/mutation/propagate.d.ts.map +1 -0
  323. package/dist/src/planner/mutation/propagate.js +220 -0
  324. package/dist/src/planner/mutation/propagate.js.map +1 -0
  325. package/dist/src/planner/mutation/scope-transform.d.ts +181 -0
  326. package/dist/src/planner/mutation/scope-transform.d.ts.map +1 -0
  327. package/dist/src/planner/mutation/scope-transform.js +574 -0
  328. package/dist/src/planner/mutation/scope-transform.js.map +1 -0
  329. package/dist/src/planner/mutation/set-op.d.ts +242 -0
  330. package/dist/src/planner/mutation/set-op.d.ts.map +1 -0
  331. package/dist/src/planner/mutation/set-op.js +1687 -0
  332. package/dist/src/planner/mutation/set-op.js.map +1 -0
  333. package/dist/src/planner/mutation/single-source.d.ts +261 -0
  334. package/dist/src/planner/mutation/single-source.d.ts.map +1 -0
  335. package/dist/src/planner/mutation/single-source.js +1096 -0
  336. package/dist/src/planner/mutation/single-source.js.map +1 -0
  337. package/dist/src/planner/nodes/aggregate-node.js +3 -3
  338. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  339. package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
  340. package/dist/src/planner/nodes/alias-node.js +5 -1
  341. package/dist/src/planner/nodes/alias-node.js.map +1 -1
  342. package/dist/src/planner/nodes/alter-table-node.d.ts +124 -1
  343. package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
  344. package/dist/src/planner/nodes/alter-table-node.js +27 -0
  345. package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
  346. package/dist/src/planner/nodes/analyze-node.d.ts +2 -1
  347. package/dist/src/planner/nodes/analyze-node.d.ts.map +1 -1
  348. package/dist/src/planner/nodes/analyze-node.js +18 -1
  349. package/dist/src/planner/nodes/analyze-node.js.map +1 -1
  350. package/dist/src/planner/nodes/asserted-keys-node.d.ts +43 -0
  351. package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -0
  352. package/dist/src/planner/nodes/asserted-keys-node.js +99 -0
  353. package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -0
  354. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  355. package/dist/src/planner/nodes/async-gather-node.js +33 -8
  356. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  357. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  358. package/dist/src/planner/nodes/bloom-join-node.js +2 -1
  359. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  360. package/dist/src/planner/nodes/create-view-node.d.ts +7 -2
  361. package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
  362. package/dist/src/planner/nodes/create-view-node.js +4 -1
  363. package/dist/src/planner/nodes/create-view-node.js.map +1 -1
  364. package/dist/src/planner/nodes/declarative-schema.d.ts +13 -1
  365. package/dist/src/planner/nodes/declarative-schema.d.ts.map +1 -1
  366. package/dist/src/planner/nodes/declarative-schema.js +32 -0
  367. package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
  368. package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
  369. package/dist/src/planner/nodes/distinct-node.js +2 -0
  370. package/dist/src/planner/nodes/distinct-node.js.map +1 -1
  371. package/dist/src/planner/nodes/dml-executor-node.d.ts +29 -1
  372. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  373. package/dist/src/planner/nodes/dml-executor-node.js +27 -3
  374. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  375. package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
  376. package/dist/src/planner/nodes/eager-prefetch-node.js +2 -0
  377. package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
  378. package/dist/src/planner/nodes/envelope-scan-node.d.ts +42 -0
  379. package/dist/src/planner/nodes/envelope-scan-node.d.ts.map +1 -0
  380. package/dist/src/planner/nodes/envelope-scan-node.js +62 -0
  381. package/dist/src/planner/nodes/envelope-scan-node.js.map +1 -0
  382. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
  383. package/dist/src/planner/nodes/fanout-lookup-join-node.js +11 -1
  384. package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
  385. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  386. package/dist/src/planner/nodes/filter.js +63 -13
  387. package/dist/src/planner/nodes/filter.js.map +1 -1
  388. package/dist/src/planner/nodes/join-node.d.ts +41 -1
  389. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  390. package/dist/src/planner/nodes/join-node.js +78 -8
  391. package/dist/src/planner/nodes/join-node.js.map +1 -1
  392. package/dist/src/planner/nodes/join-utils.d.ts +33 -6
  393. package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
  394. package/dist/src/planner/nodes/join-utils.js +124 -9
  395. package/dist/src/planner/nodes/join-utils.js.map +1 -1
  396. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts +104 -0
  397. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -0
  398. package/dist/src/planner/nodes/lens-auxiliary-access-node.js +91 -0
  399. package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -0
  400. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  401. package/dist/src/planner/nodes/limit-offset.js +4 -5
  402. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  403. package/dist/src/planner/nodes/materialized-view-nodes.d.ts +69 -0
  404. package/dist/src/planner/nodes/materialized-view-nodes.d.ts.map +1 -0
  405. package/dist/src/planner/nodes/materialized-view-nodes.js +111 -0
  406. package/dist/src/planner/nodes/materialized-view-nodes.js.map +1 -0
  407. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  408. package/dist/src/planner/nodes/merge-join-node.js +2 -1
  409. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  410. package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
  411. package/dist/src/planner/nodes/ordinal-slice-node.js +2 -0
  412. package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
  413. package/dist/src/planner/nodes/plan-node-type.d.ts +9 -0
  414. package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
  415. package/dist/src/planner/nodes/plan-node-type.js +9 -0
  416. package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
  417. package/dist/src/planner/nodes/plan-node.d.ts +265 -5
  418. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  419. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  420. package/dist/src/planner/nodes/pragma.d.ts +2 -1
  421. package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
  422. package/dist/src/planner/nodes/pragma.js +12 -0
  423. package/dist/src/planner/nodes/pragma.js.map +1 -1
  424. package/dist/src/planner/nodes/project-node.d.ts +14 -1
  425. package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
  426. package/dist/src/planner/nodes/project-node.js +85 -11
  427. package/dist/src/planner/nodes/project-node.js.map +1 -1
  428. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  429. package/dist/src/planner/nodes/reference.js +62 -27
  430. package/dist/src/planner/nodes/reference.js.map +1 -1
  431. package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
  432. package/dist/src/planner/nodes/retrieve-node.js +7 -0
  433. package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
  434. package/dist/src/planner/nodes/returning-node.d.ts.map +1 -1
  435. package/dist/src/planner/nodes/returning-node.js +10 -3
  436. package/dist/src/planner/nodes/returning-node.js.map +1 -1
  437. package/dist/src/planner/nodes/scalar.d.ts +20 -0
  438. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  439. package/dist/src/planner/nodes/scalar.js +71 -14
  440. package/dist/src/planner/nodes/scalar.js.map +1 -1
  441. package/dist/src/planner/nodes/set-object-tags-node.d.ts +39 -0
  442. package/dist/src/planner/nodes/set-object-tags-node.d.ts.map +1 -0
  443. package/dist/src/planner/nodes/set-object-tags-node.js +41 -0
  444. package/dist/src/planner/nodes/set-object-tags-node.js.map +1 -0
  445. package/dist/src/planner/nodes/set-operation-node.d.ts +123 -1
  446. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  447. package/dist/src/planner/nodes/set-operation-node.js +291 -18
  448. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  449. package/dist/src/planner/nodes/single-row.d.ts.map +1 -1
  450. package/dist/src/planner/nodes/single-row.js +3 -0
  451. package/dist/src/planner/nodes/single-row.js.map +1 -1
  452. package/dist/src/planner/nodes/sort.d.ts.map +1 -1
  453. package/dist/src/planner/nodes/sort.js +7 -6
  454. package/dist/src/planner/nodes/sort.js.map +1 -1
  455. package/dist/src/planner/nodes/subquery.d.ts +2 -0
  456. package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
  457. package/dist/src/planner/nodes/subquery.js +18 -2
  458. package/dist/src/planner/nodes/subquery.js.map +1 -1
  459. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  460. package/dist/src/planner/nodes/table-access-nodes.js +23 -3
  461. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  462. package/dist/src/planner/nodes/table-function-call.js +6 -0
  463. package/dist/src/planner/nodes/table-function-call.js.map +1 -1
  464. package/dist/src/planner/nodes/values-node.d.ts +1 -0
  465. package/dist/src/planner/nodes/values-node.d.ts.map +1 -1
  466. package/dist/src/planner/nodes/values-node.js +16 -6
  467. package/dist/src/planner/nodes/values-node.js.map +1 -1
  468. package/dist/src/planner/nodes/view-mutation-node.d.ts +259 -0
  469. package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -0
  470. package/dist/src/planner/nodes/view-mutation-node.js +273 -0
  471. package/dist/src/planner/nodes/view-mutation-node.js.map +1 -0
  472. package/dist/src/planner/nodes/window-function.d.ts +17 -1
  473. package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
  474. package/dist/src/planner/nodes/window-function.js +15 -1
  475. package/dist/src/planner/nodes/window-function.js.map +1 -1
  476. package/dist/src/planner/nodes/window-node.js +2 -2
  477. package/dist/src/planner/nodes/window-node.js.map +1 -1
  478. package/dist/src/planner/optimizer.d.ts.map +1 -1
  479. package/dist/src/planner/optimizer.js +372 -39
  480. package/dist/src/planner/optimizer.js.map +1 -1
  481. package/dist/src/planner/planning-context.d.ts +1 -1
  482. package/dist/src/planner/planning-context.d.ts.map +1 -1
  483. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts +70 -0
  484. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts.map +1 -0
  485. package/dist/src/planner/rules/access/lens-access-form-matcher.js +156 -0
  486. package/dist/src/planner/rules/access/lens-access-form-matcher.js.map +1 -0
  487. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts +31 -0
  488. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts.map +1 -0
  489. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js +176 -0
  490. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js.map +1 -0
  491. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  492. package/dist/src/planner/rules/access/rule-select-access-path.js +435 -37
  493. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  494. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
  495. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +9 -0
  496. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
  497. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +39 -0
  498. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -0
  499. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +616 -0
  500. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -0
  501. package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts.map +1 -1
  502. package/dist/src/planner/rules/cache/rule-scalar-cse.js +8 -1
  503. package/dist/src/planner/rules/cache/rule-scalar-cse.js.map +1 -1
  504. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +36 -0
  505. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
  506. package/dist/src/planner/rules/join/equi-pair-extractor.js +38 -1
  507. package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
  508. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
  509. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +10 -0
  510. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
  511. package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
  512. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +19 -1
  513. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  514. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +130 -0
  515. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts.map +1 -0
  516. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +206 -0
  517. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js.map +1 -0
  518. package/dist/src/planner/rules/join/rule-join-elimination.d.ts +67 -14
  519. package/dist/src/planner/rules/join/rule-join-elimination.d.ts.map +1 -1
  520. package/dist/src/planner/rules/join/rule-join-elimination.js +81 -25
  521. package/dist/src/planner/rules/join/rule-join-elimination.js.map +1 -1
  522. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts +84 -0
  523. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts.map +1 -0
  524. package/dist/src/planner/rules/join/rule-join-existence-pruning.js +138 -0
  525. package/dist/src/planner/rules/join/rule-join-existence-pruning.js.map +1 -0
  526. package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
  527. package/dist/src/planner/rules/join/rule-join-greedy-commute.js +9 -1
  528. package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
  529. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  530. package/dist/src/planner/rules/join/rule-join-physical-selection.js +12 -1
  531. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  532. package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -1
  533. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +4 -0
  534. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -1
  535. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  536. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +4 -0
  537. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  538. package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
  539. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +10 -0
  540. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
  541. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts +286 -0
  542. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts.map +1 -0
  543. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js +548 -0
  544. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js.map +1 -0
  545. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.d.ts.map +1 -1
  546. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js +9 -1
  547. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js.map +1 -1
  548. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.d.ts.map +1 -1
  549. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +7 -0
  550. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
  551. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.d.ts.map +1 -1
  552. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js +10 -1
  553. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js.map +1 -1
  554. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.d.ts.map +1 -1
  555. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js +9 -0
  556. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js.map +1 -1
  557. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts.map +1 -1
  558. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +18 -0
  559. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js.map +1 -1
  560. package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
  561. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +7 -0
  562. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
  563. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
  564. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +9 -0
  565. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  566. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -3
  567. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
  568. package/dist/src/planner/rules/retrieve/rule-projection-pruning.d.ts.map +1 -1
  569. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js +14 -0
  570. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js.map +1 -1
  571. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +1 -1
  572. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +4 -4
  573. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js.map +1 -1
  574. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
  575. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +8 -0
  576. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
  577. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
  578. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +7 -0
  579. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
  580. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  581. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +12 -0
  582. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  583. package/dist/src/planner/type-utils.d.ts +14 -0
  584. package/dist/src/planner/type-utils.d.ts.map +1 -1
  585. package/dist/src/planner/type-utils.js +66 -21
  586. package/dist/src/planner/type-utils.js.map +1 -1
  587. package/dist/src/planner/util/fd-utils.d.ts +177 -43
  588. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  589. package/dist/src/planner/util/fd-utils.js +396 -101
  590. package/dist/src/planner/util/fd-utils.js.map +1 -1
  591. package/dist/src/planner/util/ind-utils.d.ts +27 -1
  592. package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
  593. package/dist/src/planner/util/ind-utils.js +80 -6
  594. package/dist/src/planner/util/ind-utils.js.map +1 -1
  595. package/dist/src/planner/util/key-utils.d.ts.map +1 -1
  596. package/dist/src/planner/util/key-utils.js +81 -12
  597. package/dist/src/planner/util/key-utils.js.map +1 -1
  598. package/dist/src/planner/util/set-op-wrapper.d.ts +37 -0
  599. package/dist/src/planner/util/set-op-wrapper.d.ts.map +1 -0
  600. package/dist/src/planner/util/set-op-wrapper.js +82 -0
  601. package/dist/src/planner/util/set-op-wrapper.js.map +1 -0
  602. package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
  603. package/dist/src/planner/validation/plan-validator.js +1 -0
  604. package/dist/src/planner/validation/plan-validator.js.map +1 -1
  605. package/dist/src/runtime/context-helpers.d.ts +13 -1
  606. package/dist/src/runtime/context-helpers.d.ts.map +1 -1
  607. package/dist/src/runtime/context-helpers.js +7 -1
  608. package/dist/src/runtime/context-helpers.js.map +1 -1
  609. package/dist/src/runtime/delta-executor.d.ts +30 -1
  610. package/dist/src/runtime/delta-executor.d.ts.map +1 -1
  611. package/dist/src/runtime/delta-executor.js +29 -4
  612. package/dist/src/runtime/delta-executor.js.map +1 -1
  613. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  614. package/dist/src/runtime/emit/add-constraint.js +38 -5
  615. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  616. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  617. package/dist/src/runtime/emit/aggregate.js +10 -8
  618. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  619. package/dist/src/runtime/emit/alter-table.d.ts +1 -1
  620. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  621. package/dist/src/runtime/emit/alter-table.js +664 -108
  622. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  623. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  624. package/dist/src/runtime/emit/analyze.js +2 -1
  625. package/dist/src/runtime/emit/analyze.js.map +1 -1
  626. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  627. package/dist/src/runtime/emit/asof-scan.js +18 -5
  628. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  629. package/dist/src/runtime/emit/asserted-keys.d.ts +13 -0
  630. package/dist/src/runtime/emit/asserted-keys.d.ts.map +1 -0
  631. package/dist/src/runtime/emit/asserted-keys.js +13 -0
  632. package/dist/src/runtime/emit/asserted-keys.js.map +1 -0
  633. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  634. package/dist/src/runtime/emit/between.js +24 -19
  635. package/dist/src/runtime/emit/between.js.map +1 -1
  636. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  637. package/dist/src/runtime/emit/binary.js +5 -9
  638. package/dist/src/runtime/emit/binary.js.map +1 -1
  639. package/dist/src/runtime/emit/block.d.ts.map +1 -1
  640. package/dist/src/runtime/emit/block.js +11 -2
  641. package/dist/src/runtime/emit/block.js.map +1 -1
  642. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  643. package/dist/src/runtime/emit/bloom-join.js +8 -2
  644. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  645. package/dist/src/runtime/emit/constraint-check.js +15 -0
  646. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  647. package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
  648. package/dist/src/runtime/emit/create-table.js +8 -0
  649. package/dist/src/runtime/emit/create-table.js.map +1 -1
  650. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  651. package/dist/src/runtime/emit/create-view.js +16 -1
  652. package/dist/src/runtime/emit/create-view.js.map +1 -1
  653. package/dist/src/runtime/emit/dml-executor.d.ts +27 -0
  654. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  655. package/dist/src/runtime/emit/dml-executor.js +413 -193
  656. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  657. package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
  658. package/dist/src/runtime/emit/drop-table.js +10 -0
  659. package/dist/src/runtime/emit/drop-table.js.map +1 -1
  660. package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
  661. package/dist/src/runtime/emit/drop-view.js +17 -0
  662. package/dist/src/runtime/emit/drop-view.js.map +1 -1
  663. package/dist/src/runtime/emit/envelope-scan.d.ts +13 -0
  664. package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -0
  665. package/dist/src/runtime/emit/envelope-scan.js +22 -0
  666. package/dist/src/runtime/emit/envelope-scan.js.map +1 -0
  667. package/dist/src/runtime/emit/join.d.ts +10 -2
  668. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  669. package/dist/src/runtime/emit/join.js +128 -38
  670. package/dist/src/runtime/emit/join.js.map +1 -1
  671. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts +16 -0
  672. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts.map +1 -0
  673. package/dist/src/runtime/emit/lens-auxiliary-access.js +16 -0
  674. package/dist/src/runtime/emit/lens-auxiliary-access.js.map +1 -0
  675. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +640 -0
  676. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -0
  677. package/dist/src/runtime/emit/materialized-view-helpers.js +2576 -0
  678. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -0
  679. package/dist/src/runtime/emit/materialized-view.d.ts +31 -0
  680. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -0
  681. package/dist/src/runtime/emit/materialized-view.js +187 -0
  682. package/dist/src/runtime/emit/materialized-view.js.map +1 -0
  683. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  684. package/dist/src/runtime/emit/merge-join.js +15 -3
  685. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  686. package/dist/src/runtime/emit/project.d.ts.map +1 -1
  687. package/dist/src/runtime/emit/project.js +10 -5
  688. package/dist/src/runtime/emit/project.js.map +1 -1
  689. package/dist/src/runtime/emit/schema-declarative.d.ts +1 -0
  690. package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
  691. package/dist/src/runtime/emit/schema-declarative.js +101 -5
  692. package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
  693. package/dist/src/runtime/emit/set-object-tags.d.ts +16 -0
  694. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -0
  695. package/dist/src/runtime/emit/set-object-tags.js +57 -0
  696. package/dist/src/runtime/emit/set-object-tags.js.map +1 -0
  697. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  698. package/dist/src/runtime/emit/set-operation.js +140 -24
  699. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  700. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  701. package/dist/src/runtime/emit/subquery.js +110 -5
  702. package/dist/src/runtime/emit/subquery.js.map +1 -1
  703. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  704. package/dist/src/runtime/emit/unary.js +34 -6
  705. package/dist/src/runtime/emit/unary.js.map +1 -1
  706. package/dist/src/runtime/emit/view-mutation.d.ts +70 -0
  707. package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -0
  708. package/dist/src/runtime/emit/view-mutation.js +299 -0
  709. package/dist/src/runtime/emit/view-mutation.js.map +1 -0
  710. package/dist/src/runtime/emit/window.js +29 -5
  711. package/dist/src/runtime/emit/window.js.map +1 -1
  712. package/dist/src/runtime/foreign-key-actions.d.ts +66 -3
  713. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  714. package/dist/src/runtime/foreign-key-actions.js +580 -172
  715. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  716. package/dist/src/runtime/parallel-driver.d.ts +4 -1
  717. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  718. package/dist/src/runtime/parallel-driver.js +5 -1
  719. package/dist/src/runtime/parallel-driver.js.map +1 -1
  720. package/dist/src/runtime/register.d.ts.map +1 -1
  721. package/dist/src/runtime/register.js +17 -1
  722. package/dist/src/runtime/register.js.map +1 -1
  723. package/dist/src/runtime/types.d.ts +10 -0
  724. package/dist/src/runtime/types.d.ts.map +1 -1
  725. package/dist/src/runtime/types.js.map +1 -1
  726. package/dist/src/schema/basis-backfill.d.ts +63 -0
  727. package/dist/src/schema/basis-backfill.d.ts.map +1 -0
  728. package/dist/src/schema/basis-backfill.js +161 -0
  729. package/dist/src/schema/basis-backfill.js.map +1 -0
  730. package/dist/src/schema/catalog.d.ts +115 -1
  731. package/dist/src/schema/catalog.d.ts.map +1 -1
  732. package/dist/src/schema/catalog.js +249 -22
  733. package/dist/src/schema/catalog.js.map +1 -1
  734. package/dist/src/schema/change-events.d.ts +42 -1
  735. package/dist/src/schema/change-events.d.ts.map +1 -1
  736. package/dist/src/schema/change-events.js.map +1 -1
  737. package/dist/src/schema/column.d.ts +16 -0
  738. package/dist/src/schema/column.d.ts.map +1 -1
  739. package/dist/src/schema/column.js.map +1 -1
  740. package/dist/src/schema/constraint-builder.d.ts +182 -0
  741. package/dist/src/schema/constraint-builder.d.ts.map +1 -0
  742. package/dist/src/schema/constraint-builder.js +424 -0
  743. package/dist/src/schema/constraint-builder.js.map +1 -0
  744. package/dist/src/schema/ddl-generator.d.ts +86 -1
  745. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  746. package/dist/src/schema/ddl-generator.js +316 -20
  747. package/dist/src/schema/ddl-generator.js.map +1 -1
  748. package/dist/src/schema/declared-schema-manager.d.ts +51 -0
  749. package/dist/src/schema/declared-schema-manager.d.ts.map +1 -1
  750. package/dist/src/schema/declared-schema-manager.js +61 -0
  751. package/dist/src/schema/declared-schema-manager.js.map +1 -1
  752. package/dist/src/schema/derivation.d.ts +106 -0
  753. package/dist/src/schema/derivation.d.ts.map +1 -0
  754. package/dist/src/schema/derivation.js +25 -0
  755. package/dist/src/schema/derivation.js.map +1 -0
  756. package/dist/src/schema/function.d.ts +13 -0
  757. package/dist/src/schema/function.d.ts.map +1 -1
  758. package/dist/src/schema/function.js.map +1 -1
  759. package/dist/src/schema/lens-ack.d.ts +90 -0
  760. package/dist/src/schema/lens-ack.d.ts.map +1 -0
  761. package/dist/src/schema/lens-ack.js +361 -0
  762. package/dist/src/schema/lens-ack.js.map +1 -0
  763. package/dist/src/schema/lens-compiler.d.ts +62 -0
  764. package/dist/src/schema/lens-compiler.d.ts.map +1 -0
  765. package/dist/src/schema/lens-compiler.js +1594 -0
  766. package/dist/src/schema/lens-compiler.js.map +1 -0
  767. package/dist/src/schema/lens-fk-discovery.d.ts +175 -0
  768. package/dist/src/schema/lens-fk-discovery.d.ts.map +1 -0
  769. package/dist/src/schema/lens-fk-discovery.js +336 -0
  770. package/dist/src/schema/lens-fk-discovery.js.map +1 -0
  771. package/dist/src/schema/lens-prover.d.ts +336 -0
  772. package/dist/src/schema/lens-prover.d.ts.map +1 -0
  773. package/dist/src/schema/lens-prover.js +1988 -0
  774. package/dist/src/schema/lens-prover.js.map +1 -0
  775. package/dist/src/schema/lens.d.ts +254 -0
  776. package/dist/src/schema/lens.d.ts.map +1 -0
  777. package/dist/src/schema/lens.js +21 -0
  778. package/dist/src/schema/lens.js.map +1 -0
  779. package/dist/src/schema/manager.d.ts +676 -18
  780. package/dist/src/schema/manager.d.ts.map +1 -1
  781. package/dist/src/schema/manager.js +1573 -238
  782. package/dist/src/schema/manager.js.map +1 -1
  783. package/dist/src/schema/mapping-advertisement-tags.d.ts +39 -0
  784. package/dist/src/schema/mapping-advertisement-tags.d.ts.map +1 -0
  785. package/dist/src/schema/mapping-advertisement-tags.js +216 -0
  786. package/dist/src/schema/mapping-advertisement-tags.js.map +1 -0
  787. package/dist/src/schema/rename-rewriter.d.ts +45 -4
  788. package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
  789. package/dist/src/schema/rename-rewriter.js +412 -19
  790. package/dist/src/schema/rename-rewriter.js.map +1 -1
  791. package/dist/src/schema/reserved-tags-policy.d.ts +32 -0
  792. package/dist/src/schema/reserved-tags-policy.d.ts.map +1 -0
  793. package/dist/src/schema/reserved-tags-policy.js +34 -0
  794. package/dist/src/schema/reserved-tags-policy.js.map +1 -0
  795. package/dist/src/schema/reserved-tags.d.ts +170 -0
  796. package/dist/src/schema/reserved-tags.d.ts.map +1 -0
  797. package/dist/src/schema/reserved-tags.js +507 -0
  798. package/dist/src/schema/reserved-tags.js.map +1 -0
  799. package/dist/src/schema/schema-differ.d.ts +158 -2
  800. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  801. package/dist/src/schema/schema-differ.js +1460 -78
  802. package/dist/src/schema/schema-differ.js.map +1 -1
  803. package/dist/src/schema/schema-hasher.d.ts +8 -3
  804. package/dist/src/schema/schema-hasher.d.ts.map +1 -1
  805. package/dist/src/schema/schema-hasher.js +22 -2
  806. package/dist/src/schema/schema-hasher.js.map +1 -1
  807. package/dist/src/schema/schema.d.ts +25 -1
  808. package/dist/src/schema/schema.d.ts.map +1 -1
  809. package/dist/src/schema/schema.js +36 -2
  810. package/dist/src/schema/schema.js.map +1 -1
  811. package/dist/src/schema/table.d.ts +259 -10
  812. package/dist/src/schema/table.d.ts.map +1 -1
  813. package/dist/src/schema/table.js +309 -26
  814. package/dist/src/schema/table.js.map +1 -1
  815. package/dist/src/schema/unique-enforcement.d.ts +78 -0
  816. package/dist/src/schema/unique-enforcement.d.ts.map +1 -0
  817. package/dist/src/schema/unique-enforcement.js +93 -0
  818. package/dist/src/schema/unique-enforcement.js.map +1 -0
  819. package/dist/src/schema/view.d.ts +83 -2
  820. package/dist/src/schema/view.d.ts.map +1 -1
  821. package/dist/src/schema/view.js +67 -1
  822. package/dist/src/schema/view.js.map +1 -1
  823. package/dist/src/schema/window-function.d.ts +9 -1
  824. package/dist/src/schema/window-function.d.ts.map +1 -1
  825. package/dist/src/schema/window-function.js.map +1 -1
  826. package/dist/src/util/comparison.d.ts +24 -0
  827. package/dist/src/util/comparison.d.ts.map +1 -1
  828. package/dist/src/util/comparison.js +34 -0
  829. package/dist/src/util/comparison.js.map +1 -1
  830. package/dist/src/util/mutation-statement.d.ts.map +1 -1
  831. package/dist/src/util/mutation-statement.js +4 -1
  832. package/dist/src/util/mutation-statement.js.map +1 -1
  833. package/dist/src/util/serialization.d.ts +9 -0
  834. package/dist/src/util/serialization.d.ts.map +1 -1
  835. package/dist/src/util/serialization.js +26 -0
  836. package/dist/src/util/serialization.js.map +1 -1
  837. package/dist/src/vtab/backing-host.d.ts +286 -0
  838. package/dist/src/vtab/backing-host.d.ts.map +1 -0
  839. package/dist/src/vtab/backing-host.js +118 -0
  840. package/dist/src/vtab/backing-host.js.map +1 -0
  841. package/dist/src/vtab/best-access-plan.d.ts +21 -0
  842. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  843. package/dist/src/vtab/best-access-plan.js.map +1 -1
  844. package/dist/src/vtab/capabilities.d.ts +5 -5
  845. package/dist/src/vtab/capabilities.d.ts.map +1 -1
  846. package/dist/src/vtab/mapping-advertisement.d.ts +163 -0
  847. package/dist/src/vtab/mapping-advertisement.d.ts.map +1 -0
  848. package/dist/src/vtab/mapping-advertisement.js +2 -0
  849. package/dist/src/vtab/mapping-advertisement.js.map +1 -0
  850. package/dist/src/vtab/memory/index.d.ts +64 -4
  851. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  852. package/dist/src/vtab/memory/index.js +119 -12
  853. package/dist/src/vtab/memory/index.js.map +1 -1
  854. package/dist/src/vtab/memory/layer/base.d.ts +38 -1
  855. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  856. package/dist/src/vtab/memory/layer/base.js +112 -24
  857. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  858. package/dist/src/vtab/memory/layer/manager.d.ts +291 -4
  859. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  860. package/dist/src/vtab/memory/layer/manager.js +1050 -91
  861. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  862. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  863. package/dist/src/vtab/memory/layer/plan-filter.js +35 -6
  864. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  865. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  866. package/dist/src/vtab/memory/layer/scan-layer.js +66 -14
  867. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  868. package/dist/src/vtab/memory/layer/scan-plan.d.ts +14 -0
  869. package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
  870. package/dist/src/vtab/memory/layer/scan-plan.js +27 -4
  871. package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
  872. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  873. package/dist/src/vtab/memory/layer/transaction.js +5 -1
  874. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  875. package/dist/src/vtab/memory/module.d.ts +17 -0
  876. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  877. package/dist/src/vtab/memory/module.js +82 -3
  878. package/dist/src/vtab/memory/module.js.map +1 -1
  879. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  880. package/dist/src/vtab/memory/table.js +15 -5
  881. package/dist/src/vtab/memory/table.js.map +1 -1
  882. package/dist/src/vtab/memory/types.d.ts +20 -2
  883. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  884. package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
  885. package/dist/src/vtab/memory/utils/predicate.js +46 -24
  886. package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
  887. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts +31 -0
  888. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -0
  889. package/dist/src/vtab/memory/utils/primary-key-encode.js +101 -0
  890. package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -0
  891. package/dist/src/vtab/memory/utils/primary-key.d.ts +8 -0
  892. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  893. package/dist/src/vtab/memory/utils/primary-key.js +12 -5
  894. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  895. package/dist/src/vtab/module.d.ts +203 -4
  896. package/dist/src/vtab/module.d.ts.map +1 -1
  897. package/dist/src/vtab/table.d.ts +9 -0
  898. package/dist/src/vtab/table.d.ts.map +1 -1
  899. package/dist/src/vtab/table.js.map +1 -1
  900. package/package.json +6 -5
@@ -1,29 +1,55 @@
1
- import { createTableToString, createViewToString, createIndexToString, createAssertionToString, columnDefToString, quoteIdentifier, expressionToString } from '../emit/ast-stringify.js';
1
+ import { createTableToString, createViewToString, createMaterializedViewToString, createIndexToString, createAssertionToString, columnDefToString, quoteIdentifier, expressionToString, tagsBodyToString, tableConstraintsToString, constraintBodyToCanonicalString, createIndexBodyToCanonicalString, indexedColumnBareName, viewDefinitionToCanonicalString, astToString } from '../emit/ast-stringify.js';
2
+ import { computeBodyHash, normalizeBackingModuleName, canonicalBackingModuleArgs } from './view.js';
2
3
  import { QuereusError } from '../common/errors.js';
3
4
  import { StatusCode } from '../common/types.js';
4
5
  import { createLogger } from '../common/logger.js';
6
+ import { validateReservedTags } from './reserved-tags.js';
7
+ import { raiseReservedTagDiagnostics } from './reserved-tags-policy.js';
8
+ import { renameColumnInAst, renameColumnInCheckExpression, renameTableInAst } from './rename-rewriter.js';
9
+ import { cloneExpr, cloneQueryExpr } from '../planner/mutation/scope-transform.js';
10
+ import { normalizeCollationName } from '../util/comparison.js';
11
+ import { inferType } from '../types/registry.js';
12
+ import { resolveDefaultCollation } from './table.js';
5
13
  const log = createLogger('schema:differ');
6
14
  const warnLog = log.extend('warn');
7
15
  /** Reserved tag namespace prefix used for differ-recognized hints. */
8
16
  const QUEREUS_TAG_PREFIX = 'quereus.';
9
- /** Recognized tag keys under the `quereus.*` namespace. */
10
- const KNOWN_QUEREUS_KEYS = new Set(['quereus.id', 'quereus.previous_name']);
11
17
  /**
12
18
  * Computes the difference between declared schema and actual catalog
13
19
  */
14
- export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow') {
20
+ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow',
21
+ /**
22
+ * Session `default_collation` used to resolve an omitted COLLATE on the *declared*
23
+ * side, matching how the CREATE path resolves it for a fresh `apply`. Defaults to
24
+ * `'BINARY'` so direct callers (and the existing test suite, which runs under the
25
+ * BINARY session default) keep byte-for-byte identical diffs. The emitters thread
26
+ * the live `default_collation` session option.
27
+ */
28
+ defaultCollation = 'BINARY') {
15
29
  const diff = {
16
30
  tablesToCreate: [],
17
31
  tablesToDrop: [],
18
32
  tablesToAlter: [],
33
+ maintainedModuleMigrations: [],
19
34
  viewsToCreate: [],
20
35
  viewsToDrop: [],
21
36
  indexesToCreate: [],
22
37
  indexesToDrop: [],
23
38
  assertionsToCreate: [],
24
39
  assertionsToDrop: [],
40
+ viewTagsChanges: [],
41
+ indexTagsChanges: [],
25
42
  renames: [],
43
+ lensToAttach: [],
44
+ lensToDetach: [],
26
45
  };
46
+ // Logical schema: the per-table diff is attach/detach-lens, never
47
+ // create/drop-table. A logical schema's actual catalog views ARE its lens
48
+ // bodies (a logical schema has no user views), so compare declared logical
49
+ // tables against the registered views. Basis storage is untouched.
50
+ if (declaredSchema.isLogical) {
51
+ return computeLogicalSchemaDiff(declaredSchema, actualCatalog, diff);
52
+ }
27
53
  const targetSchemaName = actualCatalog.schemaName;
28
54
  // Extract schema-level default module settings
29
55
  const defaultVtabModule = declaredSchema.using?.defaultVtabModule;
@@ -31,38 +57,111 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
31
57
  // Build maps of declared items
32
58
  const declaredTables = new Map();
33
59
  const declaredViews = new Map();
60
+ const declaredMaterializedViews = new Map();
34
61
  const declaredIndexes = new Map();
35
62
  const declaredAssertions = new Map();
63
+ // Reserved-tag shape/site validation flows through the SAME typed registry
64
+ // (`validateReservedTags`) as the lens-compile / mutation / advertisement
65
+ // paths — there is no second differ-local allow-list. Severity is unified:
66
+ // an unknown / mis-sited / malformed `quereus.*` key is a hard error here too
67
+ // (Decision 2 of the ticket), so a physical-schema typo fails `apply`/`diff`
68
+ // loudly instead of silently soft-warning. We accumulate every declared
69
+ // object's diagnostics across the whole schema, then raise once — BEFORE the
70
+ // throw-y rename resolution below, so a tag typo surfaces deterministically
71
+ // rather than being masked by a rename conflict. Sites per Decision 3:
72
+ // table → physical-table (also the basis-table/advertisement position),
73
+ // column → physical-column, view / materialized view → view-ddl,
74
+ // index → physical-index, table constraint (named or not) → physical-constraint.
75
+ // Assertions carry no `tags` field (no site). The rename hints
76
+ // (quereus.id / quereus.previous_name) are first-class specs valid at each of
77
+ // these physical sites; an MV's hint validates (over-permissive: the differ
78
+ // supports no MV rename and simply ignores it — harmless, see Decision 1).
79
+ const tagDiagnostics = [];
36
80
  for (const item of declaredSchema.items) {
37
81
  switch (item.type) {
38
82
  case 'declaredTable':
39
83
  declaredTables.set(item.tableStmt.table.name.toLowerCase(), item);
40
- warnUnknownQuereusKeys(item.tableStmt.tags, 'table', item.tableStmt.table.name);
84
+ tagDiagnostics.push(...validateReservedTags(item.tableStmt.tags, 'physical-table'));
41
85
  for (const col of item.tableStmt.columns) {
42
- warnUnknownQuereusKeys(col.tags, 'column', `${item.tableStmt.table.name}.${col.name}`);
86
+ tagDiagnostics.push(...validateReservedTags(col.tags, 'physical-column'));
43
87
  }
44
88
  for (const c of item.tableStmt.constraints ?? []) {
45
- if (c.name)
46
- warnUnknownQuereusKeys(c.tags, 'constraint', c.name);
89
+ // Validate every table constraint's tags, named or not. A table-level
90
+ // constraint consumes a trailing `WITH TAGS` unconditionally (the
91
+ // parser only defers it to the column for *unnamed inline column*
92
+ // constraints), so an unnamed table constraint CAN carry a reserved
93
+ // tag — gating validation on `c.name` would leave a typo there as a
94
+ // silent no-op, the exact escape the unified hard-error posture
95
+ // exists to close. Rename detection still keys off named constraints
96
+ // only; this is validation-only and harmless on unnamed ones.
97
+ tagDiagnostics.push(...validateReservedTags(c.tags, 'physical-constraint'));
98
+ }
99
+ // Inline *named* column constraints carry their own trailing `WITH TAGS`
100
+ // on `cc.tags` at the SAME physical-constraint site as a table-level
101
+ // constraint (the parser lifts a trailing tag onto the constraint only
102
+ // when it is named; an unnamed inline constraint defers its tags to the
103
+ // column, so `cc.tags` is undefined there and validateReservedTags returns
104
+ // [] — a harmless no-op, no `cc.name` guard needed). Validate regardless of
105
+ // constraint *kind* (validation is independent of the lifecycle lift that
106
+ // handles only check/unique/fk) so a typo'd or mis-sited reserved key on
107
+ // e.g. `qty integer constraint chk check (qty>0) with tags (...)` fails
108
+ // here too. Appended LAST (after the table-level constraint loop) so the
109
+ // accumulated diagnostic order is identical to the direct CREATE path:
110
+ // table → columns → table-constraints → column-constraints.
111
+ for (const col of item.tableStmt.columns) {
112
+ for (const cc of col.constraints ?? []) {
113
+ tagDiagnostics.push(...validateReservedTags(cc.tags, 'physical-constraint'));
114
+ }
47
115
  }
48
116
  break;
49
117
  case 'declaredView':
50
118
  declaredViews.set(item.viewStmt.view.name.toLowerCase(), item);
51
- warnUnknownQuereusKeys(item.viewStmt.tags, 'view', item.viewStmt.view.name);
119
+ tagDiagnostics.push(...validateReservedTags(item.viewStmt.tags, 'view-ddl'));
120
+ break;
121
+ case 'declaredMaterializedView':
122
+ declaredMaterializedViews.set(item.viewStmt.view.name.toLowerCase(), item);
123
+ tagDiagnostics.push(...validateReservedTags(item.viewStmt.tags, 'view-ddl'));
52
124
  break;
53
125
  case 'declaredIndex':
54
126
  declaredIndexes.set(item.indexStmt.index.name.toLowerCase(), item);
55
- warnUnknownQuereusKeys(item.indexStmt.tags, 'index', item.indexStmt.index.name);
127
+ tagDiagnostics.push(...validateReservedTags(item.indexStmt.tags, 'physical-index'));
56
128
  break;
57
129
  case 'declaredAssertion':
58
130
  declaredAssertions.set(item.assertionStmt.name.toLowerCase(), item);
59
131
  break;
60
132
  }
61
133
  }
134
+ raiseReservedTagDiagnostics(tagDiagnostics, {
135
+ log: (d) => warnLog('reserved tag advisory (%s) on %s: %s', d.reason, d.site, d.message),
136
+ });
137
+ // Normalize every declared `materialized view` into the TABLE category: a
138
+ // declared table whose `maintained` clause carries the body (no declared
139
+ // columns — the body owns the shape). This is what dissolves the standalone MV
140
+ // comparison: a maintained table is now compared per name against a declared
141
+ // table or maintained clause in ONE category, so a table↔maintained transition
142
+ // becomes an attach/detach alter, never a cross-category drop+create. The
143
+ // original `declaredMaterializedViews` map is retained for the CREATE branch
144
+ // (a fresh maintained table renders the `create materialized view` sugar, since
145
+ // a column-less `create table … maintained as` has no parseable DDL).
146
+ for (const [name, declaredMv] of declaredMaterializedViews) {
147
+ if (declaredTables.has(name)) {
148
+ throw new QuereusError(`'${name}' is declared as both a table and a materialized view`, StatusCode.ERROR);
149
+ }
150
+ declaredTables.set(name, materializedViewToDeclaredTable(declaredMv));
151
+ }
62
152
  // Build maps of actual items
63
153
  const actualTables = new Map(actualCatalog.tables.map(t => [t.name.toLowerCase(), t]));
64
154
  const actualViews = new Map(actualCatalog.views.map(v => [v.name.toLowerCase(), v]));
65
- const actualIndexes = new Map(actualCatalog.indexes.map(i => [i.name.toLowerCase(), i]));
155
+ // Exclude *exposed implicit covering indexes* (`CatalogIndex.implicit` the
156
+ // secondary BTree backing a UNIQUE constraint tagged
157
+ // `quereus.expose_implicit_index`). The catalog surfaces them for introspection
158
+ // (`schema()` / `index_info()`), but their lifecycle belongs to the originating
159
+ // UNIQUE constraint (the named-constraint diff path), NOT to `CREATE/DROP INDEX`.
160
+ // Filtering here keeps them out of ALL three downstream index consumers in one
161
+ // place — rename resolution, the create/body loop, and the orphan-drop loop — so a
162
+ // converged schema with an exposed implicit index diffs empty (no phantom
163
+ // `DROP INDEX IF EXISTS`). See `catalog.ts` `CatalogIndex.implicit`.
164
+ const actualIndexes = new Map(actualCatalog.indexes.filter(i => !i.implicit).map(i => [i.name.toLowerCase(), i]));
66
165
  // Resolve renames per-kind. Each call returns:
67
166
  // - rename ops (oldName -> newName)
68
167
  // - matched pairs (declaredKey -> actual): for the alter-diff loop later
@@ -101,13 +200,99 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
101
200
  policy,
102
201
  });
103
202
  diff.renames.push(...indexRenames.renames);
104
- // Tables: creates / alters
203
+ // Pre-pass: resolve every name-matched declared table's column renames, keyed by
204
+ // declared (new) table name (lowercased). This gives the per-table alter loop
205
+ // cross-table visibility of a *parent* table's column renames, so the FK branch
206
+ // of `reconciledDeclaredBody` can inverse-rename an FK's referenced PARENT column
207
+ // (its `foreignKey.table` carries the parent's declared name at diff time — the
208
+ // same lookup key). A self-referential FK falls out for free: the parent is the
209
+ // current table, so `map.get(currentTable)` matches `diff.columnsToRename`. Pure
210
+ // creates (no matched actual) contribute nothing. NOTE: this re-resolves the
211
+ // current table's renames a second time (once here, once in its own
212
+ // `computeTableAlterDiff`); see `resolveColumnRenames` for why that's accepted.
213
+ const columnRenamesByTable = new Map();
214
+ for (const [name, declaredTable] of declaredTables) {
215
+ const matchedActual = tableRenames.pairs.get(name);
216
+ if (!matchedActual)
217
+ continue;
218
+ const renames = resolveColumnRenames(declaredTable, matchedActual, policy).renames;
219
+ if (renames.length > 0) {
220
+ columnRenamesByTable.set(name, renames.map(r => ({ oldName: r.oldName, newName: r.newName })));
221
+ }
222
+ }
223
+ // Declared-side column-existence resolver for the scope-aware seeded column
224
+ // rewrites in the reconcilers (the optional `ResolveColumnInSource` arg of
225
+ // `renameColumnInCheckExpression`). The forward rename propagation passes a
226
+ // live schemaManager lookup (see `rewriteTableForColumnRename` in
227
+ // runtime/emit/alter-table.ts); the diff side has no live catalog for the
228
+ // post-rename world, so it answers from the DECLARED column sets instead:
229
+ // the inverse walk's match target (`oldCol`) is the rename's NEW column
230
+ // name, and the question being answered is "in the declared world, does
231
+ // this inner FROM source expose that name (so the unqualified ref binds
232
+ // there, not to the owning seed)?". The walk's `realSources` carry OLD
233
+ // table names when the inverse table-rename pass has pre-normalized
234
+ // qualifiers (and DECLARED names when it hasn't — `columnReconciledViewStmt`),
235
+ // hence the old→new table-name mapping before the declared lookup: an
236
+ // already-declared name simply misses the rename find and passes through.
237
+ // Cross-schema sources answer false (the catalog is single-schema) —
238
+ // conservative where the forward path's live lookup could say yes; worst
239
+ // case a benign drop+recreate.
240
+ const targetSchemaLower = targetSchemaName.toLowerCase();
241
+ const resolveDeclaredColumn = (schema, table, column) => {
242
+ if (schema !== targetSchemaLower)
243
+ return false;
244
+ const declaredName = tableRenames.renames.find(r => r.oldName.toLowerCase() === table)?.newName.toLowerCase() ?? table;
245
+ const dt = declaredTables.get(declaredName);
246
+ return dt?.tableStmt.columns.some(c => c.name.toLowerCase() === column) ?? false;
247
+ };
248
+ // Tables: creates / alters. `dropSet` accumulates the destructive
249
+ // backing-module moves here (the live incarnation must be dropped) AND the
250
+ // orphan drops below — both flow through `orderDropsByFKDependency` together.
251
+ // `maintainedModuleRecreates` counts those module-move drop+create pairs so the
252
+ // require-hint guard can exclude them (a deliberate recreate of a matched
253
+ // object, not an ambiguous unhinted rename — mirroring viewRecreates /
254
+ // indexRecreates).
255
+ const dropSet = new Set();
256
+ let maintainedModuleRecreates = 0;
105
257
  for (const [name, declaredTable] of declaredTables) {
106
258
  const tableStmt = declaredTable.tableStmt;
107
259
  const matchedActual = tableRenames.pairs.get(name);
108
260
  if (matchedActual) {
109
261
  // Either a rename match or a name-based match — compute alter diff against the matched actual.
110
- const alterDiff = computeTableAlterDiff(declaredTable, matchedActual, policy);
262
+ // Thread the table renames (all `kind: 'table'` from this resolver), the
263
+ // schema name, and the cross-table column-rename map so the constraint body
264
+ // comparison can reconcile a renamed local column / FK-parent-table /
265
+ // FK-referenced-parent-column against the actual (pre-rename) catalog body.
266
+ const alterDiff = computeTableAlterDiff(declaredTable, matchedActual, policy, tableRenames.renames, targetSchemaName, columnRenamesByTable, resolveDeclaredColumn, defaultCollation);
267
+ if (alterDiff.maintainedModuleMigration) {
268
+ // Destructive backing-module move: drop the live incarnation (via the
269
+ // shared drop ordering) and recreate into the newly declared module
270
+ // (re-materializing the body). The recreate subsumes any concurrent body /
271
+ // tag / shape op, so the alter diff is SUPPRESSED — no `tablesToAlter`
272
+ // entry for this table. Gated at apply (allow_destructive); surfaced
273
+ // unconditionally by `diff schema`.
274
+ //
275
+ // Rename-coincident case: when the same apply BOTH renames this
276
+ // maintained table (hinted match, `matchedActual.name !== name`) AND moves
277
+ // its backing module, the table RENAME op is preserved in `diff.renames`
278
+ // (dependents over the old name retarget via the ALTER … RENAME primitive —
279
+ // see reconciledDeclaredViewDefinition). At apply that rename runs FIRST,
280
+ // moving the live incarnation to the NEW declared name, so the drop must
281
+ // target the NEW name `name` — dropping the old `matchedActual.name` would
282
+ // no-op (it was just renamed away) and the recreate, which renders under
283
+ // `name`, would then collide ("already exists"). For a plain name match the
284
+ // two names are identical.
285
+ const dropName = matchedActual.name.toLowerCase() !== name ? name : matchedActual.name.toLowerCase();
286
+ dropSet.add(dropName);
287
+ diff.tablesToCreate.push(renderFreshTableCreate(name, tableStmt, declaredMaterializedViews, targetSchemaName, defaultVtabModule, defaultVtabArgs));
288
+ diff.maintainedModuleMigrations.push({
289
+ name: tableStmt.table.name,
290
+ fromModule: alterDiff.maintainedModuleMigration.fromModule,
291
+ toModule: alterDiff.maintainedModuleMigration.toModule,
292
+ });
293
+ maintainedModuleRecreates++;
294
+ continue;
295
+ }
111
296
  // If this was a rename, set the alter target to the new name (post-rename)
112
297
  if (matchedActual.name.toLowerCase() !== name) {
113
298
  alterDiff.tableName = tableStmt.table.name;
@@ -117,17 +302,22 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
117
302
  || alterDiff.columnsToAlter.length > 0
118
303
  || alterDiff.columnsToRename.length > 0
119
304
  || (alterDiff.constraintsToRename?.length ?? 0) > 0
120
- || alterDiff.primaryKeyChange) {
305
+ || (alterDiff.constraintsToDrop?.length ?? 0) > 0
306
+ || (alterDiff.constraintsToAdd?.length ?? 0) > 0
307
+ || alterDiff.primaryKeyChange
308
+ || alterDiff.tableTagsChange !== undefined
309
+ || (alterDiff.constraintTagsChanges?.length ?? 0) > 0
310
+ || alterDiff.setMaintained !== undefined
311
+ || alterDiff.dropMaintained === true) {
121
312
  diff.tablesToAlter.push(alterDiff);
122
313
  }
123
314
  }
124
315
  else {
125
- const effectiveStmt = applyTableDefaults(tableStmt, targetSchemaName, defaultVtabModule, defaultVtabArgs);
126
- diff.tablesToCreate.push(createTableToString(effectiveStmt));
316
+ diff.tablesToCreate.push(renderFreshTableCreate(name, tableStmt, declaredMaterializedViews, targetSchemaName, defaultVtabModule, defaultVtabArgs));
127
317
  }
128
318
  }
129
- // Tables: drops (skip those consumed by a rename)
130
- const dropSet = new Set();
319
+ // Tables: drops (skip those consumed by a rename). The module-move drops added
320
+ // above stay in `dropSet`; the orphan drops join them here.
131
321
  for (const [name] of actualTables) {
132
322
  if (tableRenames.consumedActuals.has(name))
133
323
  continue;
@@ -135,10 +325,64 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
135
325
  dropSet.add(name);
136
326
  }
137
327
  diff.tablesToDrop = orderDropsByFKDependency(dropSet, actualTables);
138
- // Views: creates / drops
328
+ // Views: creates / drops / definition-change recreates / hinted-rename
329
+ // recreates / in-place tag changes.
330
+ // A matched view (name- OR rename-matched) whose canonical definition drifted
331
+ // (explicit column list, body, or the `insert defaults` clause — see
332
+ // `viewDefinitionToCanonicalString`) drops+recreates: a plain view is
333
+ // data-less, so the recreate is free, and it carries the declared tags — so a
334
+ // definition change SUPPRESSES any separate SET TAGS (the same mutual
335
+ // exclusion the MV/index paths use). The declared definition is compared raw
336
+ // first; on mismatch a rename-RECONCILED render (every in-diff table/column
337
+ // rename inverse-applied NEW→OLD — see `reconciledDeclaredViewDefinition`)
338
+ // re-compares, so a dependent view over a source renamed in this same diff
339
+ // does not churn a spurious recreate. That reconciliation is
340
+ // correctness-critical, not just churn: `generateMigrationDDL` emits view
341
+ // creates BEFORE the table-alter block where RENAME COLUMN lives, and CREATE
342
+ // VIEW plans its body at create time — an unreconciled recreate naming the
343
+ // NEW column would fail at apply. A RENAME-matched view (hinted via
344
+ // quereus.id / quereus.previous_name) resolves to drop(actual old name) +
345
+ // create(declared new name) whether or not its definition changed — its
346
+ // `kind: 'view'` rename op is metadata only (no ALTER VIEW … RENAME TO
347
+ // primitive), so the convergence DDL must come from these buckets. A
348
+ // definition-UNCHANGED hinted rename renders the recreate with the in-diff
349
+ // COLUMN renames inverse-applied while keeping declared TABLE names (table
350
+ // renames run before creates; column renames run after — see
351
+ // `columnReconciledViewStmt`); the recreate carries the declared tags, so a
352
+ // rename + tag drift converges through it and the in-place SET TAGS branch
353
+ // below never double-emits (renames `continue` past it). A pure name match
354
+ // whose definition is unchanged but tags drifted still takes the in-place
355
+ // `ALTER VIEW … SET TAGS`.
356
+ let viewRecreates = 0; // deliberate drop+create pairs, excluded from the require-hint counts
139
357
  for (const [name, declaredView] of declaredViews) {
140
- if (!viewRenames.pairs.has(name)) {
358
+ const matchedActual = viewRenames.pairs.get(name);
359
+ if (!matchedActual) {
141
360
  diff.viewsToCreate.push(createViewToString(declaredView.viewStmt));
361
+ continue;
362
+ }
363
+ const stmt = declaredView.viewStmt;
364
+ // The body string carries any trailing `with defaults (…)` clause, so the
365
+ // canonical definition (and its rename-reconciled form) covers defaults drift.
366
+ let definitionDrifted = viewDefinitionToCanonicalString(stmt.columns, stmt.select) !== matchedActual.definition;
367
+ if (definitionDrifted && (tableRenames.renames.length > 0 || columnRenamesByTable.size > 0)) {
368
+ definitionDrifted = reconciledDeclaredViewDefinition(stmt.columns, stmt.select, tableRenames.renames, columnRenamesByTable, targetSchemaName, resolveDeclaredColumn) !== matchedActual.definition;
369
+ }
370
+ if (definitionDrifted) {
371
+ diff.viewsToDrop.push(matchedActual.name);
372
+ diff.viewsToCreate.push(createViewToString(stmt));
373
+ viewRecreates++;
374
+ continue;
375
+ }
376
+ if (matchedActual.name.toLowerCase() !== name) {
377
+ // Hinted rename, definition unchanged → drop(old) + recreate(declared),
378
+ // rendered with in-diff column renames inverse-applied (NEW→OLD).
379
+ diff.viewsToDrop.push(matchedActual.name);
380
+ diff.viewsToCreate.push(createViewToString(columnReconciledViewStmt(stmt, columnRenamesByTable, targetSchemaName, resolveDeclaredColumn)));
381
+ viewRecreates++;
382
+ continue;
383
+ }
384
+ if (tagsDrifted(stmt.tags, matchedActual.tags)) {
385
+ diff.viewTagsChanges.push({ name: stmt.view.name, tags: desiredTagSet(stmt.tags) });
142
386
  }
143
387
  }
144
388
  for (const [name] of actualViews) {
@@ -147,11 +391,81 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
147
391
  if (!declaredViews.has(name))
148
392
  diff.viewsToDrop.push(name);
149
393
  }
150
- // Indexes: creates / drops
394
+ // (Maintained tables are no longer compared here. They are normalized into the
395
+ // table category above, so the unified table loop recognizes a body change as a
396
+ // re-attach (`set maintained as`), a table↔maintained transition as an
397
+ // attach/detach, and an undeclared live maintained table as a `drop table` —
398
+ // see `computeTableAlterDiff` / `applyMaintainedTransition`. A backing-module
399
+ // change on a both-maintained name-match IS detected (see `backingModuleDrifted`)
400
+ // and routed to a destructive drop+recreate in the table loop above —
401
+ // incarnation-minting, so gated at apply on `allow_destructive`.)
402
+ // Indexes: creates / drops / body-change recreates / hinted-rename recreates /
403
+ // in-place tag changes.
404
+ // A name-matched index whose canonical body drifted (UNIQUE-ness, column
405
+ // set/order/direction, partial WHERE) drops+recreates — the same drop+recreate
406
+ // shape MVs use, since an index has no in-place "redefine" primitive. The
407
+ // recreate carries the declared tags, so a body change SUPPRESSES any separate
408
+ // SET TAGS for that index (mutually exclusive per object, mirroring the MV
409
+ // precedence above). A RENAME-matched index (hinted) resolves to drop(actual
410
+ // old name) + create(declared new name) whether or not its body changed — the
411
+ // `kind: 'index'` rename op is metadata only (no ALTER INDEX … RENAME TO
412
+ // primitive), so the convergence DDL must come from these buckets; the
413
+ // rebuild cost on a pure rename is the documented tradeoff. A body-UNCHANGED
414
+ // hinted rename renders the recreate with the in-diff COLUMN renames
415
+ // inverse-applied while keeping declared TABLE names (see
416
+ // `columnReconciledIndexStmt`); the recreate carries the declared tags, so the
417
+ // in-place SET TAGS branch below never double-emits (renames `continue` past
418
+ // it). A pure name match whose body is unchanged but tags drifted still takes
419
+ // the in-place `ALTER INDEX … SET TAGS`.
420
+ let indexRecreates = 0; // deliberate drop+create pairs, excluded from the require-hint counts
151
421
  for (const [name, declaredIndex] of declaredIndexes) {
152
- if (!indexRenames.pairs.has(name)) {
422
+ const matchedActual = indexRenames.pairs.get(name);
423
+ if (!matchedActual) {
424
+ const effectiveStmt = applyIndexDefaults(declaredIndex.indexStmt, targetSchemaName);
425
+ diff.indexesToCreate.push(createIndexToString(effectiveStmt));
426
+ continue;
427
+ }
428
+ // Body comparison (canonical: name / tags excluded; per-column collation
429
+ // included via both-sides pre-resolution). Schema qualification does not affect
430
+ // the body render, so compare the raw declared stmt. The declared side resolves
431
+ // each column's effective collation against the matched declared table (so an
432
+ // inherited/BINARY collation that is unchanged does not churn, while a real
433
+ // collation change recreates), then inverse-applies the index table's in-diff
434
+ // column renames so a same-named index over a column renamed in this same diff
435
+ // matches the actual (pre-rename) body instead of churning a spurious
436
+ // drop+recreate (the column rename rides the table-alter channel). The rename
437
+ // lookup is keyed by the index's *declared* (post-rename) table name — exactly
438
+ // how `columnRenamesByTable` is keyed — so a table renamed in the same diff still
439
+ // resolves its column renames. ALL in-diff table renames are threaded too, so
440
+ // both a table-qualified self-reference and a cross-table reference in the
441
+ // partial WHERE predicate reconcile alongside the column renames (the own-table
442
+ // rename's remaining special role — seeding the column rewrites — is resolved
443
+ // inside declaredIndexCanonicalBody). The drop targets the actual
444
+ // (pre-rename) name and the recreate carries the declared (post-rename) name, so
445
+ // a genuine body change on a rename-matched index resolves to a correct
446
+ // drop+recreate that supersedes the no-op rename op.
447
+ const declaredTableForIndex = declaredTables.get(declaredIndex.indexStmt.table.name.toLowerCase());
448
+ const indexColRenames = columnRenamesByTable.get(declaredIndex.indexStmt.table.name.toLowerCase()) ?? [];
449
+ const declaredBody = declaredIndexCanonicalBody(declaredIndex.indexStmt, declaredTableForIndex, indexColRenames, tableRenames.renames, targetSchemaName, defaultCollation);
450
+ if (declaredBody !== matchedActual.definition) {
451
+ diff.indexesToDrop.push(matchedActual.name);
153
452
  const effectiveStmt = applyIndexDefaults(declaredIndex.indexStmt, targetSchemaName);
154
453
  diff.indexesToCreate.push(createIndexToString(effectiveStmt));
454
+ indexRecreates++;
455
+ continue;
456
+ }
457
+ if (matchedActual.name.toLowerCase() !== name) {
458
+ // Hinted rename, body unchanged → drop(old) + recreate(declared),
459
+ // rendered with in-diff column renames inverse-applied (NEW→OLD).
460
+ diff.indexesToDrop.push(matchedActual.name);
461
+ const reconciledStmt = columnReconciledIndexStmt(declaredIndex.indexStmt, indexColRenames, columnRenamesByTable, targetSchemaName);
462
+ diff.indexesToCreate.push(createIndexToString(applyIndexDefaults(reconciledStmt, targetSchemaName)));
463
+ indexRecreates++;
464
+ continue;
465
+ }
466
+ // Body unchanged → in-place tag change (pure name match only — renames `continue` above).
467
+ if (tagsDrifted(declaredIndex.indexStmt.tags, matchedActual.tags)) {
468
+ diff.indexTagsChanges.push({ name: declaredIndex.indexStmt.index.name, tags: desiredTagSet(declaredIndex.indexStmt.tags) });
155
469
  }
156
470
  }
157
471
  for (const [name] of actualIndexes) {
@@ -161,11 +475,17 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
161
475
  diff.indexesToDrop.push(name);
162
476
  }
163
477
  // Apply 'require-hint' policy: any unhinted name change is an error rather
164
- // than a silent drop+create.
478
+ // than a silent drop+create. A body-change OR hinted-rename recreate counts as
479
+ // both a create and a drop, which would falsely trip the unhinted-rename guard
480
+ // — so exclude those from the view/index counts (the constraint path does the
481
+ // same with its pure counts): both are deliberate drop+create pairs of a
482
+ // matched object, not an ambiguous unhinted rename. A backing-module move is the
483
+ // table-side analogue (matched-object recreate, not a rename), so subtract
484
+ // `maintainedModuleRecreates` from both table counts.
165
485
  if (policy === 'require-hint') {
166
- enforceRequireHint('table', diff.tablesToCreate.length, diff.tablesToDrop.length);
167
- enforceRequireHint('view', diff.viewsToCreate.length, diff.viewsToDrop.length);
168
- enforceRequireHint('index', diff.indexesToCreate.length, diff.indexesToDrop.length);
486
+ enforceRequireHint('table', diff.tablesToCreate.length - maintainedModuleRecreates, diff.tablesToDrop.length - maintainedModuleRecreates);
487
+ enforceRequireHint('view', diff.viewsToCreate.length - viewRecreates, diff.viewsToDrop.length - viewRecreates);
488
+ enforceRequireHint('index', diff.indexesToCreate.length - indexRecreates, diff.indexesToDrop.length - indexRecreates);
169
489
  }
170
490
  // Assertions (no rename support — names are explicitly part of the contract).
171
491
  const actualAssertions = new Map(actualCatalog.assertions.map(a => [a.name.toLowerCase(), a]));
@@ -181,6 +501,36 @@ export function computeSchemaDiff(declaredSchema, actualCatalog, policy = 'allow
181
501
  }
182
502
  return diff;
183
503
  }
504
+ /**
505
+ * Computes the diff for a **logical** declared schema. The per-table unit is
506
+ * attach/detach-lens, not create/drop-table:
507
+ * - a declared logical table not currently registered → attach,
508
+ * - a registered lens body absent from the declaration → detach.
509
+ *
510
+ * Crucially, this never populates `tablesToDrop` — a logical removal detaches
511
+ * the lens and leaves basis storage intact (see `docs/lens.md` § Deployment).
512
+ * Physical buckets stay empty; `generateMigrationDDL` emits nothing for a
513
+ * logical diff (attach/detach happens in the lens compiler at apply time).
514
+ */
515
+ function computeLogicalSchemaDiff(declaredSchema, actualCatalog, diff) {
516
+ const declaredLogical = new Set();
517
+ for (const item of declaredSchema.items) {
518
+ if (item.type === 'declaredTable') {
519
+ declaredLogical.add(item.tableStmt.table.name.toLowerCase());
520
+ }
521
+ }
522
+ // In a logical schema, registered views are exclusively lens bodies.
523
+ const actualLens = new Set(actualCatalog.views.map(v => v.name.toLowerCase()));
524
+ for (const name of declaredLogical) {
525
+ if (!actualLens.has(name))
526
+ diff.lensToAttach.push(name);
527
+ }
528
+ for (const name of actualLens) {
529
+ if (!declaredLogical.has(name))
530
+ diff.lensToDetach.push(name);
531
+ }
532
+ return diff;
533
+ }
184
534
  /**
185
535
  * Generic resolver: pair declared and actual objects by name first, then by
186
536
  * `quereus.id` and `quereus.previous_name` tag hints to detect renames.
@@ -287,26 +637,28 @@ function readQuereusHint(tags, key) {
287
637
  const trimmed = v.trim();
288
638
  return trimmed.length > 0 ? trimmed : undefined;
289
639
  }
290
- /**
291
- * Soft-warn on unrecognized `quereus.*` tag keys so future versions can add
292
- * keys without breaking older parsers.
293
- */
294
- function warnUnknownQuereusKeys(tags, subject, subjectName) {
295
- if (!tags)
296
- return;
297
- for (const key of Object.keys(tags)) {
298
- if (!key.startsWith(QUEREUS_TAG_PREFIX))
299
- continue;
300
- if (KNOWN_QUEREUS_KEYS.has(key))
301
- continue;
302
- warnLog('Unknown reserved tag key %s on %s %s', key, subject, subjectName);
303
- }
304
- }
305
640
  function enforceRequireHint(kind, creates, drops) {
306
641
  if (creates > 0 && drops > 0) {
307
642
  throw new QuereusError(`rename_policy = 'require-hint': ${kind} drops and creates both present (${drops} drop / ${creates} create); add 'quereus.previous_name' or 'quereus.id' to hint renames, or use 'allow' / 'deny'.`, StatusCode.ERROR);
308
643
  }
309
644
  }
645
+ /**
646
+ * Renders the fresh-create DDL for a declared table — shared by the create branch
647
+ * and the destructive backing-module move's recreate, so both re-materialize a
648
+ * maintained body into the declared module through one renderer. A maintained
649
+ * table declared via the `materialized view` sugar renders that sugar (its
650
+ * column-less normalized table form has no parseable `create table` DDL); a
651
+ * declared-shape maintained table and a plain table render the `create table …`
652
+ * form (carrying any `maintained as` clause).
653
+ */
654
+ function renderFreshTableCreate(name, tableStmt, declaredMaterializedViews, targetSchemaName, defaultVtabModule, defaultVtabArgs) {
655
+ const declaredMv = declaredMaterializedViews.get(name);
656
+ if (declaredMv) {
657
+ return createMaterializedViewToString(declaredMv.viewStmt);
658
+ }
659
+ const effectiveStmt = applyTableDefaults(tableStmt, targetSchemaName, defaultVtabModule, defaultVtabArgs);
660
+ return createTableToString(effectiveStmt);
661
+ }
310
662
  /**
311
663
  * Applies schema-level defaults (schema name, default vtab module) to a table statement
312
664
  */
@@ -371,17 +723,541 @@ function applyIndexDefaults(indexStmt, targetSchemaName) {
371
723
  }
372
724
  return result;
373
725
  }
374
- function computeTableAlterDiff(declaredTable, actualTable, policy) {
375
- const diff = {
376
- // Default to actual's name; caller may override to declared name when this is a rename target.
377
- tableName: actualTable.name,
378
- columnsToAdd: [],
379
- columnsToDrop: [],
380
- columnsToAlter: [],
381
- columnsToRename: [],
726
+ /**
727
+ * Reads an index column's EXPLICIT per-column collation as-written, covering both
728
+ * indexed-column forms: the plain `col.collation` and the parser's collate-folded
729
+ * form (`col COLLATE x`, whose collation lives on `col.expr.collation`). Returns
730
+ * undefined when the column carries no explicit COLLATE — it then inherits the
731
+ * table column's collation (see {@link declaredColumnCollation}).
732
+ */
733
+ function explicitIndexColumnCollation(col) {
734
+ if (col.collation)
735
+ return col.collation;
736
+ if (col.expr?.type === 'collate')
737
+ return col.expr.collation;
738
+ return undefined;
739
+ }
740
+ /**
741
+ * Reads a declared table column's effective collation from its `collate` column
742
+ * constraint, normalized (uppercase), defaulting to `'BINARY'`. Mirrors the
743
+ * engine's resolution of a table column's collation ({@link extractDeclaredCollation}).
744
+ * Returns `'BINARY'` when no declared table is supplied (an index on a non-declared
745
+ * table — not a coherent name-match) or the column is not found in it.
746
+ */
747
+ function declaredColumnCollation(declaredTable, columnName, defaultCollation) {
748
+ if (!declaredTable)
749
+ return 'BINARY';
750
+ const lower = columnName.toLowerCase();
751
+ const col = declaredTable.tableStmt.columns.find(c => c.name.toLowerCase() === lower);
752
+ return col ? extractDeclaredCollation(col, defaultCollation) : 'BINARY';
753
+ }
754
+ /**
755
+ * Renders the canonical body of a DECLARED index, pre-resolving each column's
756
+ * effective collation the same way the engine does at create/import time
757
+ * (`buildIndexSchema` / `importIndex`): explicit index COLLATE, else the declared
758
+ * table column's collation, else BINARY — normalized. The resolved value is placed
759
+ * on a normalized plain-form {@link AST.IndexedColumn} (`{ name, collation,
760
+ * direction }`) so {@link createIndexBodyToCanonicalString} reads it off
761
+ * `col.collation`, matching the actual side's lift (`indexToCanonicalDDL`). Because
762
+ * both sides feed an identically-resolved collation, an unchanged inherited/BINARY
763
+ * collation renders identically (no churn) while a genuine collation change diverges
764
+ * (drop+recreate).
765
+ *
766
+ * A genuine expression-index column (no resolvable bare name) is passed through
767
+ * untouched — there is no column collation to resolve and the renderer falls back to
768
+ * `expressionToString`. `declaredTable` is the matched declared table (looked up by
769
+ * the index's table reference); undefined falls back to explicit-or-BINARY.
770
+ *
771
+ * **Column-rename reconciliation.** `colRenames` are the in-diff column renames of
772
+ * the index's table (keyed by the declared/new table name in `computeSchemaDiff`'s
773
+ * pre-pass). The actual catalog body still renders the *pre-rename* column names at
774
+ * diff time, while the declared side renders the *new* names — so a same-named index
775
+ * over a column renamed in this same diff would otherwise churn a spurious
776
+ * drop+recreate. To reconcile, each resolved bare name is inverse-mapped from its NEW
777
+ * name back to its OLD name (case-insensitive) so a pure rename matches the actual
778
+ * body (no churn) while a genuine body edit layered on the rename still differs
779
+ * (recreate). The indexed-column list carries bare names (no qualifiers) and the body
780
+ * excludes the `on <table>` reference, so a *table* rename alone never churns the
781
+ * column list — but the partial WHERE predicate CAN embed table names: the index
782
+ * table as a qualifier (`where t.active = 1`) or, in principle, another table
783
+ * inside a subquery — so ALL in-diff table renames (`tableRenames`) are
784
+ * reconciled there (see below). The cross-table case is currently unreachable
785
+ * end-to-end (the memory backend rejects subqueries — and any cross-table ref —
786
+ * in partial-index predicates at create time, so no actual catalog index can
787
+ * carry one), but the all-renames scope is kept for symmetry with the forward
788
+ * rewriter and future backends.
789
+ *
790
+ * **Ordering is load-bearing**: the effective collation is resolved from the *new*
791
+ * (declared) column name FIRST — the declared table's `ColumnDef` is keyed by the new
792
+ * name — and only THEN is the emitted name inverse-renamed to its old form. Reversing
793
+ * this would look up the declared column's collation under the old name and miss it.
794
+ *
795
+ * The partial-index `where` predicate is reconciled the same way the constraint CHECK
796
+ * path is: EVERY in-diff table rename is inverse-rewritten NEW→OLD first (via
797
+ * {@link renameTableInAst} — the exact inverse of the forward rewriter the executed
798
+ * rename migration runs over ALL tables, so the diff-side reconcile and the migration
799
+ * cannot drift), THEN each renamed column is inverse-rewritten NEW→OLD via
800
+ * {@link renameColumnInCheckExpression} seeded with the OLD table name (qualifiers
801
+ * are pre-normalized to OLD by that point; unqualified refs resolve via the seed
802
+ * either way). The index table's OWN rename retains one special role: it supplies
803
+ * that seed. Sequential inverse application of multiple renames is order-independent
804
+ * because `resolveRenames` makes chains/swaps unrepresentable — no inverse output
805
+ * (oldName) can match another inverse input (newName). This keeps a partial index
806
+ * over a renamed column AND/OR a qualified self-reference under a renamed table from
807
+ * churning, while a genuine predicate edit layered on either rename still differs
808
+ * (recreate). `schemaName` is the default schema for both rewriters. Known accepted
809
+ * edge (symmetric with the forward path): the rewriters are scope-naive about a
810
+ * subquery alias that happens to equal a renamed table's new name — worst case a
811
+ * spurious (valid) recreate.
812
+ */
813
+ function declaredIndexCanonicalBody(indexStmt, declaredTable, colRenames, tableRenames, schemaName, defaultCollation) {
814
+ const columns = indexStmt.columns.map(col => {
815
+ const bareName = indexedColumnBareName(col);
816
+ if (!bareName)
817
+ return col;
818
+ // Collation resolves on the DECLARED (new) name — see the ordering note above.
819
+ // An inherited (no explicit index COLLATE) column resolves the table column's
820
+ // collation under the session default, matching the actual catalog index built
821
+ // from a default-resolved table column — so a non-BINARY default doesn't churn.
822
+ const effective = normalizeCollationName(explicitIndexColumnCollation(col) || declaredColumnCollation(declaredTable, bareName, defaultCollation) || 'BINARY');
823
+ // THEN inverse-rename the emitted name to its old (actual-catalog) form.
824
+ const oldName = colRenames.find(r => r.newName.toLowerCase() === bareName.toLowerCase())?.oldName ?? bareName;
825
+ return { name: oldName, collation: effective, direction: col.direction };
826
+ });
827
+ // Reconcile the partial-WHERE predicate: inverse-rewrite EVERY in-diff table
828
+ // rename NEW→OLD first (self-qualifier or a cross-table reference in a
829
+ // subquery alike — mirroring the forward rewriter's all-tables walk), then
830
+ // each renamed column NEW→OLD, over a clone (the rewriters mutate in place;
831
+ // indexStmt backs the recreate DDL). Skip the clone when nothing can match.
832
+ // The column rewrites are seeded with the OLD table name because the
833
+ // qualifier pass has already normalized qualified refs to it (with no own-
834
+ // table rename, OLD == declared, so the seed is unchanged).
835
+ let where = indexStmt.where;
836
+ if (where && (colRenames.length > 0 || tableRenames.length > 0)) {
837
+ const clone = cloneExpr(where);
838
+ for (const r of tableRenames) {
839
+ renameTableInAst(clone, r.newName, r.oldName, schemaName);
840
+ }
841
+ // The index's OWN table rename retains one special role: seeding the
842
+ // column rewrites with that table's OLD name (matched by the declared/
843
+ // NEW table name, exactly the lookup the call site used to do).
844
+ const ownRename = tableRenames.find(r => r.newName.toLowerCase() === indexStmt.table.name.toLowerCase());
845
+ const seedTableName = ownRename?.oldName ?? indexStmt.table.name;
846
+ for (const r of colRenames) {
847
+ renameColumnInCheckExpression(clone, seedTableName, r.newName, r.oldName, schemaName);
848
+ }
849
+ where = clone;
850
+ }
851
+ return createIndexBodyToCanonicalString({ ...indexStmt, columns, where });
852
+ }
853
+ /**
854
+ * Renders the declared view definition's canonical string with the in-diff
855
+ * renames inverse-applied — each renamed identifier rewritten from its declared
856
+ * NEW name back to the ACTUAL (pre-rename) name the catalog still carries at
857
+ * diff time. The view analogue of {@link reconciledDeclaredBody} (constraints)
858
+ * and {@link declaredIndexCanonicalBody}: comparing this against the actual
859
+ * definition distinguishes a *pure source rename* (matches after reconciliation
860
+ * → no recreate; the rename ops alone converge the view at apply, via the live
861
+ * rename propagation) from a *genuine definition edit* (still differs →
862
+ * drop+recreate). Callers short-circuit the raw-equal compare and call this
863
+ * only on mismatch with renames present.
864
+ *
865
+ * The explicit column list names the VIEW's own output columns — stable
866
+ * identity untouched by source renames — so it passes through unchanged; the
867
+ * body (including its trailing `with defaults (…)` clause) reconciles via
868
+ * {@link inverseRenamedViewParts} with ALL in-diff table renames threaded.
869
+ */
870
+ function reconciledDeclaredViewDefinition(columns, select, tableRenames,
871
+ /** Declared (new) table name (lowercased) → that table's column renames. */
872
+ columnRenamesByTable, schemaName,
873
+ /** Declared-side column-existence resolver for the seeded defaults-expr rewrites (see `computeSchemaDiff`). */
874
+ resolveDeclaredColumn) {
875
+ const reconciledSelect = inverseRenamedViewParts(select, tableRenames, columnRenamesByTable, schemaName, resolveDeclaredColumn);
876
+ return viewDefinitionToCanonicalString(columns, reconciledSelect);
877
+ }
878
+ /**
879
+ * Core inverse-rename pass shared by {@link reconciledDeclaredViewDefinition}
880
+ * (which threads ALL in-diff table renames) and {@link columnReconciledViewStmt}
881
+ * (which passes none — its render must keep declared table names): clones the
882
+ * declared select and `insert defaults` clause (the rewriters mutate in place;
883
+ * the declared stmt backs the declared-schema store / recreate DDL) and
884
+ * rewrites the in-diff renames NEW→OLD.
885
+ *
886
+ * Reconciled body: inverse table renames over the select clone for ALL threaded
887
+ * renames FIRST — so both a direct FROM reference and a cross-table reference in
888
+ * a subquery normalize to OLD names — THEN each renamed table's column renames
889
+ * NEW→OLD, seeded with that table's OLD name (qualifiers are pre-normalized to
890
+ * OLD by the table pass; with no own-table rename — in particular with no table
891
+ * pass at all — the seed is the DECLARED name the qualifiers still carry).
892
+ * Sequential inverse application is order-independent: `resolveRenames` makes
893
+ * chains/swaps unrepresentable, so no inverse output (oldName) can match another
894
+ * inverse input (newName).
895
+ *
896
+ * The trailing `with defaults (…)` clause now rides inside `select`
897
+ * ({@link AST.SelectStmt.defaults}), so the scope-aware body walks descend it
898
+ * directly: each entry's `expr` inverse-renames in the select's FROM scope frame
899
+ * and each entry's `column` target rides the same synthetic-probe rewrite as a
900
+ * `with inverse` target, so the body reconciliation here covers the defaults for
901
+ * free. The `resolveDeclaredColumn` resolver is threaded into that body walk so
902
+ * an unqualified ref inside a defaults-expr subquery that binds a like-named
903
+ * column on its own FROM is NOT false-captured by the enclosing FROM seed
904
+ * (declared-side parity with the forward live-lookup walk; see `computeSchemaDiff`).
905
+ */
906
+ function inverseRenamedViewParts(select, tableRenames,
907
+ /** Declared (new) table name (lowercased) → that table's column renames. */
908
+ columnRenamesByTable, schemaName,
909
+ /** Declared-side column-existence resolver for the scope-aware body walk (see `computeSchemaDiff`). */
910
+ resolveDeclaredColumn) {
911
+ const selectClone = cloneQueryExpr(select);
912
+ for (const r of tableRenames) {
913
+ renameTableInAst(selectClone, r.newName, r.oldName, schemaName);
914
+ }
915
+ for (const [declaredTableName, colRenames] of columnRenamesByTable) {
916
+ const ownRename = tableRenames.find(r => r.newName.toLowerCase() === declaredTableName);
917
+ const seedTableName = ownRename?.oldName ?? declaredTableName;
918
+ for (const r of colRenames) {
919
+ renameColumnInAst(selectClone, seedTableName, r.newName, r.oldName, schemaName, resolveDeclaredColumn);
920
+ }
921
+ }
922
+ return selectClone;
923
+ }
924
+ /**
925
+ * Declared {@link AST.CreateViewStmt} with the in-diff COLUMN renames
926
+ * inverse-applied (NEW→OLD); table references untouched. Renders the recreate
927
+ * DDL of a hinted-RENAME-matched view whose definition is otherwise unchanged:
928
+ * in migration order the create runs AFTER `ALTER TABLE … RENAME TO` (declared
929
+ * table names are already live) but BEFORE `ALTER TABLE … RENAME COLUMN` (a
930
+ * body naming the NEW column would fail to plan at create time). After the
931
+ * create, the live column-rename propagation rewrites the fresh body, so the
932
+ * post-apply state and a re-diff converge. Unlike
933
+ * {@link reconciledDeclaredViewDefinition} there is NO inverse table pass (it
934
+ * would name a table that no longer exists at create time) — the shared
935
+ * {@link inverseRenamedViewParts} core runs with no table renames, seeding the
936
+ * column rewrites with each table's DECLARED name, which the body's qualifiers
937
+ * still carry. Identity when the diff carries no column renames.
938
+ */
939
+ function columnReconciledViewStmt(stmt,
940
+ /** Declared (new) table name (lowercased) → that table's column renames. */
941
+ columnRenamesByTable, schemaName,
942
+ /** Declared-side column-existence resolver for the seeded defaults-expr rewrites (see `computeSchemaDiff`). */
943
+ resolveDeclaredColumn) {
944
+ if (columnRenamesByTable.size === 0)
945
+ return stmt;
946
+ // The reconciled body carries any trailing `with defaults (…)` clause inline.
947
+ const reconciledSelect = inverseRenamedViewParts(stmt.select, [], columnRenamesByTable, schemaName, resolveDeclaredColumn);
948
+ return { ...stmt, select: reconciledSelect };
949
+ }
950
+ /**
951
+ * Declared {@link AST.CreateIndexStmt} with the in-diff COLUMN renames
952
+ * inverse-applied (NEW→OLD); table references untouched. The index analogue of
953
+ * {@link columnReconciledViewStmt}, rendering the recreate DDL of a
954
+ * hinted-RENAME-matched index whose body is otherwise unchanged. Indexed-column
955
+ * bare names map NEW→OLD via the index table's own renames (both indexed-column
956
+ * forms — plain and the parser's collate-folded `col COLLATE x`). The partial
957
+ * WHERE predicate reuses the walk shape of {@link declaredIndexCanonicalBody}
958
+ * minus its inverse table pass: the own table's renames via the
959
+ * CHECK-expression entry point seeded with the DECLARED table name (qualifiers
960
+ * still carry it — no table pass pre-normalized them), other tables' renames
961
+ * via the plain scope-aware walk (a cross-table predicate reference is
962
+ * unreachable today — the memory backend rejects it at create time — kept for
963
+ * symmetry with the canonical-body reconciler). After the create, the live
964
+ * column-rename propagation rewrites the indexed columns and predicate, so a
965
+ * re-diff converges. Identity when the diff carries no column renames.
966
+ */
967
+ function columnReconciledIndexStmt(stmt,
968
+ /** The index's own table's in-diff column renames. */
969
+ colRenames,
970
+ /** Declared (new) table name (lowercased) → that table's column renames. */
971
+ columnRenamesByTable, schemaName) {
972
+ if (columnRenamesByTable.size === 0)
973
+ return stmt;
974
+ const columns = stmt.columns.map(col => {
975
+ const bareName = indexedColumnBareName(col);
976
+ if (!bareName)
977
+ return col;
978
+ const rename = colRenames.find(r => r.newName.toLowerCase() === bareName.toLowerCase());
979
+ if (!rename)
980
+ return col;
981
+ if (col.name)
982
+ return { ...col, name: rename.oldName };
983
+ // Collate-folded form: the bare name lives on col.expr.expr.name.
984
+ const collate = col.expr;
985
+ const inner = collate.expr;
986
+ return { ...col, expr: { ...collate, expr: { ...inner, name: rename.oldName } } };
987
+ });
988
+ let where = stmt.where;
989
+ if (where) {
990
+ const clone = cloneExpr(where);
991
+ for (const r of colRenames) {
992
+ renameColumnInCheckExpression(clone, stmt.table.name, r.newName, r.oldName, schemaName);
993
+ }
994
+ const ownTableLower = stmt.table.name.toLowerCase();
995
+ for (const [declaredTableName, renames] of columnRenamesByTable) {
996
+ if (declaredTableName === ownTableLower)
997
+ continue;
998
+ for (const r of renames) {
999
+ renameColumnInAst(clone, declaredTableName, r.newName, r.oldName, schemaName);
1000
+ }
1001
+ }
1002
+ where = clone;
1003
+ }
1004
+ return { ...stmt, columns, where };
1005
+ }
1006
+ /**
1007
+ * Converts a column-level constraint carrying a name into the equivalent
1008
+ * table-level {@link AST.TableConstraint}, so it can be stringified into an
1009
+ * `ADD CONSTRAINT` fragment and diffed by name alongside table-level constraints.
1010
+ * Returns undefined for constraint kinds that are not lifecycle-managed named
1011
+ * constraints (NOT NULL / NULL / DEFAULT / COLLATE / GENERATED / PRIMARY KEY).
1012
+ */
1013
+ function columnConstraintToTableConstraint(columnName, cc) {
1014
+ switch (cc.type) {
1015
+ case 'check':
1016
+ if (!cc.expr)
1017
+ return undefined;
1018
+ return { type: 'check', name: cc.name, expr: cc.expr, operations: cc.operations, onConflict: cc.onConflict, tags: cc.tags };
1019
+ case 'unique':
1020
+ return { type: 'unique', name: cc.name, columns: [{ name: columnName }], onConflict: cc.onConflict, tags: cc.tags };
1021
+ case 'foreignKey':
1022
+ if (!cc.foreignKey)
1023
+ return undefined;
1024
+ return { type: 'foreignKey', name: cc.name, columns: [{ name: columnName }], foreignKey: cc.foreignKey, tags: cc.tags };
1025
+ default:
1026
+ return undefined;
1027
+ }
1028
+ }
1029
+ /**
1030
+ * Gathers declared *user-named* CHECK / UNIQUE / FOREIGN KEY constraints from a
1031
+ * declared table — both table-level and column-level (carrying an explicit name)
1032
+ * — keyed by lowercased name. PRIMARY KEY is excluded (handled by
1033
+ * `primaryKeyChange`); engine-synthesized `_`-prefixed names are excluded to stay
1034
+ * symmetric with the catalog's `namedConstraints`. On a name collision the first
1035
+ * wins (a duplicate user constraint name is a separate validation concern).
1036
+ *
1037
+ * `schemaName` is the schema this table is being diffed under (the differ runs
1038
+ * per schema — it is the CHILD schema for any FK declared here). It is threaded
1039
+ * into the canonical-body render so an FK's explicit own-schema qualifier folds
1040
+ * out symmetrically with the actual-catalog side (see
1041
+ * {@link constraintBodyToCanonicalString}); a genuine cross-schema parent stays a
1042
+ * body-change channel.
1043
+ */
1044
+ function collectDeclaredNamedConstraints(declaredTable, schemaName) {
1045
+ const out = new Map();
1046
+ const add = (name, tags, tc) => {
1047
+ if (!name)
1048
+ return;
1049
+ const lower = name.toLowerCase();
1050
+ if (lower.startsWith('_'))
1051
+ return;
1052
+ if (out.has(lower))
1053
+ return;
1054
+ out.set(lower, { name, tags, ddl: tableConstraintsToString([tc]), definition: constraintBodyToCanonicalString(tc, schemaName), bodyAst: tc });
382
1055
  };
383
- // Detect column renames first so subsequent add/drop/alter operate on the
384
- // post-rename column set.
1056
+ for (const c of declaredTable.tableStmt.constraints ?? []) {
1057
+ if (c.type === 'primaryKey')
1058
+ continue;
1059
+ add(c.name, c.tags, c);
1060
+ }
1061
+ for (const col of declaredTable.tableStmt.columns) {
1062
+ for (const cc of col.constraints ?? []) {
1063
+ if (!cc.name)
1064
+ continue;
1065
+ const tc = columnConstraintToTableConstraint(col.name, cc);
1066
+ if (tc)
1067
+ add(cc.name, cc.tags, tc);
1068
+ }
1069
+ }
1070
+ return out;
1071
+ }
1072
+ /**
1073
+ * Inverse-applies the in-diff column renames to a constraint's column list: maps
1074
+ * each `{ name }` entry from its NEW name back to its OLD name (case-insensitive).
1075
+ * Used to reconcile a UNIQUE column set / an FK's local (child) column set against
1076
+ * the actual catalog body, which still renders the pre-rename names at diff time.
1077
+ * Mutates the supplied (already-cloned) array in place.
1078
+ */
1079
+ function inverseRenameConstraintColumns(columns, colRenames) {
1080
+ if (!columns)
1081
+ return;
1082
+ for (const col of columns) {
1083
+ const lower = col.name.toLowerCase();
1084
+ const r = colRenames.find(cr => cr.newName.toLowerCase() === lower);
1085
+ if (r)
1086
+ col.name = r.oldName;
1087
+ }
1088
+ }
1089
+ /**
1090
+ * String-list variant of {@link inverseRenameConstraintColumns}: inverse-applies
1091
+ * column renames to a bare `string[]` (an FK's referenced PARENT column list,
1092
+ * which is `string[]` rather than `{ name }[]`), mapping each entry from its NEW
1093
+ * name back to its OLD name (case-insensitive). Used to reconcile an FK whose
1094
+ * *parent* table renamed a referenced column — the parent's column renames are
1095
+ * threaded in from `computeSchemaDiff`'s pre-pass. Mutates the supplied
1096
+ * (already-cloned) array in place; an undefined / elided list is a no-op (so a
1097
+ * `references parent` with no column list never synthesizes one).
1098
+ */
1099
+ function inverseRenameStringColumns(columns, colRenames) {
1100
+ if (!columns)
1101
+ return;
1102
+ for (let i = 0; i < columns.length; i++) {
1103
+ const lower = columns[i].toLowerCase();
1104
+ const r = colRenames.find(cr => cr.newName.toLowerCase() === lower);
1105
+ if (r)
1106
+ columns[i] = r.oldName;
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Renders the declared constraint's canonical body with the in-diff renames
1111
+ * inverse-applied — i.e. each renamed identifier rewritten from its NEW name
1112
+ * back to the ACTUAL (pre-rename) name the catalog still carries at diff time.
1113
+ * Comparing this against `actual.definition` lets the body-change detector
1114
+ * distinguish a *pure rename* (bodies match after reconciliation → no churn) from
1115
+ * a *genuine body edit* (still differ → drop+recreate). A body edit layered on a
1116
+ * rename survives the reconciliation, so the existing rename-vs-body precedence is
1117
+ * preserved.
1118
+ *
1119
+ * Reconciles only what each kind needs (surgical clone, never the whole tree):
1120
+ * - CHECK: inverse table renames on ALL in-diff renamed tables FIRST —
1121
+ * mirroring the forward path (`rewriteTableForTableRename` walks
1122
+ * every table's CHECKs), so both a qualified self-reference and a
1123
+ * cross-table reference inside a subquery reconcile — then inverse
1124
+ * column renames, in two passes mirroring the forward
1125
+ * `rewriteTableForColumnRename` branch split: the OWNING table's
1126
+ * renames via the seeded CHECK rewriter (OLD-table seed — correct
1127
+ * unconditionally, since qualifiers are pre-normalized to OLD by
1128
+ * the qualifier pass — plus the declared-side scope resolver, so
1129
+ * an unqualified inner-subquery ref binding to a like-named column
1130
+ * on its own FROM source is not falsely captured by the seed),
1131
+ * then OTHER tables' renames via the plain scope-aware walk (no
1132
+ * seed, no resolver — exactly the forward non-owning branch).
1133
+ * Within each pass, sequential inverse application is
1134
+ * order-independent: `resolveRenames` makes rename chains/swaps
1135
+ * unrepresentable, so no inverse output (oldName) can match
1136
+ * another inverse input (newName). BETWEEN the passes order
1137
+ * matters — owning first (see the cross-table loop's comment).
1138
+ * - UNIQUE: inverse column renames on the column list.
1139
+ * - FK: inverse column renames on the LOCAL (child) column list, inverse
1140
+ * column renames on the referenced PARENT column list (via the parent
1141
+ * table's column renames, keyed by the declared parent name in
1142
+ * `columnRenamesByTable`), AND inverse table renames on the referenced
1143
+ * parent `foreignKey.table`. A parent-table rename and a parent-column
1144
+ * rename in the same diff reconcile together: look up the parent's
1145
+ * column renames by the *new* parent name first, then rewrite the
1146
+ * table name back to its old form.
1147
+ */
1148
+ function reconciledDeclaredBody(d, colRenames, tableRenames, tableName, schemaName,
1149
+ /** Declared (new) table name (lowercased) → that table's column renames; for the FK parent-column reconcile. */
1150
+ columnRenamesByTable,
1151
+ /** Declared-side column-existence resolver for the seeded CHECK rewrites (see `computeSchemaDiff`). */
1152
+ resolveDeclaredColumn) {
1153
+ const tc = d.bodyAst;
1154
+ switch (tc.type) {
1155
+ case 'check': {
1156
+ if (!tc.expr)
1157
+ return d.definition;
1158
+ // cloneExpr: the rewriters mutate in place; bodyAst backs ddl/definition.
1159
+ const clone = { ...tc, expr: cloneExpr(tc.expr) };
1160
+ // Qualifiers first: any qualified reference in the declared CHECK carries
1161
+ // a NEW table name (a self-reference after the owning table's rename, or a
1162
+ // cross-table reference inside a subquery after THAT table's rename);
1163
+ // inverse-rewrite every in-diff rename to its OLD name so the body matches
1164
+ // the actual catalog and the OLD-seeded column rewrites below see the
1165
+ // owning table's OLD qualifier. Sequential in-place application is safe:
1166
+ // `resolveRenames` makes chains and swaps unrepresentable (every newName is
1167
+ // absent from the actual catalog while every oldName is present), so no
1168
+ // rename's inverse output can match another's inverse input — order is
1169
+ // immaterial and equivalent to simultaneous substitution.
1170
+ for (const r of tableRenames) {
1171
+ renameTableInAst(clone.expr, r.newName, r.oldName, schemaName);
1172
+ }
1173
+ for (const r of colRenames) {
1174
+ // Inverse: rewrite the declared NEW column name back to its OLD name.
1175
+ // The declared-side resolver keeps the seeded walk scope-aware — an
1176
+ // unqualified ref inside a subquery whose own FROM source exposes the
1177
+ // NEW name (in the declared world) binds there, not to the owning
1178
+ // seed, so it is NOT inverse-rewritten — mirroring the forward seeded
1179
+ // call in `rewriteTableForColumnRename` (owning-table branch).
1180
+ renameColumnInCheckExpression(clone.expr, tableName, r.newName, r.oldName, schemaName, resolveDeclaredColumn);
1181
+ }
1182
+ // Cross-table column renames: a subquery in this CHECK may reference
1183
+ // ANOTHER table whose column was renamed in this same diff; the forward
1184
+ // propagation rewrites those refs via the plain scope-aware walk (no seed
1185
+ // frame, no resolver — `rewriteTableForColumnRename`'s non-owning branch),
1186
+ // so the inverse uses the same walker: an unqualified ref only rewrites
1187
+ // when the renamed table sits in an enclosing FROM frame, which is exactly
1188
+ // right for subquery references. The map key is the DECLARED (new) table
1189
+ // name; the qualifier pass above already rewrote the clone's references to
1190
+ // OLD names, so map the walk's table seed back. The owning table's entry
1191
+ // is skipped — its renames are `colRenames`, handled by the seeded loop
1192
+ // above (`tableName` is the ACTUAL/old owning name, so the comparison
1193
+ // holds even when the owning table was itself renamed). ORDER MATTERS:
1194
+ // owning-seeded inverse FIRST. With the reverse order, a compound diff
1195
+ // (owning `qty→cap` + referenced `lim.cap→capacity`) has this loop turn
1196
+ // the inner `capacity` back into `cap`, which the owning inverse then
1197
+ // falsely captures; owning-first leaves the inner ref spelled `capacity`
1198
+ // (no match) until this loop fixes it.
1199
+ for (const [declaredTableName, renames] of columnRenamesByTable) {
1200
+ const ownRename = tableRenames.find(r => r.newName.toLowerCase() === declaredTableName);
1201
+ const seedTableName = ownRename?.oldName ?? declaredTableName;
1202
+ if (seedTableName.toLowerCase() === tableName.toLowerCase())
1203
+ continue;
1204
+ for (const r of renames) {
1205
+ renameColumnInAst(clone.expr, seedTableName, r.newName, r.oldName, schemaName);
1206
+ }
1207
+ }
1208
+ return constraintBodyToCanonicalString(clone);
1209
+ }
1210
+ case 'unique': {
1211
+ const clone = { ...tc, columns: tc.columns?.map(c => ({ ...c })) };
1212
+ inverseRenameConstraintColumns(clone.columns, colRenames);
1213
+ return constraintBodyToCanonicalString(clone);
1214
+ }
1215
+ case 'foreignKey': {
1216
+ const clone = {
1217
+ ...tc,
1218
+ columns: tc.columns?.map(c => ({ ...c })),
1219
+ foreignKey: tc.foreignKey
1220
+ ? { ...tc.foreignKey, columns: tc.foreignKey.columns ? [...tc.foreignKey.columns] : undefined }
1221
+ : tc.foreignKey,
1222
+ };
1223
+ // Local (child) columns live on THIS table → inverse column rename.
1224
+ inverseRenameConstraintColumns(clone.columns, colRenames);
1225
+ if (clone.foreignKey) {
1226
+ // Referenced PARENT columns → inverse column rename via the parent
1227
+ // table's renames, looked up by the DECLARED (new) parent name —
1228
+ // BEFORE the table inverse-rename below rewrites that name to its old
1229
+ // form. Absent from the map (e.g. a freshly created parent) ⇒ no-op.
1230
+ const parentLower = clone.foreignKey.table.toLowerCase();
1231
+ const parentColRenames = columnRenamesByTable.get(parentLower);
1232
+ if (parentColRenames)
1233
+ inverseRenameStringColumns(clone.foreignKey.columns, parentColRenames);
1234
+ // Parent table reference → inverse table rename (newTable → oldTable).
1235
+ // The parent SCHEMA is NOT a rename channel (renames are within-schema),
1236
+ // so `foreignKey.schema` rides the clone untouched; the canonical render
1237
+ // folds an own-schema qualifier out against `schemaName` (the child schema).
1238
+ const tr = tableRenames.find(r => r.newName.toLowerCase() === parentLower);
1239
+ if (tr)
1240
+ clone.foreignKey = { ...clone.foreignKey, table: tr.oldName };
1241
+ }
1242
+ return constraintBodyToCanonicalString(clone, schemaName);
1243
+ }
1244
+ default:
1245
+ return d.definition;
1246
+ }
1247
+ }
1248
+ /**
1249
+ * Resolves a declared table's column renames against its matched actual catalog
1250
+ * table — the map-building + {@link resolveRenames} step shared by both the
1251
+ * `computeSchemaDiff` pre-pass (which keeps only `.renames`, keyed by declared
1252
+ * table name, so the FK branch of {@link reconciledDeclaredBody} can inverse-
1253
+ * rename a parent's referenced column cross-table) and {@link computeTableAlterDiff}
1254
+ * (which uses the full `{ renames, pairs, consumedActuals }` for add/drop/alter).
1255
+ * The current table's renames are therefore resolved twice per diff — once in the
1256
+ * pre-pass and once in its own alter-diff. Accepted: `resolveRenames` over a
1257
+ * table's columns is O(columns) with no I/O, and threading the full result through
1258
+ * the loop for a micro-optimization would widen the blast radius.
1259
+ */
1260
+ function resolveColumnRenames(declaredTable, actualTable, policy) {
385
1261
  const declaredColumns = new Map();
386
1262
  for (const col of declaredTable.tableStmt.columns) {
387
1263
  declaredColumns.set(col.name.toLowerCase(), col);
@@ -390,7 +1266,7 @@ function computeTableAlterDiff(declaredTable, actualTable, policy) {
390
1266
  for (const col of actualTable.columns) {
391
1267
  actualColumns.set(col.name.toLowerCase(), col);
392
1268
  }
393
- const colRenames = resolveRenames({
1269
+ return resolveRenames({
394
1270
  kind: 'constraint', // unused for ColumnRenameOp; kind only flows into RenameOp not surfaced here
395
1271
  declared: declaredColumns,
396
1272
  actual: actualColumns,
@@ -400,21 +1276,83 @@ function computeTableAlterDiff(declaredTable, actualTable, policy) {
400
1276
  getActualTags: a => a.tags,
401
1277
  policy,
402
1278
  });
1279
+ }
1280
+ function computeTableAlterDiff(declaredTable, actualTable, policy,
1281
+ /** Table renames detected in this same diff (used to reconcile FK parent-table refs). */
1282
+ tableRenames,
1283
+ /** Schema name — the default schema for the CHECK column-rename rewriter. */
1284
+ schemaName,
1285
+ /**
1286
+ * Declared (new) table name (lowercased) → that table's column renames, for the
1287
+ * cross-table FK referenced-parent-column reconcile in {@link reconciledDeclaredBody}.
1288
+ */
1289
+ columnRenamesByTable,
1290
+ /** Declared-side column-existence resolver for the scope-aware CHECK reconcile (see `computeSchemaDiff`). */
1291
+ resolveDeclaredColumn,
1292
+ /** Session `default_collation` for resolving an omitted COLLATE on the declared side. */
1293
+ defaultCollation) {
1294
+ const diff = {
1295
+ // Default to actual's name; caller may override to declared name when this is a rename target.
1296
+ tableName: actualTable.name,
1297
+ columnsToAdd: [],
1298
+ columnsToDrop: [],
1299
+ columnsToAlter: [],
1300
+ columnsToRename: [],
1301
+ };
1302
+ const declaredMaintained = declaredTable.tableStmt.maintained;
1303
+ const liveMaintained = actualTable.maintained;
1304
+ // Backing-module drift on a both-maintained name-match → a destructive,
1305
+ // incarnation-minting move (drop+recreate). Detected here, where both module
1306
+ // sides are in scope (declared on the table stmt; live on the maintained
1307
+ // descriptor); `computeSchemaDiff` routes it to drop+recreate instead of
1308
+ // pushing this alter. Compare ONLY when BOTH sides are maintained — a
1309
+ // table↔maintained transition is an attach/detach (handled by
1310
+ // applyMaintainedTransition), never a module move — and use the SAME
1311
+ // normalization the live catalog already applied (absent/`mem` ⇒ `memory`;
1312
+ // args stable-sorted, absent ⇒ ''), so the two spellings of the memory default
1313
+ // never drift. The module is deliberately separate from `bodyHash`, so the body
1314
+ // re-attach path stays untouched.
1315
+ const moduleMigration = backingModuleDrifted(declaredMaintained, liveMaintained, declaredTable.tableStmt.moduleName, declaredTable.tableStmt.moduleArgs);
1316
+ if (moduleMigration)
1317
+ diff.maintainedModuleMigration = moduleMigration;
1318
+ // MV-sugar / implicit maintained form: the body owns the shape, so the differ
1319
+ // stays PLAN-FREE — it never derives or compares this table's columns /
1320
+ // constraints / PK. A genuine shape mismatch surfaces at apply's attach shape
1321
+ // check (the sited, authoritative check), not here. Only table tags and the
1322
+ // derivation transition matter. (The declared-shape table form carries columns
1323
+ // and falls through to the full comparison below.)
1324
+ if (declaredMaintained && declaredTable.tableStmt.columns.length === 0) {
1325
+ if (tagsDrifted(declaredTable.tableStmt.tags, actualTable.tags)) {
1326
+ diff.tableTagsChange = desiredTagSet(declaredTable.tableStmt.tags);
1327
+ }
1328
+ applyMaintainedTransition(diff, declaredMaintained, liveMaintained, tableRenames, columnRenamesByTable, schemaName, resolveDeclaredColumn);
1329
+ markMaintainedTagRoute(diff, liveMaintained);
1330
+ return diff;
1331
+ }
1332
+ // Detect column renames first so subsequent add/drop/alter operate on the
1333
+ // post-rename column set.
1334
+ const colRenames = resolveColumnRenames(declaredTable, actualTable, policy);
403
1335
  for (const r of colRenames.renames) {
404
1336
  diff.columnsToRename.push({ oldName: r.oldName, newName: r.newName });
405
1337
  }
406
- // Find columns to add (store full column definition for DDL generation)
1338
+ // Find columns to add (store full column definition for DDL generation).
1339
+ // Emit an EXPLICIT resolved COLLATE when the declared column omits one and the
1340
+ // session `default_collation` resolves to a non-BINARY collation for its type
1341
+ // (see {@link withResolvedAddColumnCollation}) — keeps the migration both
1342
+ // self-contained (lands the same collation under any executing session default)
1343
+ // and idempotent (matches the catalog column the engine's ADD COLUMN now creates).
407
1344
  for (const col of declaredTable.tableStmt.columns) {
408
1345
  if (!colRenames.pairs.has(col.name.toLowerCase())) {
409
- diff.columnsToAdd.push(columnDefToString(col));
1346
+ diff.columnsToAdd.push(columnDefToString(withResolvedAddColumnCollation(col, defaultCollation)));
410
1347
  }
411
1348
  }
412
1349
  // Find columns to drop (skip those consumed by a rename)
1350
+ const declaredColumnNames = new Set(declaredTable.tableStmt.columns.map(c => c.name.toLowerCase()));
413
1351
  for (const col of actualTable.columns) {
414
1352
  const ln = col.name.toLowerCase();
415
1353
  if (colRenames.consumedActuals.has(ln))
416
1354
  continue;
417
- if (!declaredColumns.has(ln)) {
1355
+ if (!declaredColumnNames.has(ln)) {
418
1356
  diff.columnsToDrop.push(col.name);
419
1357
  }
420
1358
  }
@@ -423,7 +1361,7 @@ function computeTableAlterDiff(declaredTable, actualTable, policy) {
423
1361
  const matched = colRenames.pairs.get(col.name.toLowerCase());
424
1362
  if (!matched)
425
1363
  continue;
426
- const change = computeColumnAttributeChange(col, matched);
1364
+ const change = computeColumnAttributeChange(col, matched, defaultCollation);
427
1365
  if (change) {
428
1366
  diff.columnsToAlter.push(change);
429
1367
  }
@@ -432,15 +1370,15 @@ function computeTableAlterDiff(declaredTable, actualTable, policy) {
432
1370
  if (policy === 'require-hint') {
433
1371
  enforceRequireHint(`column (${actualTable.name})`, diff.columnsToAdd.length, diff.columnsToDrop.length);
434
1372
  }
435
- // Detect named-constraint renames (table-level only). We do not surface
436
- // drops/creates of constraints here the engine has no ALTER for those
437
- // today; the caller would have to manage that out-of-band. For renames,
438
- // when a primitive doesn't exist this becomes drop+recreate at DDL emit time.
439
- const declaredNamedConstraints = new Map();
440
- for (const c of declaredTable.tableStmt.constraints ?? []) {
441
- if (c.name)
442
- declaredNamedConstraints.set(c.name.toLowerCase(), c);
443
- }
1373
+ // Constraint lifecycle (CHECK / UNIQUE / FOREIGN KEY) by name: rename / drop /
1374
+ // add. We gather declared *user-named* constraints from BOTH the table-level
1375
+ // `constraints` list AND column-level constraints carrying an explicit name
1376
+ // (e.g. `qty int constraint chk_qty check (qty > 0)`) the actual catalog's
1377
+ // `namedConstraints` already merges both. PRIMARY KEY constraints are excluded
1378
+ // (PK changes flow through `primaryKeyChange`); auto-prefixed (`_`) names are
1379
+ // excluded to stay symmetric with the catalog (see catalog.ts) so an unnamed
1380
+ // declared constraint never churns add/drop against its synthesized actual name.
1381
+ const declaredNamedConstraints = collectDeclaredNamedConstraints(declaredTable, schemaName);
444
1382
  const actualNamedConstraints = new Map();
445
1383
  for (const c of actualTable.namedConstraints ?? []) {
446
1384
  actualNamedConstraints.set(c.name.toLowerCase(), c);
@@ -455,20 +1393,293 @@ function computeTableAlterDiff(declaredTable, actualTable, policy) {
455
1393
  getActualTags: a => a.tags,
456
1394
  policy,
457
1395
  });
458
- if (constraintRenames.renames.length > 0) {
459
- diff.constraintsToRename = constraintRenames.renames.map(r => ({ oldName: r.oldName, newName: r.newName }));
1396
+ // Adds / drops / body-change recreates. A declared constraint is matched (by
1397
+ // name or by rename hint) to at most one actual via `constraintRenames.pairs`:
1398
+ // - no match → create (ADD declared fragment)
1399
+ // - match, same body → no-op here (rename, if any, handled below; tags below)
1400
+ // - match, body changed → drop the old + add the declared (drop+recreate); a
1401
+ // constraint body change has no in-place "redefine"
1402
+ // primitive, and re-creation re-validates existing rows
1403
+ // against the new rule.
1404
+ // Precedence: when a constraint was rename-matched AND its body changed, prefer
1405
+ // the drop+recreate (under the declared name/def) and SUPPRESS the RENAME — one
1406
+ // coherent op, and the new body must re-validate regardless.
1407
+ const constraintsToAdd = [];
1408
+ const constraintsToDrop = [];
1409
+ const renamesSuppressedByBodyChange = new Set(); // declared lower-names
1410
+ const bodyChangedNames = new Set(); // declared lower-names recreated
1411
+ // Counts that feed the `require-hint` guard: only a PURE create + PURE drop
1412
+ // (the unhinted-rename shape) trips it. A same-constraint body-change drop+add
1413
+ // is a deliberate recreate, not an ambiguous rename, so it is excluded here.
1414
+ let pureCreateCount = 0;
1415
+ let pureDropCount = 0;
1416
+ for (const [lower, d] of declaredNamedConstraints) {
1417
+ const matchedActual = constraintRenames.pairs.get(lower);
1418
+ if (!matchedActual) {
1419
+ constraintsToAdd.push(d.ddl); // create (name- or rename-unmatched)
1420
+ pureCreateCount++;
1421
+ continue;
1422
+ }
1423
+ // Body comparison. The actual side (`matchedActual.definition`) renders the
1424
+ // PRE-rename identifier names (a column/parent-table rename has not landed at
1425
+ // diff time); the declared side uses the NEW names. So a string mismatch that
1426
+ // is purely a renamed identifier — already emitted as a rename in this same
1427
+ // diff — must NOT churn a drop+recreate. Short-circuit the common no-rename
1428
+ // case (raw strings equal) first; only then reconcile the declared body back
1429
+ // to the old names and re-compare. A genuine body edit still differs after
1430
+ // reconciliation, so the drop+recreate (and its rename-suppression) is kept.
1431
+ if (d.definition !== matchedActual.definition &&
1432
+ reconciledDeclaredBody(d, diff.columnsToRename, tableRenames, actualTable.name, schemaName, columnRenamesByTable, resolveDeclaredColumn) !== matchedActual.definition) {
1433
+ constraintsToDrop.push(matchedActual.name); // drop old
1434
+ constraintsToAdd.push(d.ddl); // add new (declared name + tags)
1435
+ bodyChangedNames.add(lower);
1436
+ if (matchedActual.name.toLowerCase() !== lower)
1437
+ renamesSuppressedByBodyChange.add(lower);
1438
+ }
1439
+ }
1440
+ // Renames, minus any subsumed by a same-constraint body change.
1441
+ const effectiveConstraintRenames = constraintRenames.renames.filter(r => !renamesSuppressedByBodyChange.has(r.newName.toLowerCase()));
1442
+ if (effectiveConstraintRenames.length > 0) {
1443
+ diff.constraintsToRename = effectiveConstraintRenames.map(r => ({ oldName: r.oldName, newName: r.newName }));
1444
+ }
1445
+ // Drops: actual constraint neither declared nor consumed by a rename → DROP.
1446
+ // (A rename-consumed actual whose body also changed was already dropped above;
1447
+ // it stays in `consumedActuals`, so it is skipped here — no double drop.)
1448
+ for (const [lower, a] of actualNamedConstraints) {
1449
+ if (constraintRenames.consumedActuals.has(lower))
1450
+ continue;
1451
+ if (!declaredNamedConstraints.has(lower)) {
1452
+ constraintsToDrop.push(a.name);
1453
+ pureDropCount++;
1454
+ }
1455
+ }
1456
+ if (constraintsToAdd.length > 0)
1457
+ diff.constraintsToAdd = constraintsToAdd;
1458
+ if (constraintsToDrop.length > 0)
1459
+ diff.constraintsToDrop = constraintsToDrop;
1460
+ // Apply require-hint to constraints within this table (a PURE add + a PURE drop
1461
+ // with no rename hint is the ambiguous case the policy guards — body-change
1462
+ // recreates are excluded from the counts).
1463
+ if (policy === 'require-hint') {
1464
+ enforceRequireHint(`constraint (${actualTable.name})`, pureCreateCount, pureDropCount);
1465
+ }
1466
+ // Detect named-constraint tag drift (name-matched constraints only — a
1467
+ // renamed constraint is not addressable by ALTER CONSTRAINT, see the runtime
1468
+ // emitter). Whole-set replacement; rename hints excluded from the compare. A
1469
+ // constraint that is being body-change-recreated is skipped: its recreate
1470
+ // fragment already carries the declared tags, so a separate SET TAGS would be
1471
+ // redundant (and would target a constraint that no longer exists at that point).
1472
+ const constraintTagsChanges = [];
1473
+ for (const [name, declaredConstraint] of declaredNamedConstraints) {
1474
+ if (bodyChangedNames.has(name))
1475
+ continue;
1476
+ const actualConstraint = actualNamedConstraints.get(name);
1477
+ if (!actualConstraint)
1478
+ continue; // a rename/create — not a same-name tag change
1479
+ if (tagsDrifted(declaredConstraint.tags, actualConstraint.tags)) {
1480
+ constraintTagsChanges.push({
1481
+ constraintName: declaredConstraint.name,
1482
+ tags: desiredTagSet(declaredConstraint.tags),
1483
+ });
1484
+ }
1485
+ }
1486
+ if (constraintTagsChanges.length > 0) {
1487
+ diff.constraintTagsChanges = constraintTagsChanges;
1488
+ }
1489
+ // Detect table-level tag drift (whole-set replacement; rename hints excluded).
1490
+ if (tagsDrifted(declaredTable.tableStmt.tags, actualTable.tags)) {
1491
+ diff.tableTagsChange = desiredTagSet(declaredTable.tableStmt.tags);
460
1492
  }
461
1493
  // Detect PK changes
462
1494
  const declaredPk = extractDeclaredPK(declaredTable);
463
1495
  const actualPk = actualTable.primaryKey;
464
- if (!pkSequencesEqual(declaredPk, actualPk)) {
1496
+ // Inverse-rename declared PK column names (new → old) so a pure PK-column rename
1497
+ // — already emitted as RENAME COLUMN — does not also churn an ALTER PRIMARY KEY.
1498
+ // Mirrors the constraint-body reconciliation (reconciledDeclaredBody). A PK
1499
+ // references only THIS table's own columns, so `diff.columnsToRename` suffices —
1500
+ // no cross-table `columnRenamesByTable` / table renames (unlike the FK body case).
1501
+ // Clone first: inverseRenameConstraintColumns mutates in place, and declaredPk
1502
+ // backs the NEW names carried in `newPkColumns`.
1503
+ const reconciledDeclaredPk = declaredPk.map(c => ({ ...c }));
1504
+ inverseRenameConstraintColumns(reconciledDeclaredPk, diff.columnsToRename);
1505
+ if (!pkSequencesEqual(reconciledDeclaredPk, actualPk)) {
465
1506
  diff.primaryKeyChange = {
466
1507
  oldPkColumns: actualPk.map(pk => pk.columnName),
467
- newPkColumns: declaredPk,
1508
+ newPkColumns: declaredPk, // keep NEW (declared) names for the genuine-change DDL
468
1509
  };
469
1510
  }
1511
+ // Derivation transition LAST, so a re-attach with a concurrent shape change can
1512
+ // observe the column / PK / constraint ops recorded above (→ detach-reshape-attach).
1513
+ applyMaintainedTransition(diff, declaredMaintained, liveMaintained, tableRenames, columnRenamesByTable, schemaName, resolveDeclaredColumn);
1514
+ markMaintainedTagRoute(diff, liveMaintained);
470
1515
  return diff;
471
1516
  }
1517
+ /**
1518
+ * Route a maintained table's tag change through `ALTER MATERIALIZED VIEW` (see
1519
+ * {@link TableAlterDiff.maintainedTags}): set the flag when a tag change exists on
1520
+ * a table that is live-maintained and NOT detached early in this same diff (so it
1521
+ * is still maintained at the moment the alter block's SET TAGS runs).
1522
+ */
1523
+ function markMaintainedTagRoute(diff, liveMaintained) {
1524
+ if (diff.tableTagsChange !== undefined && liveMaintained && !diff.dropMaintained) {
1525
+ diff.maintainedTags = true;
1526
+ }
1527
+ }
1528
+ /**
1529
+ * Detects a backing-module move on a name-matched maintained table: the declared
1530
+ * `using <module>(args)` clause normalizes to a different backing than the live
1531
+ * one. Returns `{ fromModule, toModule }` (normalized labels) on drift, else
1532
+ * `undefined`. Fires ONLY when BOTH sides are maintained — a table↔maintained
1533
+ * transition is an attach/detach, not a module move. The name half uses
1534
+ * {@link normalizeBackingModuleName} (absent/`mem` ⇒ `memory`, lowercased) and
1535
+ * the args half {@link canonicalBackingModuleArgs} (stable sorted-key render,
1536
+ * absent ⇒ ''), so the two spellings of the memory default (`using memory()` /
1537
+ * `using mem()` / omitted) never register as drift.
1538
+ */
1539
+ function backingModuleDrifted(declaredMaintained, liveMaintained, declaredModuleName, declaredModuleArgs) {
1540
+ if (!declaredMaintained || !liveMaintained)
1541
+ return undefined;
1542
+ const declName = normalizeBackingModuleName(declaredModuleName);
1543
+ const liveName = normalizeBackingModuleName(liveMaintained.backingModuleName);
1544
+ const declArgs = canonicalBackingModuleArgs(declaredModuleArgs);
1545
+ const liveArgs = canonicalBackingModuleArgs(liveMaintained.backingModuleArgs);
1546
+ if (declName === liveName && declArgs === liveArgs)
1547
+ return undefined;
1548
+ return {
1549
+ fromModule: backingModuleLabel(liveName, liveArgs),
1550
+ toModule: backingModuleLabel(declName, declArgs),
1551
+ };
1552
+ }
1553
+ /** Renders a normalized backing-module identity as `name` or `name(args)` for diagnostics. */
1554
+ function backingModuleLabel(name, canonicalArgs) {
1555
+ return canonicalArgs ? `${name}(${canonicalArgs})` : name;
1556
+ }
1557
+ /**
1558
+ * Recognize the maintained-table (derivation) transition for a name-matched table
1559
+ * and record the attach / detach / re-attach op on `diff`. PLAN-FREE: it compares
1560
+ * only the canonical body hash (declared `maintained` clause vs the live
1561
+ * `derivation.bodyHash`), never the body's derived shape.
1562
+ *
1563
+ * declared maintained, live plain → attach (set maintained as)
1564
+ * declared plain, live maintained → detach (drop maintained)
1565
+ * both maintained, body hash equal → no-op (idempotent — no phantom re-attach)
1566
+ * both maintained, body hash drift → re-attach: a single `set maintained as`
1567
+ * when the table shape is unchanged (content reconcile / refresh — NOT a
1568
+ * recreate, so the table incarnation and unrelated rows survive); a
1569
+ * detach → column ops → attach when the declared shape ALSO drifted (the new
1570
+ * column's values arrive via the attach reconcile).
1571
+ *
1572
+ * A pure in-diff source rename is inverse-applied before the hash compare so a
1573
+ * rename never churns a spurious re-attach (mirrors the view/MV body reconcile).
1574
+ * A backing-module change is deliberately NOT compared here — it is a destructive
1575
+ * incarnation-minting move (drop+recreate), detected separately by
1576
+ * {@link backingModuleDrifted} in `computeTableAlterDiff` and routed by
1577
+ * `computeSchemaDiff`, never folded into this non-destructive body transition.
1578
+ */
1579
+ function applyMaintainedTransition(diff, declaredMaintained, liveMaintained, tableRenames, columnRenamesByTable, schemaName,
1580
+ /** Declared-side column-existence resolver for the seeded defaults-expr rewrites (see `computeSchemaDiff`). */
1581
+ resolveDeclaredColumn) {
1582
+ if (!declaredMaintained && !liveMaintained)
1583
+ return;
1584
+ if (declaredMaintained && !liveMaintained) {
1585
+ // Attach: derived content reconciles the live (plain) table's rows. Any
1586
+ // `with defaults (…)` rides inside the body select. Carry the DECLARED rename
1587
+ // list (the verb renames + records explicit; `undefined` ⇒ stays implicit).
1588
+ diff.setMaintained = { columns: declaredMaintained.columns, select: declaredMaintained.select };
1589
+ return;
1590
+ }
1591
+ if (!declaredMaintained && liveMaintained) {
1592
+ // Detach: rows stay, maintenance stops, the table becomes writable.
1593
+ diff.dropMaintained = true;
1594
+ return;
1595
+ }
1596
+ // Both maintained — compare the canonical body hash (reconciling in-diff renames
1597
+ // so a pure source rename converges via the rename propagation, not a re-attach).
1598
+ if (maintainedBodyMatches(declaredMaintained, liveMaintained.bodyHash, tableRenames, columnRenamesByTable, schemaName, resolveDeclaredColumn)) {
1599
+ return; // no derivation change → no churn
1600
+ }
1601
+ // Body drift → re-attach. A concurrent shape change (column / PK / constraint
1602
+ // ops already on `diff`) means detach → reshape → re-attach; an unchanged shape
1603
+ // re-attaches in place (content reconcile / refresh). Carry the DECLARED rename
1604
+ // list so an explicit rename-list change (`(a, b)` → `(a, c)`) reshapes the
1605
+ // backing in place via the verb instead of erroring at the strict attach shape
1606
+ // check; `undefined` ⇒ the verb follows the body (implicit reshape).
1607
+ if (alterDiffHasShapeOps(diff))
1608
+ diff.dropMaintained = true;
1609
+ diff.setMaintained = { columns: declaredMaintained.columns, select: declaredMaintained.select };
1610
+ }
1611
+ /**
1612
+ * True when the declared maintained body canonicalizes to the live
1613
+ * `derivation.bodyHash` — i.e. an unchanged derivation (no re-attach).
1614
+ *
1615
+ * The recorded form is the as-authored `maintained.columns`: `undefined` for an
1616
+ * implicit body (MV sugar / `create table … maintained as` / the re-attach verb,
1617
+ * which now records implicit) and the names array for an explicit rename list
1618
+ * (`mv (a, b)` / `maintained (a, b) as`). Both the create path AND the re-attach
1619
+ * verb record the same implicit form for a body whose natural names already equal
1620
+ * the table columns, so a single as-authored variant suffices — there is no
1621
+ * create-vs-attach representation gap to absorb. The variant is also re-compared
1622
+ * under in-diff rename reconciliation so a pure source rename converges via the
1623
+ * rename propagation rather than churning a re-attach. A genuine body / clause edit
1624
+ * (including an explicit rename-list change b → c) fails the compare, so it is not
1625
+ * masked.
1626
+ */
1627
+ function maintainedBodyMatches(declared, liveBodyHash, tableRenames, columnRenamesByTable, schemaName,
1628
+ /** Declared-side column-existence resolver for the seeded defaults-expr rewrites (see `computeSchemaDiff`). */
1629
+ resolveDeclaredColumn) {
1630
+ const hasRenames = tableRenames.length > 0 || columnRenamesByTable.size > 0;
1631
+ // The single as-authored form: implicit (`undefined`) for a sugar / verb-attached
1632
+ // body, or the explicit declared rename list. Both create and re-attach record the
1633
+ // implicit form for a same-name body, so there is no live-names fallback to add.
1634
+ // The body string carries any trailing `with defaults (…)` clause, so a
1635
+ // defaults-only edit fails the compare and a pure source rename converges.
1636
+ const variants = [declared.columns];
1637
+ for (const columns of variants) {
1638
+ if (computeBodyHash(viewDefinitionToCanonicalString(columns, declared.select)) === liveBodyHash)
1639
+ return true;
1640
+ if (hasRenames
1641
+ && computeBodyHash(reconciledDeclaredViewDefinition(columns, declared.select, tableRenames, columnRenamesByTable, schemaName, resolveDeclaredColumn)) === liveBodyHash) {
1642
+ return true;
1643
+ }
1644
+ }
1645
+ return false;
1646
+ }
1647
+ /** True when a table-alter diff carries any structural (column / PK / constraint) op. */
1648
+ function alterDiffHasShapeOps(diff) {
1649
+ return diff.columnsToAdd.length > 0
1650
+ || diff.columnsToDrop.length > 0
1651
+ || diff.columnsToAlter.length > 0
1652
+ || diff.columnsToRename.length > 0
1653
+ || !!diff.primaryKeyChange
1654
+ || (diff.constraintsToAdd?.length ?? 0) > 0
1655
+ || (diff.constraintsToDrop?.length ?? 0) > 0
1656
+ || (diff.constraintsToRename?.length ?? 0) > 0;
1657
+ }
1658
+ /**
1659
+ * Normalize a declared `materialized view` item into the table category: a
1660
+ * declared table whose `maintained` clause carries the body and whose declared
1661
+ * column list is empty (the body owns the shape). This is what lets the differ
1662
+ * compare a maintained declaration per name in ONE category — a table↔maintained
1663
+ * transition becomes an attach/detach alter, never a cross-category drop+create.
1664
+ */
1665
+ function materializedViewToDeclaredTable(declaredMv) {
1666
+ const mv = declaredMv.viewStmt;
1667
+ return {
1668
+ type: 'declaredTable',
1669
+ tableStmt: {
1670
+ type: 'createTable',
1671
+ table: mv.view,
1672
+ ifNotExists: false,
1673
+ columns: [],
1674
+ constraints: [],
1675
+ moduleName: mv.moduleName,
1676
+ moduleArgs: mv.moduleArgs,
1677
+ tags: mv.tags,
1678
+ // Any `with defaults (…)` rides inside mv.select.
1679
+ maintained: { columns: mv.columns, select: mv.select },
1680
+ },
1681
+ };
1682
+ }
472
1683
  /**
473
1684
  * Extract a declared column's effective nullability from its AST constraints.
474
1685
  * Returns undefined when no explicit NULL/NOT NULL is present (session default applies).
@@ -493,6 +1704,42 @@ function extractDeclaredDefault(col) {
493
1704
  const d = col.constraints.find(c => c.type === 'default');
494
1705
  return d?.expr ?? null;
495
1706
  }
1707
+ /**
1708
+ * Extract a declared column's effective collation from its COLLATE constraint,
1709
+ * canonicalized (uppercase). When no COLLATE is declared, resolves the
1710
+ * `defaultCollation` exactly as the engine's CREATE path does
1711
+ * ({@link resolveDefaultCollation}) — so absent COLLATE and an explicit
1712
+ * `COLLATE <default>` compare equal against the actual catalog collation, and an
1713
+ * `apply schema` under a non-BINARY default stays idempotent (the live catalog
1714
+ * column already carries the resolved default; resolving the declared side to the
1715
+ * same value avoids a spurious `SET COLLATE`). `defaultCollation` is threaded from
1716
+ * the live session — the cross-session rehydrate concern stays fixed-BINARY on the
1717
+ * `importTable` path, not here.
1718
+ */
1719
+ function extractDeclaredCollation(col, defaultCollation) {
1720
+ const c = col.constraints?.find(c => c.type === 'collate');
1721
+ if (c)
1722
+ return c.collation ? normalizeCollationName(c.collation) : 'BINARY';
1723
+ return resolveDefaultCollation(inferType(col.dataType), defaultCollation);
1724
+ }
1725
+ /**
1726
+ * Returns `col` unchanged when it already declares an explicit COLLATE, or when the
1727
+ * session `default_collation` resolves to BINARY for its type; otherwise returns a
1728
+ * shallow clone with an explicit `{ type: 'collate', collation: <resolved> }`
1729
+ * constraint appended. Used by the ADD COLUMN emission so generated DDL carries an
1730
+ * explicit non-BINARY collation rather than relying on the executing session's
1731
+ * default — this is what makes `apply schema` idempotent and `diff schema` output
1732
+ * portable across sessions with different defaults. Never mutates the declared AST
1733
+ * (clones the `constraints` array).
1734
+ */
1735
+ function withResolvedAddColumnCollation(col, defaultCollation) {
1736
+ if (col.constraints?.some(c => c.type === 'collate'))
1737
+ return col;
1738
+ const resolved = resolveDefaultCollation(inferType(col.dataType), defaultCollation);
1739
+ if (resolved === 'BINARY')
1740
+ return col;
1741
+ return { ...col, constraints: [...(col.constraints ?? []), { type: 'collate', collation: resolved }] };
1742
+ }
496
1743
  /**
497
1744
  * Structural equality for DEFAULT expressions. Compares AST shape by
498
1745
  * JSON serialization with a stable key order — adequate for literals
@@ -514,7 +1761,38 @@ function stableStringify(v) {
514
1761
  const keys = Object.keys(obj).filter(k => k !== 'loc').sort();
515
1762
  return `{${keys.map(k => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',')}}`;
516
1763
  }
517
- function computeColumnAttributeChange(declared, actual) {
1764
+ /**
1765
+ * Rename-hint keys excluded from tag-drift comparison: they drive rename
1766
+ * detection ({@link resolveRenames}), not data state, so a tag set carrying only
1767
+ * a hint must not churn out a `SET TAGS` after the rename completes. All other
1768
+ * reserved tags (`quereus.lens.*`, `quereus.expose_implicit_index`, …) are real
1769
+ * schema state and ARE compared.
1770
+ */
1771
+ const RENAME_HINT_KEYS = new Set([QUEREUS_TAG_PREFIX + 'id', QUEREUS_TAG_PREFIX + 'previous_name']);
1772
+ /** A tag record with the rename-hint keys stripped, for drift comparison. */
1773
+ function tagsForDriftCompare(tags) {
1774
+ const out = {};
1775
+ if (tags) {
1776
+ for (const [k, v] of Object.entries(tags)) {
1777
+ if (!RENAME_HINT_KEYS.has(k.toLowerCase()))
1778
+ out[k] = v;
1779
+ }
1780
+ }
1781
+ return out;
1782
+ }
1783
+ /**
1784
+ * True when the declared and actual tag sets differ (order-independent, rename
1785
+ * hints ignored). On drift the differ emits a `SET TAGS` carrying the full
1786
+ * declared set (hints included — they are stored verbatim).
1787
+ */
1788
+ function tagsDrifted(declared, actual) {
1789
+ return stableStringify(tagsForDriftCompare(declared)) !== stableStringify(tagsForDriftCompare(actual));
1790
+ }
1791
+ /** The whole-set replacement value to emit for a drifted tag site (declared, or `{}`). */
1792
+ function desiredTagSet(declared) {
1793
+ return declared ? { ...declared } : {};
1794
+ }
1795
+ function computeColumnAttributeChange(declared, actual, defaultCollation) {
518
1796
  const change = { columnName: declared.name };
519
1797
  let any = false;
520
1798
  // Nullability — only compare when explicitly declared; session default handles unspecified.
@@ -542,6 +1820,19 @@ function computeColumnAttributeChange(declared, actual) {
542
1820
  change.defaultValue = null;
543
1821
  any = true;
544
1822
  }
1823
+ // Collation — declared COLLATE (default BINARY) vs actual, case-insensitive.
1824
+ // Absent and BINARY are equal, so a column that never mentions COLLATE never
1825
+ // churns a diff against an actual BINARY column.
1826
+ const declaredCollation = extractDeclaredCollation(declared, defaultCollation);
1827
+ if (declaredCollation !== (actual.collation || 'BINARY').toUpperCase()) {
1828
+ change.collation = declaredCollation;
1829
+ any = true;
1830
+ }
1831
+ // Tag drift — whole-set replacement (rename hints excluded from the compare).
1832
+ if (tagsDrifted(declared.tags, actual.tags)) {
1833
+ change.tags = desiredTagSet(declared.tags);
1834
+ any = true;
1835
+ }
545
1836
  return any ? change : undefined;
546
1837
  }
547
1838
  function extractDeclaredPK(declaredTable) {
@@ -632,22 +1923,32 @@ export function generateMigrationDDL(diff, schemaName) {
632
1923
  const schemaPrefix = (schemaName && schemaName !== 'main') ? `${quoteIdentifier(schemaName)}.` : '';
633
1924
  // Renames first — they free old names for subsequent creates and re-target
634
1925
  // dependents (handled inside ALTER TABLE ... RENAME by the rename rewriter).
635
- // Tables get a primitive ALTER TABLE RENAME; views/indexes/named constraints
636
- // have no engine primitive yet fall back to drop+recreate is left to the
637
- // caller (we surface only the rename op here for tables and the alter-diff
638
- // pipeline already drops+recreates non-table objects via diff.viewsToDrop /
639
- // indexesToDrop when no rename hint is present).
1926
+ // Only tables have a rename primitive. The other RenameOp kinds are metadata
1927
+ // here: a hinted view/index rename's convergence DDL is emitted by
1928
+ // computeSchemaDiff itself as drop(old) + recreate(declared) through the
1929
+ // standard viewsToDrop/viewsToCreate / indexesToDrop/indexesToCreate buckets
1930
+ // (whether or not the definition also changed), and a constraint rename rides
1931
+ // the table-alter channel (RENAME CONSTRAINT below).
640
1932
  for (const r of diff.renames) {
641
1933
  if (r.kind === 'table') {
642
1934
  statements.push(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(r.oldName)} RENAME TO ${quoteIdentifier(r.newName)}`);
643
1935
  }
644
- // View / index / constraint renames have no primitive caller emits
645
- // drop+recreate via the standard buckets.
1936
+ // Non-table rename ops emit no DDL here see the note above.
646
1937
  }
647
1938
  // Drop assertions first (they may reference tables)
648
1939
  for (const name of diff.assertionsToDrop) {
649
1940
  statements.push(`DROP ASSERTION IF EXISTS ${schemaPrefix}${quoteIdentifier(name)}`);
650
1941
  }
1942
+ // Detach maintained tables (`drop maintained`) EARLY — where MV drops ran
1943
+ // before — so a detach precedes any column op on that table AND any drop of its
1944
+ // former source in the same migration. This is the reshape leg of a re-attach
1945
+ // (detach → column ops → re-attach) and the standalone detach transition; the
1946
+ // flip's cross-table detach-v2 / attach-v1 pair falls out of this ordering.
1947
+ for (const alter of diff.tablesToAlter) {
1948
+ if (alter.dropMaintained) {
1949
+ statements.push(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(alter.tableName)} DROP MAINTAINED`);
1950
+ }
1951
+ }
651
1952
  // Drop items (reverse order)
652
1953
  for (const tableName of diff.tablesToDrop) {
653
1954
  statements.push(`DROP TABLE IF EXISTS ${schemaPrefix}${quoteIdentifier(tableName)}`);
@@ -658,7 +1959,9 @@ export function generateMigrationDDL(diff, schemaName) {
658
1959
  for (const indexName of diff.indexesToDrop) {
659
1960
  statements.push(`DROP INDEX IF EXISTS ${schemaPrefix}${quoteIdentifier(indexName)}`);
660
1961
  }
661
- // Create new items
1962
+ // Create new items. A fresh maintained table rides `tablesToCreate` (rendered
1963
+ // as the `create materialized view` sugar) and re-materializes as part of its
1964
+ // create.
662
1965
  statements.push(...diff.tablesToCreate);
663
1966
  statements.push(...diff.viewsToCreate);
664
1967
  statements.push(...diff.indexesToCreate);
@@ -669,7 +1972,10 @@ export function generateMigrationDDL(diff, schemaName) {
669
1972
  // → ADD COLUMN
670
1973
  // → ALTER COLUMN (type, then default, then nullability — so SET NOT NULL
671
1974
  // can rely on an already-populated DEFAULT for backfill)
1975
+ // → RENAME CONSTRAINT, then DROP CONSTRAINT (free / remove a name before any
1976
+ // re-add; a UNIQUE drop precedes the PK change so it can't strand a PK dep)
672
1977
  // → ALTER PRIMARY KEY
1978
+ // → ADD CONSTRAINT (after the PK change and the column adds it may reference)
673
1979
  // → DROP COLUMN (last, so NOT NULL relaxation never blocks subsequent drops)
674
1980
  for (const alter of diff.tablesToAlter) {
675
1981
  const quotedTable = `${schemaPrefix}${quoteIdentifier(alter.tableName)}`;
@@ -684,6 +1990,11 @@ export function generateMigrationDDL(diff, schemaName) {
684
1990
  if (colAlter.dataType !== undefined) {
685
1991
  statements.push(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET DATA TYPE ${colAlter.dataType}`);
686
1992
  }
1993
+ // SET COLLATE right after SET DATA TYPE (both are comparison-domain
1994
+ // changes), before DEFAULT / NOT NULL.
1995
+ if (colAlter.collation !== undefined) {
1996
+ statements.push(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET COLLATE ${colAlter.collation}`);
1997
+ }
687
1998
  if (colAlter.defaultValue !== undefined) {
688
1999
  if (colAlter.defaultValue === null) {
689
2000
  statements.push(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP DEFAULT`);
@@ -698,6 +2009,15 @@ export function generateMigrationDDL(diff, schemaName) {
698
2009
  : `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP NOT NULL`);
699
2010
  }
700
2011
  }
2012
+ // Constraint lifecycle: RENAME (free a name) then DROP (remove a stale /
2013
+ // conflicting constraint), both BEFORE re-adds and before the PK change so a
2014
+ // dropped UNIQUE can't strand a PK dependency.
2015
+ for (const r of alter.constraintsToRename ?? []) {
2016
+ statements.push(`ALTER TABLE ${quotedTable} RENAME CONSTRAINT ${quoteIdentifier(r.oldName)} TO ${quoteIdentifier(r.newName)}`);
2017
+ }
2018
+ for (const name of alter.constraintsToDrop ?? []) {
2019
+ statements.push(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quoteIdentifier(name)}`);
2020
+ }
701
2021
  if (alter.primaryKeyChange) {
702
2022
  const pkCols = alter.primaryKeyChange.newPkColumns
703
2023
  .map(c => {
@@ -709,9 +2029,71 @@ export function generateMigrationDDL(diff, schemaName) {
709
2029
  .join(', ');
710
2030
  statements.push(`ALTER TABLE ${quotedTable} ALTER PRIMARY KEY (${pkCols})`);
711
2031
  }
2032
+ // ADD CONSTRAINT after the PK change (a new UNIQUE / FK may align with the new
2033
+ // key) and after the column adds it may reference. CHECK adds apply in-place;
2034
+ // UNIQUE / FK adds depend on module ADD CONSTRAINT support (see constraintsToAdd).
2035
+ for (const frag of alter.constraintsToAdd ?? []) {
2036
+ statements.push(`ALTER TABLE ${quotedTable} ADD ${frag}`);
2037
+ }
712
2038
  for (const colName of alter.columnsToDrop) {
713
2039
  statements.push(`ALTER TABLE ${quotedTable} DROP COLUMN ${quoteIdentifier(colName)}`);
714
2040
  }
2041
+ // Tags phase — last, so a SET TAGS lands on the post-structural column /
2042
+ // constraint set (a tag set emitted alongside a RENAME COLUMN targets the
2043
+ // post-rename name). Whole-set replacement; an empty set clears.
2044
+ if (alter.tableTagsChange !== undefined) {
2045
+ // A maintained table's tag edit must use ALTER MATERIALIZED VIEW (fires
2046
+ // materialized_view_modified so a store catalog re-persists; the ALTER
2047
+ // TABLE handler rejects a tag action on a maintained table). See
2048
+ // `TableAlterDiff.maintainedTags`.
2049
+ const tagVerb = alter.maintainedTags ? 'ALTER MATERIALIZED VIEW' : 'ALTER TABLE';
2050
+ statements.push(`${tagVerb} ${quotedTable} SET TAGS ${tagsBodyToString(alter.tableTagsChange)}`);
2051
+ }
2052
+ for (const colAlter of alter.columnsToAlter) {
2053
+ if (colAlter.tags === undefined)
2054
+ continue;
2055
+ statements.push(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quoteIdentifier(colAlter.columnName)} SET TAGS ${tagsBodyToString(colAlter.tags)}`);
2056
+ }
2057
+ for (const ctc of alter.constraintTagsChanges ?? []) {
2058
+ statements.push(`ALTER TABLE ${quotedTable} ALTER CONSTRAINT ${quoteIdentifier(ctc.constraintName)} SET TAGS ${tagsBodyToString(ctc.tags)}`);
2059
+ }
2060
+ }
2061
+ // Attach / re-attach maintained tables (`set maintained as`) LATE — where MV
2062
+ // creates ran — after every table create/alter, so the target's final shape and
2063
+ // the body's sources already exist (and any reshape leg's `drop maintained` +
2064
+ // column ops have run). A same-shape re-attach reconciles content in place (a
2065
+ // refresh, not a recreate). Rendered via `astToString` over a synthetic
2066
+ // `set maintained as` action so the body QueryExpr round-trips exactly.
2067
+ for (const alter of diff.tablesToAlter) {
2068
+ if (!alter.setMaintained)
2069
+ continue;
2070
+ const stmt = {
2071
+ type: 'alterTable',
2072
+ table: schemaName && schemaName !== 'main'
2073
+ ? { type: 'identifier', name: alter.tableName, schema: schemaName }
2074
+ : { type: 'identifier', name: alter.tableName },
2075
+ action: {
2076
+ // Any `with defaults (…)` rides inside the body select. The `(cols)`
2077
+ // rename list rides too when the DECLARED form is explicit (`astToString`
2078
+ // renders it; `undefined` ⇒ the bare `set maintained as` implicit form).
2079
+ type: 'setMaintained',
2080
+ columns: alter.setMaintained.columns,
2081
+ select: alter.setMaintained.select,
2082
+ },
2083
+ };
2084
+ statements.push(astToString(stmt));
2085
+ }
2086
+ // In-place tag changes on views / indexes. These are leaf metadata writes (no
2087
+ // dependency ordering vs the table-alter block). The `?? []` keeps
2088
+ // generateMigrationDDL robust against hand-built diffs (some tests construct
2089
+ // partial SchemaDiff literals), mirroring `constraintTagsChanges`. A maintained
2090
+ // table's tag-only change rides the table-alter block above (`SET TAGS`), not a
2091
+ // separate bucket.
2092
+ for (const vtc of diff.viewTagsChanges ?? []) {
2093
+ statements.push(`ALTER VIEW ${schemaPrefix}${quoteIdentifier(vtc.name)} SET TAGS ${tagsBodyToString(vtc.tags)}`);
2094
+ }
2095
+ for (const itc of diff.indexTagsChanges ?? []) {
2096
+ statements.push(`ALTER INDEX ${schemaPrefix}${quoteIdentifier(itc.name)} SET TAGS ${tagsBodyToString(itc.tags)}`);
715
2097
  }
716
2098
  return statements;
717
2099
  }