@intentius/chant-lexicon-sql 0.0.0-stage → 0.112.1

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 (936) hide show
  1. package/README.md +39 -2
  2. package/dist/clickhouse/apply/apply.d.ts +82 -0
  3. package/dist/clickhouse/apply/apply.d.ts.map +1 -0
  4. package/dist/clickhouse/apply/mutations.d.ts +46 -0
  5. package/dist/clickhouse/apply/mutations.d.ts.map +1 -0
  6. package/dist/clickhouse/apply/statements.d.ts +99 -0
  7. package/dist/clickhouse/apply/statements.d.ts.map +1 -0
  8. package/dist/clickhouse/catalog-types.d.ts +76 -0
  9. package/dist/clickhouse/catalog-types.d.ts.map +1 -0
  10. package/dist/clickhouse/container.d.ts +63 -0
  11. package/dist/clickhouse/container.d.ts.map +1 -0
  12. package/dist/clickhouse/engine-syntax.d.ts +43 -0
  13. package/dist/clickhouse/engine-syntax.d.ts.map +1 -0
  14. package/dist/clickhouse/entities.d.ts +183 -0
  15. package/dist/clickhouse/entities.d.ts.map +1 -0
  16. package/dist/clickhouse/http.d.ts +40 -0
  17. package/dist/clickhouse/http.d.ts.map +1 -0
  18. package/dist/clickhouse/import/generator.d.ts +16 -0
  19. package/dist/clickhouse/import/generator.d.ts.map +1 -0
  20. package/dist/clickhouse/import/ir.d.ts +52 -0
  21. package/dist/clickhouse/import/ir.d.ts.map +1 -0
  22. package/dist/clickhouse/import/live-export.d.ts +28 -0
  23. package/dist/clickhouse/import/live-export.d.ts.map +1 -0
  24. package/dist/clickhouse/import/parser.d.ts +10 -0
  25. package/dist/clickhouse/import/parser.d.ts.map +1 -0
  26. package/dist/clickhouse/live/bind.d.ts +56 -0
  27. package/dist/clickhouse/live/bind.d.ts.map +1 -0
  28. package/dist/clickhouse/live/catalog.d.ts +39 -0
  29. package/dist/clickhouse/live/catalog.d.ts.map +1 -0
  30. package/dist/clickhouse/live/describe-resources.d.ts +34 -0
  31. package/dist/clickhouse/live/describe-resources.d.ts.map +1 -0
  32. package/dist/clickhouse/overlays/codecs.d.ts +28 -0
  33. package/dist/clickhouse/overlays/codecs.d.ts.map +1 -0
  34. package/dist/clickhouse/overlays/engines.d.ts +20 -0
  35. package/dist/clickhouse/overlays/engines.d.ts.map +1 -0
  36. package/dist/clickhouse/overlays/kinds.d.ts +38 -0
  37. package/dist/clickhouse/overlays/kinds.d.ts.map +1 -0
  38. package/dist/clickhouse/overlays/projections.d.ts +27 -0
  39. package/dist/clickhouse/overlays/projections.d.ts.map +1 -0
  40. package/dist/clickhouse/overlays/skip-indexes.d.ts +10 -0
  41. package/dist/clickhouse/overlays/skip-indexes.d.ts.map +1 -0
  42. package/dist/clickhouse/overlays/ttl.d.ts +35 -0
  43. package/dist/clickhouse/overlays/ttl.d.ts.map +1 -0
  44. package/dist/clickhouse/overlays/types.d.ts +21 -0
  45. package/dist/clickhouse/overlays/types.d.ts.map +1 -0
  46. package/dist/clickhouse/ownership.d.ts +71 -0
  47. package/dist/clickhouse/ownership.d.ts.map +1 -0
  48. package/dist/clickhouse/parser.d.ts +130 -0
  49. package/dist/clickhouse/parser.d.ts.map +1 -0
  50. package/dist/clickhouse/plan/commands.d.ts +26 -0
  51. package/dist/clickhouse/plan/commands.d.ts.map +1 -0
  52. package/dist/clickhouse/plan/deep.d.ts +32 -0
  53. package/dist/clickhouse/plan/deep.d.ts.map +1 -0
  54. package/dist/clickhouse/plan/diff.d.ts +37 -0
  55. package/dist/clickhouse/plan/diff.d.ts.map +1 -0
  56. package/dist/clickhouse/plan/disruption.d.ts +17 -0
  57. package/dist/clickhouse/plan/disruption.d.ts.map +1 -0
  58. package/dist/clickhouse/plan/normalize.d.ts +97 -0
  59. package/dist/clickhouse/plan/normalize.d.ts.map +1 -0
  60. package/dist/clickhouse/plan/rebuild-handoff.d.ts +38 -0
  61. package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -0
  62. package/dist/clickhouse/plan/report.d.ts +6 -0
  63. package/dist/clickhouse/plan/report.d.ts.map +1 -0
  64. package/dist/clickhouse/plan/rules.d.ts +60 -0
  65. package/dist/clickhouse/plan/rules.d.ts.map +1 -0
  66. package/dist/clickhouse/plan/schema.d.ts +23 -0
  67. package/dist/clickhouse/plan/schema.d.ts.map +1 -0
  68. package/dist/clickhouse/plan/server-format.d.ts +12 -0
  69. package/dist/clickhouse/plan/server-format.d.ts.map +1 -0
  70. package/dist/clickhouse/rebuild/backfill.d.ts +70 -0
  71. package/dist/clickhouse/rebuild/backfill.d.ts.map +1 -0
  72. package/dist/clickhouse/rebuild/observe.d.ts +157 -0
  73. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -0
  74. package/dist/clickhouse/rebuild/op.d.ts +108 -0
  75. package/dist/clickhouse/rebuild/op.d.ts.map +1 -0
  76. package/dist/clickhouse/rebuild/partitions.d.ts +24 -0
  77. package/dist/clickhouse/rebuild/partitions.d.ts.map +1 -0
  78. package/dist/clickhouse/rebuild/receipts.d.ts +76 -0
  79. package/dist/clickhouse/rebuild/receipts.d.ts.map +1 -0
  80. package/dist/clickhouse/rebuild/replicas.d.ts +78 -0
  81. package/dist/clickhouse/rebuild/replicas.d.ts.map +1 -0
  82. package/dist/clickhouse/rebuild/steps.d.ts +151 -0
  83. package/dist/clickhouse/rebuild/steps.d.ts.map +1 -0
  84. package/dist/clickhouse/rebuild/verify.d.ts +51 -0
  85. package/dist/clickhouse/rebuild/verify.d.ts.map +1 -0
  86. package/dist/clickhouse/testing/fake-server.d.ts +27 -0
  87. package/dist/clickhouse/testing/fake-server.d.ts.map +1 -0
  88. package/dist/clickhouse/testing/roundtrip-schema.d.ts +3 -0
  89. package/dist/clickhouse/testing/roundtrip-schema.d.ts.map +1 -0
  90. package/dist/clickhouse/testing/writable-server.d.ts +35 -0
  91. package/dist/clickhouse/testing/writable-server.d.ts.map +1 -0
  92. package/dist/clickhouse/tokens.d.ts +15 -0
  93. package/dist/clickhouse/tokens.d.ts.map +1 -0
  94. package/dist/clickhouse.d.ts +24 -0
  95. package/dist/clickhouse.d.ts.map +1 -0
  96. package/dist/codegen/classifier-page.d.ts +6 -0
  97. package/dist/codegen/classifier-page.d.ts.map +1 -0
  98. package/dist/codegen/clickhouse-module.d.ts +23 -0
  99. package/dist/codegen/clickhouse-module.d.ts.map +1 -0
  100. package/dist/codegen/docs-cli.d.ts +3 -0
  101. package/dist/codegen/docs-cli.d.ts.map +1 -0
  102. package/dist/codegen/docs.d.ts +8 -0
  103. package/dist/codegen/docs.d.ts.map +1 -0
  104. package/dist/codegen/generate-cli.d.ts +3 -0
  105. package/dist/codegen/generate-cli.d.ts.map +1 -0
  106. package/dist/codegen/generate.d.ts +40 -0
  107. package/dist/codegen/generate.d.ts.map +1 -0
  108. package/dist/codegen/package.d.ts +7 -0
  109. package/dist/codegen/package.d.ts.map +1 -0
  110. package/dist/codegen/postgres-classifier-page.d.ts +7 -0
  111. package/dist/codegen/postgres-classifier-page.d.ts.map +1 -0
  112. package/dist/codegen/postgres-module.d.ts +30 -0
  113. package/dist/codegen/postgres-module.d.ts.map +1 -0
  114. package/dist/codegen/registry.d.ts +12 -0
  115. package/dist/codegen/registry.d.ts.map +1 -0
  116. package/dist/composites/audit-log-table.d.ts +31 -0
  117. package/dist/composites/audit-log-table.d.ts.map +1 -0
  118. package/dist/composites/catalog.d.ts +3 -0
  119. package/dist/composites/catalog.d.ts.map +1 -0
  120. package/dist/composites/cdc-mirror.d.ts +32 -0
  121. package/dist/composites/cdc-mirror.d.ts.map +1 -0
  122. package/dist/composites/clickhouse.d.ts +7 -0
  123. package/dist/composites/clickhouse.d.ts.map +1 -0
  124. package/dist/composites/events-table.d.ts +32 -0
  125. package/dist/composites/events-table.d.ts.map +1 -0
  126. package/dist/composites/index.d.ts +9 -0
  127. package/dist/composites/index.d.ts.map +1 -0
  128. package/dist/composites/join-table.d.ts +41 -0
  129. package/dist/composites/join-table.d.ts.map +1 -0
  130. package/dist/composites/postgres.d.ts +7 -0
  131. package/dist/composites/postgres.d.ts.map +1 -0
  132. package/dist/composites/refreshed-view.d.ts +32 -0
  133. package/dist/composites/refreshed-view.d.ts.map +1 -0
  134. package/dist/composites/replacing-table.d.ts +34 -0
  135. package/dist/composites/replacing-table.d.ts.map +1 -0
  136. package/dist/composites/rollup-view.d.ts +39 -0
  137. package/dist/composites/rollup-view.d.ts.map +1 -0
  138. package/dist/composites/sharded-table.d.ts +35 -0
  139. package/dist/composites/sharded-table.d.ts.map +1 -0
  140. package/dist/composites/soft-delete-table.d.ts +40 -0
  141. package/dist/composites/soft-delete-table.d.ts.map +1 -0
  142. package/dist/composites/tenant-table.d.ts +34 -0
  143. package/dist/composites/tenant-table.d.ts.map +1 -0
  144. package/dist/config.d.ts +213 -0
  145. package/dist/config.d.ts.map +1 -0
  146. package/dist/core/apply.d.ts +89 -0
  147. package/dist/core/apply.d.ts.map +1 -0
  148. package/dist/core/classifier.d.ts +88 -0
  149. package/dist/core/classifier.d.ts.map +1 -0
  150. package/dist/core/container.d.ts +45 -0
  151. package/dist/core/container.d.ts.map +1 -0
  152. package/dist/core/cursor.d.ts +67 -0
  153. package/dist/core/cursor.d.ts.map +1 -0
  154. package/dist/core/diff.d.ts +49 -0
  155. package/dist/core/diff.d.ts.map +1 -0
  156. package/dist/core/entity.d.ts +41 -0
  157. package/dist/core/entity.d.ts.map +1 -0
  158. package/dist/core/find-templates.d.ts +69 -0
  159. package/dist/core/find-templates.d.ts.map +1 -0
  160. package/dist/core/handoff.d.ts +36 -0
  161. package/dist/core/handoff.d.ts.map +1 -0
  162. package/dist/core/index.d.ts +61 -0
  163. package/dist/core/index.d.ts.map +1 -0
  164. package/dist/core/interpolation.d.ts +88 -0
  165. package/dist/core/interpolation.d.ts.map +1 -0
  166. package/dist/core/normalize.d.ts +18 -0
  167. package/dist/core/normalize.d.ts.map +1 -0
  168. package/dist/core/ownership.d.ts +60 -0
  169. package/dist/core/ownership.d.ts.map +1 -0
  170. package/dist/core/receipts.d.ts +28 -0
  171. package/dist/core/receipts.d.ts.map +1 -0
  172. package/dist/core/references.d.ts +37 -0
  173. package/dist/core/references.d.ts.map +1 -0
  174. package/dist/core/template.d.ts +41 -0
  175. package/dist/core/template.d.ts.map +1 -0
  176. package/dist/core/testing/http-stub.d.ts +17 -0
  177. package/dist/core/testing/http-stub.d.ts.map +1 -0
  178. package/dist/core/tokens.d.ts +102 -0
  179. package/dist/core/tokens.d.ts.map +1 -0
  180. package/dist/coverage.d.ts +25 -0
  181. package/dist/coverage.d.ts.map +1 -0
  182. package/dist/detect.d.ts +15 -0
  183. package/dist/detect.d.ts.map +1 -0
  184. package/dist/dialects.d.ts +20 -0
  185. package/dist/dialects.d.ts.map +1 -0
  186. package/dist/generated/clickhouse-types.d.ts +4380 -0
  187. package/dist/generated/clickhouse-types.d.ts.map +1 -0
  188. package/dist/generated/clickhouse.d.ts +11 -0
  189. package/dist/generated/clickhouse.d.ts.map +1 -0
  190. package/dist/generated/postgres-types.d.ts +1579 -0
  191. package/dist/generated/postgres-types.d.ts.map +1 -0
  192. package/dist/generated/postgres.d.ts +9 -0
  193. package/dist/generated/postgres.d.ts.map +1 -0
  194. package/dist/import-generator.d.ts +8 -0
  195. package/dist/import-generator.d.ts.map +1 -0
  196. package/dist/index.d.ts +7 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/init-templates.d.ts +20 -0
  199. package/dist/init-templates.d.ts.map +1 -0
  200. package/dist/integrity.json +61 -0
  201. package/dist/lint/audit-catalog.d.ts +10 -0
  202. package/dist/lint/audit-catalog.d.ts.map +1 -0
  203. package/dist/lint/audit-lineage.d.ts +25 -0
  204. package/dist/lint/audit-lineage.d.ts.map +1 -0
  205. package/dist/lint/post-synth/audit-entries.d.ts +8 -0
  206. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -0
  207. package/dist/lint/post-synth/clickhouse-helpers.d.ts +85 -0
  208. package/dist/lint/post-synth/clickhouse-helpers.d.ts.map +1 -0
  209. package/dist/lint/post-synth/index.d.ts +3 -0
  210. package/dist/lint/post-synth/index.d.ts.map +1 -0
  211. package/dist/lint/post-synth/postgres-audit-entries.d.ts +9 -0
  212. package/dist/lint/post-synth/postgres-audit-entries.d.ts.map +1 -0
  213. package/dist/lint/post-synth/postgres-helpers.d.ts +89 -0
  214. package/dist/lint/post-synth/postgres-helpers.d.ts.map +1 -0
  215. package/dist/lint/post-synth/sql-helpers.d.ts +27 -0
  216. package/dist/lint/post-synth/sql-helpers.d.ts.map +1 -0
  217. package/dist/lint/post-synth/sqlch101.d.ts +12 -0
  218. package/dist/lint/post-synth/sqlch101.d.ts.map +1 -0
  219. package/dist/lint/post-synth/sqlch102.d.ts +9 -0
  220. package/dist/lint/post-synth/sqlch102.d.ts.map +1 -0
  221. package/dist/lint/post-synth/sqlch103.d.ts +10 -0
  222. package/dist/lint/post-synth/sqlch103.d.ts.map +1 -0
  223. package/dist/lint/post-synth/sqlch104.d.ts +9 -0
  224. package/dist/lint/post-synth/sqlch104.d.ts.map +1 -0
  225. package/dist/lint/post-synth/sqlch105.d.ts +10 -0
  226. package/dist/lint/post-synth/sqlch105.d.ts.map +1 -0
  227. package/dist/lint/post-synth/sqlch106.d.ts +11 -0
  228. package/dist/lint/post-synth/sqlch106.d.ts.map +1 -0
  229. package/dist/lint/post-synth/sqlch107.d.ts +10 -0
  230. package/dist/lint/post-synth/sqlch107.d.ts.map +1 -0
  231. package/dist/lint/post-synth/sqlch108.d.ts +11 -0
  232. package/dist/lint/post-synth/sqlch108.d.ts.map +1 -0
  233. package/dist/lint/post-synth/sqlch109.d.ts +10 -0
  234. package/dist/lint/post-synth/sqlch109.d.ts.map +1 -0
  235. package/dist/lint/post-synth/sqlch110.d.ts +12 -0
  236. package/dist/lint/post-synth/sqlch110.d.ts.map +1 -0
  237. package/dist/lint/post-synth/sqlch111.d.ts +11 -0
  238. package/dist/lint/post-synth/sqlch111.d.ts.map +1 -0
  239. package/dist/lint/post-synth/sqlch112.d.ts +12 -0
  240. package/dist/lint/post-synth/sqlch112.d.ts.map +1 -0
  241. package/dist/lint/post-synth/sqlch113.d.ts +9 -0
  242. package/dist/lint/post-synth/sqlch113.d.ts.map +1 -0
  243. package/dist/lint/post-synth/sqlch114.d.ts +8 -0
  244. package/dist/lint/post-synth/sqlch114.d.ts.map +1 -0
  245. package/dist/lint/post-synth/sqlch115.d.ts +13 -0
  246. package/dist/lint/post-synth/sqlch115.d.ts.map +1 -0
  247. package/dist/lint/post-synth/sqlch116.d.ts +10 -0
  248. package/dist/lint/post-synth/sqlch116.d.ts.map +1 -0
  249. package/dist/lint/post-synth/sqlch117.d.ts +9 -0
  250. package/dist/lint/post-synth/sqlch117.d.ts.map +1 -0
  251. package/dist/lint/post-synth/sqlch118.d.ts +10 -0
  252. package/dist/lint/post-synth/sqlch118.d.ts.map +1 -0
  253. package/dist/lint/post-synth/sqlch119.d.ts +11 -0
  254. package/dist/lint/post-synth/sqlch119.d.ts.map +1 -0
  255. package/dist/lint/post-synth/sqlch120.d.ts +10 -0
  256. package/dist/lint/post-synth/sqlch120.d.ts.map +1 -0
  257. package/dist/lint/post-synth/sqlpg101.d.ts +12 -0
  258. package/dist/lint/post-synth/sqlpg101.d.ts.map +1 -0
  259. package/dist/lint/post-synth/sqlpg102.d.ts +11 -0
  260. package/dist/lint/post-synth/sqlpg102.d.ts.map +1 -0
  261. package/dist/lint/post-synth/sqlpg103.d.ts +11 -0
  262. package/dist/lint/post-synth/sqlpg103.d.ts.map +1 -0
  263. package/dist/lint/post-synth/sqlpg104.d.ts +10 -0
  264. package/dist/lint/post-synth/sqlpg104.d.ts.map +1 -0
  265. package/dist/lint/post-synth/sqlpg105.d.ts +10 -0
  266. package/dist/lint/post-synth/sqlpg105.d.ts.map +1 -0
  267. package/dist/lint/post-synth/sqlpg106.d.ts +9 -0
  268. package/dist/lint/post-synth/sqlpg106.d.ts.map +1 -0
  269. package/dist/lint/post-synth/sqlpg107.d.ts +10 -0
  270. package/dist/lint/post-synth/sqlpg107.d.ts.map +1 -0
  271. package/dist/lint/post-synth/sqlpg108.d.ts +11 -0
  272. package/dist/lint/post-synth/sqlpg108.d.ts.map +1 -0
  273. package/dist/lint/post-synth/sqlpg109.d.ts +11 -0
  274. package/dist/lint/post-synth/sqlpg109.d.ts.map +1 -0
  275. package/dist/lint/post-synth/sqlpg110.d.ts +12 -0
  276. package/dist/lint/post-synth/sqlpg110.d.ts.map +1 -0
  277. package/dist/lint/post-synth/sqlpg111.d.ts +10 -0
  278. package/dist/lint/post-synth/sqlpg111.d.ts.map +1 -0
  279. package/dist/lint/post-synth/sqlpg112.d.ts +10 -0
  280. package/dist/lint/post-synth/sqlpg112.d.ts.map +1 -0
  281. package/dist/lint/post-synth/sqlpg113.d.ts +11 -0
  282. package/dist/lint/post-synth/sqlpg113.d.ts.map +1 -0
  283. package/dist/lint/post-synth/sqlpg114.d.ts +13 -0
  284. package/dist/lint/post-synth/sqlpg114.d.ts.map +1 -0
  285. package/dist/lint/post-synth/sqlpg115.d.ts +13 -0
  286. package/dist/lint/post-synth/sqlpg115.d.ts.map +1 -0
  287. package/dist/lint/post-synth/sqlpg116.d.ts +11 -0
  288. package/dist/lint/post-synth/sqlpg116.d.ts.map +1 -0
  289. package/dist/lint/post-synth/sqlpg117.d.ts +12 -0
  290. package/dist/lint/post-synth/sqlpg117.d.ts.map +1 -0
  291. package/dist/lint/post-synth/sqlpg118.d.ts +12 -0
  292. package/dist/lint/post-synth/sqlpg118.d.ts.map +1 -0
  293. package/dist/lint/rules/index.d.ts +12 -0
  294. package/dist/lint/rules/index.d.ts.map +1 -0
  295. package/dist/lint/rules/postgres-templates.d.ts +18 -0
  296. package/dist/lint/rules/postgres-templates.d.ts.map +1 -0
  297. package/dist/lint/rules/sqlch001.d.ts +12 -0
  298. package/dist/lint/rules/sqlch001.d.ts.map +1 -0
  299. package/dist/lint/rules/sqlch002.d.ts +11 -0
  300. package/dist/lint/rules/sqlch002.d.ts.map +1 -0
  301. package/dist/lint/rules/sqlch003.d.ts +12 -0
  302. package/dist/lint/rules/sqlch003.d.ts.map +1 -0
  303. package/dist/lint/rules/sqlpg001.d.ts +16 -0
  304. package/dist/lint/rules/sqlpg001.d.ts.map +1 -0
  305. package/dist/lint/rules/sqlpg002.d.ts +12 -0
  306. package/dist/lint/rules/sqlpg002.d.ts.map +1 -0
  307. package/dist/lint/rules/sqlpg003.d.ts +16 -0
  308. package/dist/lint/rules/sqlpg003.d.ts.map +1 -0
  309. package/dist/lint/rules/sqlpg004.d.ts +21 -0
  310. package/dist/lint/rules/sqlpg004.d.ts.map +1 -0
  311. package/dist/lint/rules/templates.d.ts +23 -0
  312. package/dist/lint/rules/templates.d.ts.map +1 -0
  313. package/dist/live-dialect.d.ts +25 -0
  314. package/dist/live-dialect.d.ts.map +1 -0
  315. package/dist/lsp/catalog.d.ts +22 -0
  316. package/dist/lsp/catalog.d.ts.map +1 -0
  317. package/dist/lsp/completions.d.ts +13 -0
  318. package/dist/lsp/completions.d.ts.map +1 -0
  319. package/dist/lsp/hover.d.ts +12 -0
  320. package/dist/lsp/hover.d.ts.map +1 -0
  321. package/dist/lsp/postgres-catalog.d.ts +85 -0
  322. package/dist/lsp/postgres-catalog.d.ts.map +1 -0
  323. package/dist/lsp/postgres-template.d.ts +35 -0
  324. package/dist/lsp/postgres-template.d.ts.map +1 -0
  325. package/dist/lsp/postgres.d.ts +20 -0
  326. package/dist/lsp/postgres.d.ts.map +1 -0
  327. package/dist/lsp/refs.d.ts +8 -0
  328. package/dist/lsp/refs.d.ts.map +1 -0
  329. package/dist/lsp/registry.d.ts +4 -0
  330. package/dist/lsp/registry.d.ts.map +1 -0
  331. package/dist/lsp/template.d.ts +34 -0
  332. package/dist/lsp/template.d.ts.map +1 -0
  333. package/dist/manifest.json +8 -0
  334. package/dist/mcp/index.d.ts +13 -0
  335. package/dist/mcp/index.d.ts.map +1 -0
  336. package/dist/mcp/postgres.d.ts +32 -0
  337. package/dist/mcp/postgres.d.ts.map +1 -0
  338. package/dist/meta.json +67 -0
  339. package/dist/okf/index.md +67 -0
  340. package/dist/okf/rules/SQLCH001.md +11 -0
  341. package/dist/okf/rules/SQLCH002.md +11 -0
  342. package/dist/okf/rules/SQLCH003.md +11 -0
  343. package/dist/okf/rules/SQLCH101.md +15 -0
  344. package/dist/okf/rules/SQLCH102.md +11 -0
  345. package/dist/okf/rules/SQLCH103.md +11 -0
  346. package/dist/okf/rules/SQLCH104.md +11 -0
  347. package/dist/okf/rules/SQLCH105.md +11 -0
  348. package/dist/okf/rules/SQLCH106.md +11 -0
  349. package/dist/okf/rules/SQLCH107.md +11 -0
  350. package/dist/okf/rules/SQLCH108.md +15 -0
  351. package/dist/okf/rules/SQLCH109.md +11 -0
  352. package/dist/okf/rules/SQLCH110.md +11 -0
  353. package/dist/okf/rules/SQLCH111.md +11 -0
  354. package/dist/okf/rules/SQLCH112.md +11 -0
  355. package/dist/okf/rules/SQLCH113.md +11 -0
  356. package/dist/okf/rules/SQLCH114.md +11 -0
  357. package/dist/okf/rules/SQLCH115.md +11 -0
  358. package/dist/okf/rules/SQLCH116.md +11 -0
  359. package/dist/okf/rules/SQLCH117.md +15 -0
  360. package/dist/okf/rules/SQLCH118.md +15 -0
  361. package/dist/okf/rules/SQLCH119.md +15 -0
  362. package/dist/okf/rules/SQLCH120.md +15 -0
  363. package/dist/okf/rules/SQLPG001.md +11 -0
  364. package/dist/okf/rules/SQLPG002.md +11 -0
  365. package/dist/okf/rules/SQLPG003.md +11 -0
  366. package/dist/okf/rules/SQLPG004.md +11 -0
  367. package/dist/okf/rules/SQLPG101.md +11 -0
  368. package/dist/okf/rules/SQLPG102.md +11 -0
  369. package/dist/okf/rules/SQLPG103.md +11 -0
  370. package/dist/okf/rules/SQLPG104.md +11 -0
  371. package/dist/okf/rules/SQLPG105.md +11 -0
  372. package/dist/okf/rules/SQLPG106.md +11 -0
  373. package/dist/okf/rules/SQLPG107.md +11 -0
  374. package/dist/okf/rules/SQLPG108.md +11 -0
  375. package/dist/okf/rules/SQLPG109.md +11 -0
  376. package/dist/okf/rules/SQLPG110.md +11 -0
  377. package/dist/okf/rules/SQLPG111.md +11 -0
  378. package/dist/okf/rules/SQLPG112.md +11 -0
  379. package/dist/okf/rules/SQLPG113.md +24 -0
  380. package/dist/okf/rules/SQLPG114.md +11 -0
  381. package/dist/okf/rules/SQLPG115.md +11 -0
  382. package/dist/okf/rules/SQLPG116.md +15 -0
  383. package/dist/okf/rules/SQLPG117.md +11 -0
  384. package/dist/okf/rules/SQLPG118.md +11 -0
  385. package/dist/okf/types/Database.md +15 -0
  386. package/dist/okf/types/MaterializedView.md +14 -0
  387. package/dist/okf/types/PostgresDomain.md +13 -0
  388. package/dist/okf/types/PostgresEnum.md +13 -0
  389. package/dist/okf/types/PostgresExtension.md +13 -0
  390. package/dist/okf/types/PostgresIndex.md +9 -0
  391. package/dist/okf/types/PostgresMaterializedView.md +13 -0
  392. package/dist/okf/types/PostgresSchema.md +13 -0
  393. package/dist/okf/types/PostgresSequence.md +13 -0
  394. package/dist/okf/types/PostgresTable.md +13 -0
  395. package/dist/okf/types/PostgresView.md +13 -0
  396. package/dist/okf/types/Table.md +15 -0
  397. package/dist/okf/types/View.md +13 -0
  398. package/dist/op/activities/clickhouse-apply.d.ts +56 -0
  399. package/dist/op/activities/clickhouse-apply.d.ts.map +1 -0
  400. package/dist/op/activities/clickhouse-emulator.d.ts +26 -0
  401. package/dist/op/activities/clickhouse-emulator.d.ts.map +1 -0
  402. package/dist/op/activities/clickhouse-rebuild.d.ts +43 -0
  403. package/dist/op/activities/clickhouse-rebuild.d.ts.map +1 -0
  404. package/dist/op/activities/index.d.ts +19 -0
  405. package/dist/op/activities/index.d.ts.map +1 -0
  406. package/dist/op/activities/postgres-apply.d.ts +69 -0
  407. package/dist/op/activities/postgres-apply.d.ts.map +1 -0
  408. package/dist/op/activities/postgres-emulator.d.ts +33 -0
  409. package/dist/op/activities/postgres-emulator.d.ts.map +1 -0
  410. package/dist/op/activities/postgres-migration.d.ts +48 -0
  411. package/dist/op/activities/postgres-migration.d.ts.map +1 -0
  412. package/dist/op/activity-contracts.d.ts +464 -0
  413. package/dist/op/activity-contracts.d.ts.map +1 -0
  414. package/dist/package-cli.d.ts +3 -0
  415. package/dist/package-cli.d.ts.map +1 -0
  416. package/dist/plugin.d.ts +12 -0
  417. package/dist/plugin.d.ts.map +1 -0
  418. package/dist/postgres/apply/apply.d.ts +163 -0
  419. package/dist/postgres/apply/apply.d.ts.map +1 -0
  420. package/dist/postgres/apply/statements.d.ts +138 -0
  421. package/dist/postgres/apply/statements.d.ts.map +1 -0
  422. package/dist/postgres/catalog-types.d.ts +50 -0
  423. package/dist/postgres/catalog-types.d.ts.map +1 -0
  424. package/dist/postgres/container.d.ts +23 -0
  425. package/dist/postgres/container.d.ts.map +1 -0
  426. package/dist/postgres/entities.d.ts +322 -0
  427. package/dist/postgres/entities.d.ts.map +1 -0
  428. package/dist/postgres/entity-types.d.ts +17 -0
  429. package/dist/postgres/entity-types.d.ts.map +1 -0
  430. package/dist/postgres/import/generator.d.ts +28 -0
  431. package/dist/postgres/import/generator.d.ts.map +1 -0
  432. package/dist/postgres/import/ir.d.ts +24 -0
  433. package/dist/postgres/import/ir.d.ts.map +1 -0
  434. package/dist/postgres/import/live-export.d.ts +26 -0
  435. package/dist/postgres/import/live-export.d.ts.map +1 -0
  436. package/dist/postgres/keywords.d.ts +22 -0
  437. package/dist/postgres/keywords.d.ts.map +1 -0
  438. package/dist/postgres/live/bind.d.ts +76 -0
  439. package/dist/postgres/live/bind.d.ts.map +1 -0
  440. package/dist/postgres/live/catalog.d.ts +98 -0
  441. package/dist/postgres/live/catalog.d.ts.map +1 -0
  442. package/dist/postgres/live/client.d.ts +32 -0
  443. package/dist/postgres/live/client.d.ts.map +1 -0
  444. package/dist/postgres/live/describe-resources.d.ts +41 -0
  445. package/dist/postgres/live/describe-resources.d.ts.map +1 -0
  446. package/dist/postgres/migrate/backfill.d.ts +103 -0
  447. package/dist/postgres/migrate/backfill.d.ts.map +1 -0
  448. package/dist/postgres/migrate/batches.d.ts +90 -0
  449. package/dist/postgres/migrate/batches.d.ts.map +1 -0
  450. package/dist/postgres/migrate/carry.d.ts +196 -0
  451. package/dist/postgres/migrate/carry.d.ts.map +1 -0
  452. package/dist/postgres/migrate/handoff.d.ts +46 -0
  453. package/dist/postgres/migrate/handoff.d.ts.map +1 -0
  454. package/dist/postgres/migrate/names.d.ts +55 -0
  455. package/dist/postgres/migrate/names.d.ts.map +1 -0
  456. package/dist/postgres/migrate/observe.d.ts +139 -0
  457. package/dist/postgres/migrate/observe.d.ts.map +1 -0
  458. package/dist/postgres/migrate/op.d.ts +128 -0
  459. package/dist/postgres/migrate/op.d.ts.map +1 -0
  460. package/dist/postgres/migrate/receipts.d.ts +63 -0
  461. package/dist/postgres/migrate/receipts.d.ts.map +1 -0
  462. package/dist/postgres/migrate/replication.d.ts +80 -0
  463. package/dist/postgres/migrate/replication.d.ts.map +1 -0
  464. package/dist/postgres/migrate/steps.d.ts +225 -0
  465. package/dist/postgres/migrate/steps.d.ts.map +1 -0
  466. package/dist/postgres/overlays/columns.d.ts +6 -0
  467. package/dist/postgres/overlays/columns.d.ts.map +1 -0
  468. package/dist/postgres/overlays/constraints.d.ts +8 -0
  469. package/dist/postgres/overlays/constraints.d.ts.map +1 -0
  470. package/dist/postgres/overlays/indexes.d.ts +8 -0
  471. package/dist/postgres/overlays/indexes.d.ts.map +1 -0
  472. package/dist/postgres/overlays/kinds.d.ts +48 -0
  473. package/dist/postgres/overlays/kinds.d.ts.map +1 -0
  474. package/dist/postgres/overlays/partitioning.d.ts +5 -0
  475. package/dist/postgres/overlays/partitioning.d.ts.map +1 -0
  476. package/dist/postgres/overlays/relations.d.ts +6 -0
  477. package/dist/postgres/overlays/relations.d.ts.map +1 -0
  478. package/dist/postgres/overlays/storage.d.ts +28 -0
  479. package/dist/postgres/overlays/storage.d.ts.map +1 -0
  480. package/dist/postgres/overlays/type-ddl.d.ts +7 -0
  481. package/dist/postgres/overlays/type-ddl.d.ts.map +1 -0
  482. package/dist/postgres/overlays/types.d.ts +24 -0
  483. package/dist/postgres/overlays/types.d.ts.map +1 -0
  484. package/dist/postgres/parser.d.ts +216 -0
  485. package/dist/postgres/parser.d.ts.map +1 -0
  486. package/dist/postgres/plan/commands.d.ts +61 -0
  487. package/dist/postgres/plan/commands.d.ts.map +1 -0
  488. package/dist/postgres/plan/deep.d.ts +33 -0
  489. package/dist/postgres/plan/deep.d.ts.map +1 -0
  490. package/dist/postgres/plan/diff.d.ts +45 -0
  491. package/dist/postgres/plan/diff.d.ts.map +1 -0
  492. package/dist/postgres/plan/disruption.d.ts +14 -0
  493. package/dist/postgres/plan/disruption.d.ts.map +1 -0
  494. package/dist/postgres/plan/normalize.d.ts +108 -0
  495. package/dist/postgres/plan/normalize.d.ts.map +1 -0
  496. package/dist/postgres/plan/report.d.ts +6 -0
  497. package/dist/postgres/plan/report.d.ts.map +1 -0
  498. package/dist/postgres/plan/rules.d.ts +84 -0
  499. package/dist/postgres/plan/rules.d.ts.map +1 -0
  500. package/dist/postgres/plan/schema.d.ts +50 -0
  501. package/dist/postgres/plan/schema.d.ts.map +1 -0
  502. package/dist/postgres/plan/server-normalize.d.ts +26 -0
  503. package/dist/postgres/plan/server-normalize.d.ts.map +1 -0
  504. package/dist/postgres/providers/aurora.d.ts +12 -0
  505. package/dist/postgres/providers/aurora.d.ts.map +1 -0
  506. package/dist/postgres/providers/azure.d.ts +14 -0
  507. package/dist/postgres/providers/azure.d.ts.map +1 -0
  508. package/dist/postgres/providers/cloud-sql.d.ts +11 -0
  509. package/dist/postgres/providers/cloud-sql.d.ts.map +1 -0
  510. package/dist/postgres/providers/index.d.ts +35 -0
  511. package/dist/postgres/providers/index.d.ts.map +1 -0
  512. package/dist/postgres/providers/neon.d.ts +13 -0
  513. package/dist/postgres/providers/neon.d.ts.map +1 -0
  514. package/dist/postgres/providers/rds.d.ts +10 -0
  515. package/dist/postgres/providers/rds.d.ts.map +1 -0
  516. package/dist/postgres/providers/supabase.d.ts +12 -0
  517. package/dist/postgres/providers/supabase.d.ts.map +1 -0
  518. package/dist/postgres/providers/types.d.ts +59 -0
  519. package/dist/postgres/providers/types.d.ts.map +1 -0
  520. package/dist/postgres/testing/fake-client.d.ts +38 -0
  521. package/dist/postgres/testing/fake-client.d.ts.map +1 -0
  522. package/dist/postgres/testing/migration.d.ts +25 -0
  523. package/dist/postgres/testing/migration.d.ts.map +1 -0
  524. package/dist/postgres/testing/roundtrip-schema.d.ts +11 -0
  525. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -0
  526. package/dist/postgres/testing/server.d.ts +17 -0
  527. package/dist/postgres/testing/server.d.ts.map +1 -0
  528. package/dist/postgres/testing/writable-server.d.ts +45 -0
  529. package/dist/postgres/testing/writable-server.d.ts.map +1 -0
  530. package/dist/postgres/tokens.d.ts +26 -0
  531. package/dist/postgres/tokens.d.ts.map +1 -0
  532. package/dist/postgres.d.ts +13 -0
  533. package/dist/postgres.d.ts.map +1 -0
  534. package/dist/rules/audit-entries.ts +29 -0
  535. package/dist/rules/clickhouse-helpers.ts +206 -0
  536. package/dist/rules/postgres-audit-entries.ts +29 -0
  537. package/dist/rules/postgres-helpers.ts +126 -0
  538. package/dist/rules/postgres-templates.ts +36 -0
  539. package/dist/rules/sql-helpers.ts +53 -0
  540. package/dist/rules/sqlch001.ts +53 -0
  541. package/dist/rules/sqlch002.ts +115 -0
  542. package/dist/rules/sqlch003.ts +68 -0
  543. package/dist/rules/sqlch101.ts +35 -0
  544. package/dist/rules/sqlch102.ts +23 -0
  545. package/dist/rules/sqlch103.ts +45 -0
  546. package/dist/rules/sqlch104.ts +42 -0
  547. package/dist/rules/sqlch105.ts +35 -0
  548. package/dist/rules/sqlch106.ts +27 -0
  549. package/dist/rules/sqlch107.ts +35 -0
  550. package/dist/rules/sqlch108.ts +27 -0
  551. package/dist/rules/sqlch109.ts +21 -0
  552. package/dist/rules/sqlch110.ts +29 -0
  553. package/dist/rules/sqlch111.ts +40 -0
  554. package/dist/rules/sqlch112.ts +30 -0
  555. package/dist/rules/sqlch113.ts +20 -0
  556. package/dist/rules/sqlch114.ts +26 -0
  557. package/dist/rules/sqlch115.ts +31 -0
  558. package/dist/rules/sqlch116.ts +22 -0
  559. package/dist/rules/sqlch117.ts +25 -0
  560. package/dist/rules/sqlch118.ts +26 -0
  561. package/dist/rules/sqlch119.ts +31 -0
  562. package/dist/rules/sqlch120.ts +21 -0
  563. package/dist/rules/sqlpg001.ts +88 -0
  564. package/dist/rules/sqlpg002.ts +68 -0
  565. package/dist/rules/sqlpg003.ts +59 -0
  566. package/dist/rules/sqlpg004.ts +73 -0
  567. package/dist/rules/sqlpg101.ts +24 -0
  568. package/dist/rules/sqlpg102.ts +30 -0
  569. package/dist/rules/sqlpg103.ts +23 -0
  570. package/dist/rules/sqlpg104.ts +23 -0
  571. package/dist/rules/sqlpg105.ts +22 -0
  572. package/dist/rules/sqlpg106.ts +22 -0
  573. package/dist/rules/sqlpg107.ts +22 -0
  574. package/dist/rules/sqlpg108.ts +27 -0
  575. package/dist/rules/sqlpg109.ts +37 -0
  576. package/dist/rules/sqlpg110.ts +33 -0
  577. package/dist/rules/sqlpg111.ts +23 -0
  578. package/dist/rules/sqlpg112.ts +25 -0
  579. package/dist/rules/sqlpg113.ts +28 -0
  580. package/dist/rules/sqlpg114.ts +37 -0
  581. package/dist/rules/sqlpg115.ts +38 -0
  582. package/dist/rules/sqlpg116.ts +25 -0
  583. package/dist/rules/sqlpg117.ts +24 -0
  584. package/dist/rules/sqlpg118.ts +22 -0
  585. package/dist/rules/templates.ts +35 -0
  586. package/dist/serializer.d.ts +37 -0
  587. package/dist/serializer.d.ts.map +1 -0
  588. package/dist/skill-defs.d.ts +3 -0
  589. package/dist/skill-defs.d.ts.map +1 -0
  590. package/dist/spec/catalog.d.ts +125 -0
  591. package/dist/spec/catalog.d.ts.map +1 -0
  592. package/dist/spec/fetch.d.ts +43 -0
  593. package/dist/spec/fetch.d.ts.map +1 -0
  594. package/dist/spec/pin.d.ts +39 -0
  595. package/dist/spec/pin.d.ts.map +1 -0
  596. package/dist/spec/postgres-catalog.d.ts +108 -0
  597. package/dist/spec/postgres-catalog.d.ts.map +1 -0
  598. package/dist/spec/postgres-fetch.d.ts +46 -0
  599. package/dist/spec/postgres-fetch.d.ts.map +1 -0
  600. package/dist/spec/postgres-pin.d.ts +67 -0
  601. package/dist/spec/postgres-pin.d.ts.map +1 -0
  602. package/dist/types/index.d.ts +4386 -0
  603. package/dist/validate-cli.d.ts +3 -0
  604. package/dist/validate-cli.d.ts.map +1 -0
  605. package/dist/validate.d.ts +15 -0
  606. package/dist/validate.d.ts.map +1 -0
  607. package/package.json +74 -4
  608. package/src/clickhouse/apply/apply.ts +288 -0
  609. package/src/clickhouse/apply/mutations.ts +90 -0
  610. package/src/clickhouse/apply/statements.ts +393 -0
  611. package/src/clickhouse/catalog-types.ts +84 -0
  612. package/src/clickhouse/container.ts +176 -0
  613. package/src/clickhouse/engine-syntax.test.ts +53 -0
  614. package/src/clickhouse/engine-syntax.ts +173 -0
  615. package/src/clickhouse/entities.test.ts +188 -0
  616. package/src/clickhouse/entities.ts +425 -0
  617. package/src/clickhouse/http.ts +76 -0
  618. package/src/clickhouse/import/generator.ts +153 -0
  619. package/src/clickhouse/import/import.test.ts +157 -0
  620. package/src/clickhouse/import/ir.ts +171 -0
  621. package/src/clickhouse/import/live-export.ts +53 -0
  622. package/src/clickhouse/import/parser.ts +27 -0
  623. package/src/clickhouse/import/roundtrip.test.ts +90 -0
  624. package/src/clickhouse/live/bind.ts +126 -0
  625. package/src/clickhouse/live/catalog.ts +96 -0
  626. package/src/clickhouse/live/describe-resources.ts +98 -0
  627. package/src/clickhouse/live/live.test.ts +143 -0
  628. package/src/clickhouse/live/roundtrip.e2e.test.ts +93 -0
  629. package/src/clickhouse/overlays/codecs.ts +68 -0
  630. package/src/clickhouse/overlays/engines.ts +108 -0
  631. package/src/clickhouse/overlays/kinds.ts +38 -0
  632. package/src/clickhouse/overlays/projections.ts +36 -0
  633. package/src/clickhouse/overlays/skip-indexes.ts +44 -0
  634. package/src/clickhouse/overlays/ttl.ts +55 -0
  635. package/src/clickhouse/overlays/types.ts +58 -0
  636. package/src/clickhouse/ownership.test.ts +20 -0
  637. package/src/clickhouse/ownership.ts +103 -0
  638. package/src/clickhouse/parser.test.ts +166 -0
  639. package/src/clickhouse/parser.ts +459 -0
  640. package/src/clickhouse/plan/commands.ts +101 -0
  641. package/src/clickhouse/plan/deep.ts +124 -0
  642. package/src/clickhouse/plan/diff.ts +203 -0
  643. package/src/clickhouse/plan/disruption.ts +61 -0
  644. package/src/clickhouse/plan/normalize.ts +352 -0
  645. package/src/clickhouse/plan/plan-wiring.test.ts +77 -0
  646. package/src/clickhouse/plan/plan.e2e.test.ts +77 -0
  647. package/src/clickhouse/plan/plan.test.ts +201 -0
  648. package/src/clickhouse/plan/rebuild-handoff.ts +59 -0
  649. package/src/clickhouse/plan/report.ts +19 -0
  650. package/src/clickhouse/plan/rules.ts +241 -0
  651. package/src/clickhouse/plan/schema.ts +56 -0
  652. package/src/clickhouse/plan/server-format.ts +39 -0
  653. package/src/clickhouse/rebuild/backfill.ts +186 -0
  654. package/src/clickhouse/rebuild/observe.ts +380 -0
  655. package/src/clickhouse/rebuild/op.ts +206 -0
  656. package/src/clickhouse/rebuild/partitions.ts +62 -0
  657. package/src/clickhouse/rebuild/rebuild-replicated.e2e.test.ts +513 -0
  658. package/src/clickhouse/rebuild/rebuild.e2e.test.ts +351 -0
  659. package/src/clickhouse/rebuild/rebuild.test.ts +190 -0
  660. package/src/clickhouse/rebuild/receipts.ts +143 -0
  661. package/src/clickhouse/rebuild/replicas.test.ts +70 -0
  662. package/src/clickhouse/rebuild/replicas.ts +148 -0
  663. package/src/clickhouse/rebuild/steps.ts +382 -0
  664. package/src/clickhouse/rebuild/verify.ts +124 -0
  665. package/src/clickhouse/testdata/NOTICE +5 -0
  666. package/src/clickhouse/testdata/clickhouse-tests-corpus.json +302 -0
  667. package/src/clickhouse/testing/fake-server.ts +65 -0
  668. package/src/clickhouse/testing/roundtrip-schema.ts +25 -0
  669. package/src/clickhouse/testing/writable-server.ts +141 -0
  670. package/src/clickhouse/tokens.ts +28 -0
  671. package/src/clickhouse.ts +63 -0
  672. package/src/codegen/classifier-page.test.ts +16 -0
  673. package/src/codegen/classifier-page.ts +76 -0
  674. package/src/codegen/clickhouse-module.test.ts +63 -0
  675. package/src/codegen/clickhouse-module.ts +269 -0
  676. package/src/codegen/docs-cli.ts +4 -0
  677. package/src/codegen/docs.ts +225 -0
  678. package/src/codegen/generate-cli.ts +13 -0
  679. package/src/codegen/generate.ts +126 -0
  680. package/src/codegen/package.ts +36 -0
  681. package/src/codegen/postgres-classifier-page.ts +81 -0
  682. package/src/codegen/postgres-module.test.ts +77 -0
  683. package/src/codegen/postgres-module.ts +434 -0
  684. package/src/codegen/registry.ts +36 -0
  685. package/src/composites/audit-log-table.ts +54 -0
  686. package/src/composites/catalog.test.ts +61 -0
  687. package/src/composites/catalog.ts +529 -0
  688. package/src/composites/cdc-mirror.ts +54 -0
  689. package/src/composites/clickhouse.ts +7 -0
  690. package/src/composites/composites.test.ts +758 -0
  691. package/src/composites/events-table.ts +49 -0
  692. package/src/composites/index.ts +9 -0
  693. package/src/composites/join-table.ts +57 -0
  694. package/src/composites/postgres.ts +7 -0
  695. package/src/composites/refreshed-view.ts +46 -0
  696. package/src/composites/replacing-table.ts +49 -0
  697. package/src/composites/rollup-view.ts +55 -0
  698. package/src/composites/sharded-table.ts +49 -0
  699. package/src/composites/soft-delete-table.ts +61 -0
  700. package/src/composites/tenant-table.ts +50 -0
  701. package/src/config.ts +157 -0
  702. package/src/core/apply.ts +153 -0
  703. package/src/core/classifier.ts +186 -0
  704. package/src/core/container.ts +93 -0
  705. package/src/core/core.test.ts +254 -0
  706. package/src/core/cursor.ts +189 -0
  707. package/src/core/diff.ts +72 -0
  708. package/src/core/entity.ts +85 -0
  709. package/src/core/find-templates.ts +158 -0
  710. package/src/core/handoff.ts +44 -0
  711. package/src/core/index.ts +61 -0
  712. package/src/core/interpolation.ts +225 -0
  713. package/src/core/normalize.ts +39 -0
  714. package/src/core/ownership.test.ts +36 -0
  715. package/src/core/ownership.ts +133 -0
  716. package/src/core/receipts.ts +29 -0
  717. package/src/core/references.ts +74 -0
  718. package/src/core/template.ts +83 -0
  719. package/src/core/testing/http-stub.ts +33 -0
  720. package/src/core/tokens.ts +252 -0
  721. package/src/coverage.ts +64 -0
  722. package/src/detect.ts +32 -0
  723. package/src/dialects.ts +21 -0
  724. package/src/generated/clickhouse-types.ts +4386 -0
  725. package/src/generated/clickhouse.ts +2476 -0
  726. package/src/generated/index.d.ts +4386 -0
  727. package/src/generated/lexicon-sql.json +67 -0
  728. package/src/generated/postgres-types.ts +1190 -0
  729. package/src/generated/postgres.ts +1039 -0
  730. package/src/import-generator.ts +18 -0
  731. package/src/index.ts +21 -0
  732. package/src/init-templates.test.ts +108 -0
  733. package/src/init-templates.ts +327 -0
  734. package/src/lint/audit-catalog.ts +73 -0
  735. package/src/lint/audit-lineage.ts +47 -0
  736. package/src/lint/post-synth/audit-entries.ts +29 -0
  737. package/src/lint/post-synth/clickhouse-checks.test.ts +278 -0
  738. package/src/lint/post-synth/clickhouse-helpers.ts +206 -0
  739. package/src/lint/post-synth/index.ts +81 -0
  740. package/src/lint/post-synth/post-synth.test.ts +33 -0
  741. package/src/lint/post-synth/postgres-audit-entries.ts +29 -0
  742. package/src/lint/post-synth/postgres-checks.test.ts +257 -0
  743. package/src/lint/post-synth/postgres-helpers.ts +126 -0
  744. package/src/lint/post-synth/sql-helpers.ts +53 -0
  745. package/src/lint/post-synth/sqlch101.ts +35 -0
  746. package/src/lint/post-synth/sqlch102.ts +23 -0
  747. package/src/lint/post-synth/sqlch103.ts +45 -0
  748. package/src/lint/post-synth/sqlch104.ts +42 -0
  749. package/src/lint/post-synth/sqlch105.ts +35 -0
  750. package/src/lint/post-synth/sqlch106.ts +27 -0
  751. package/src/lint/post-synth/sqlch107.ts +35 -0
  752. package/src/lint/post-synth/sqlch108.ts +27 -0
  753. package/src/lint/post-synth/sqlch109.ts +21 -0
  754. package/src/lint/post-synth/sqlch110.ts +29 -0
  755. package/src/lint/post-synth/sqlch111.ts +40 -0
  756. package/src/lint/post-synth/sqlch112.ts +30 -0
  757. package/src/lint/post-synth/sqlch113.ts +20 -0
  758. package/src/lint/post-synth/sqlch114.ts +26 -0
  759. package/src/lint/post-synth/sqlch115.ts +31 -0
  760. package/src/lint/post-synth/sqlch116.ts +22 -0
  761. package/src/lint/post-synth/sqlch117.ts +25 -0
  762. package/src/lint/post-synth/sqlch118.ts +26 -0
  763. package/src/lint/post-synth/sqlch119.ts +31 -0
  764. package/src/lint/post-synth/sqlch120.ts +21 -0
  765. package/src/lint/post-synth/sqlpg101.ts +24 -0
  766. package/src/lint/post-synth/sqlpg102.ts +30 -0
  767. package/src/lint/post-synth/sqlpg103.ts +23 -0
  768. package/src/lint/post-synth/sqlpg104.ts +23 -0
  769. package/src/lint/post-synth/sqlpg105.ts +22 -0
  770. package/src/lint/post-synth/sqlpg106.ts +22 -0
  771. package/src/lint/post-synth/sqlpg107.ts +22 -0
  772. package/src/lint/post-synth/sqlpg108.ts +27 -0
  773. package/src/lint/post-synth/sqlpg109.ts +37 -0
  774. package/src/lint/post-synth/sqlpg110.ts +33 -0
  775. package/src/lint/post-synth/sqlpg111.ts +23 -0
  776. package/src/lint/post-synth/sqlpg112.ts +25 -0
  777. package/src/lint/post-synth/sqlpg113.ts +28 -0
  778. package/src/lint/post-synth/sqlpg114.ts +37 -0
  779. package/src/lint/post-synth/sqlpg115.ts +38 -0
  780. package/src/lint/post-synth/sqlpg116.ts +25 -0
  781. package/src/lint/post-synth/sqlpg117.ts +24 -0
  782. package/src/lint/post-synth/sqlpg118.ts +22 -0
  783. package/src/lint/rules/index.ts +13 -0
  784. package/src/lint/rules/postgres-templates.ts +36 -0
  785. package/src/lint/rules/rules.test.ts +228 -0
  786. package/src/lint/rules/sqlch001.ts +53 -0
  787. package/src/lint/rules/sqlch002.ts +115 -0
  788. package/src/lint/rules/sqlch003.ts +68 -0
  789. package/src/lint/rules/sqlpg001.ts +88 -0
  790. package/src/lint/rules/sqlpg002.ts +68 -0
  791. package/src/lint/rules/sqlpg003.ts +59 -0
  792. package/src/lint/rules/sqlpg004.ts +73 -0
  793. package/src/lint/rules/templates.ts +35 -0
  794. package/src/live-dialect.ts +37 -0
  795. package/src/lsp/catalog.ts +60 -0
  796. package/src/lsp/completions.test.ts +111 -0
  797. package/src/lsp/completions.ts +128 -0
  798. package/src/lsp/diagnostics.test.ts +26 -0
  799. package/src/lsp/hover.test.ts +90 -0
  800. package/src/lsp/hover.ts +113 -0
  801. package/src/lsp/postgres-catalog.ts +234 -0
  802. package/src/lsp/postgres-template.ts +161 -0
  803. package/src/lsp/postgres.test.ts +150 -0
  804. package/src/lsp/postgres.ts +270 -0
  805. package/src/lsp/refs.ts +53 -0
  806. package/src/lsp/registry.ts +19 -0
  807. package/src/lsp/template.ts +166 -0
  808. package/src/mcp/index.ts +304 -0
  809. package/src/mcp/mcp.test.ts +184 -0
  810. package/src/mcp/postgres.ts +125 -0
  811. package/src/op/activities/clickhouse-apply.e2e.test.ts +150 -0
  812. package/src/op/activities/clickhouse-apply.test.ts +265 -0
  813. package/src/op/activities/clickhouse-apply.ts +113 -0
  814. package/src/op/activities/clickhouse-emulator.test.ts +23 -0
  815. package/src/op/activities/clickhouse-emulator.ts +37 -0
  816. package/src/op/activities/clickhouse-rebuild.ts +136 -0
  817. package/src/op/activities/index.ts +50 -0
  818. package/src/op/activities/postgres-apply.e2e.test.ts +184 -0
  819. package/src/op/activities/postgres-apply.test.ts +304 -0
  820. package/src/op/activities/postgres-apply.ts +170 -0
  821. package/src/op/activities/postgres-emulator.test.ts +26 -0
  822. package/src/op/activities/postgres-emulator.ts +44 -0
  823. package/src/op/activities/postgres-migration.ts +182 -0
  824. package/src/op/activity-contracts.ts +210 -0
  825. package/src/package-cli.ts +17 -0
  826. package/src/plugin.test.ts +74 -0
  827. package/src/plugin.ts +242 -0
  828. package/src/postgres/apply/apply.ts +582 -0
  829. package/src/postgres/apply/statements.test.ts +155 -0
  830. package/src/postgres/apply/statements.ts +603 -0
  831. package/src/postgres/catalog-types.ts +59 -0
  832. package/src/postgres/container.ts +72 -0
  833. package/src/postgres/entities.test.ts +170 -0
  834. package/src/postgres/entities.ts +1016 -0
  835. package/src/postgres/entity-types.ts +18 -0
  836. package/src/postgres/import/generator.ts +176 -0
  837. package/src/postgres/import/import.test.ts +71 -0
  838. package/src/postgres/import/ir.ts +67 -0
  839. package/src/postgres/import/live-export.ts +53 -0
  840. package/src/postgres/import/live-warnings.test.ts +45 -0
  841. package/src/postgres/keywords.ts +40 -0
  842. package/src/postgres/live/bind.ts +146 -0
  843. package/src/postgres/live/catalog.ts +581 -0
  844. package/src/postgres/live/client.ts +84 -0
  845. package/src/postgres/live/describe-resources.ts +111 -0
  846. package/src/postgres/live/live.test.ts +198 -0
  847. package/src/postgres/live/roundtrip.e2e.test.ts +111 -0
  848. package/src/postgres/migrate/backfill.ts +233 -0
  849. package/src/postgres/migrate/batches.e2e.test.ts +163 -0
  850. package/src/postgres/migrate/batches.test.ts +36 -0
  851. package/src/postgres/migrate/batches.ts +159 -0
  852. package/src/postgres/migrate/carry.e2e.test.ts +335 -0
  853. package/src/postgres/migrate/carry.test.ts +103 -0
  854. package/src/postgres/migrate/carry.ts +653 -0
  855. package/src/postgres/migrate/handoff.ts +73 -0
  856. package/src/postgres/migrate/kinds.e2e.test.ts +204 -0
  857. package/src/postgres/migrate/migrate.e2e.test.ts +362 -0
  858. package/src/postgres/migrate/migrate.test.ts +322 -0
  859. package/src/postgres/migrate/names.ts +93 -0
  860. package/src/postgres/migrate/observe.ts +521 -0
  861. package/src/postgres/migrate/op.ts +242 -0
  862. package/src/postgres/migrate/receipts.ts +135 -0
  863. package/src/postgres/migrate/replication.ts +151 -0
  864. package/src/postgres/migrate/steps.ts +657 -0
  865. package/src/postgres/overlays/columns.ts +34 -0
  866. package/src/postgres/overlays/constraints.ts +56 -0
  867. package/src/postgres/overlays/indexes.ts +27 -0
  868. package/src/postgres/overlays/kinds.ts +53 -0
  869. package/src/postgres/overlays/partitioning.ts +26 -0
  870. package/src/postgres/overlays/relations.ts +29 -0
  871. package/src/postgres/overlays/storage.ts +75 -0
  872. package/src/postgres/overlays/type-ddl.ts +38 -0
  873. package/src/postgres/overlays/types.ts +56 -0
  874. package/src/postgres/parser.test.ts +214 -0
  875. package/src/postgres/parser.ts +1062 -0
  876. package/src/postgres/plan/commands.ts +124 -0
  877. package/src/postgres/plan/deep.ts +177 -0
  878. package/src/postgres/plan/diff.ts +323 -0
  879. package/src/postgres/plan/disruption.ts +52 -0
  880. package/src/postgres/plan/normalize.test.ts +148 -0
  881. package/src/postgres/plan/normalize.ts +542 -0
  882. package/src/postgres/plan/plan.e2e.test.ts +117 -0
  883. package/src/postgres/plan/plan.test.ts +244 -0
  884. package/src/postgres/plan/report.ts +24 -0
  885. package/src/postgres/plan/rules.ts +276 -0
  886. package/src/postgres/plan/schema.ts +114 -0
  887. package/src/postgres/plan/server-normalize.ts +123 -0
  888. package/src/postgres/providers/aurora.ts +45 -0
  889. package/src/postgres/providers/azure.ts +54 -0
  890. package/src/postgres/providers/cloud-sql.ts +42 -0
  891. package/src/postgres/providers/index.ts +90 -0
  892. package/src/postgres/providers/neon.ts +45 -0
  893. package/src/postgres/providers/providers.test.ts +136 -0
  894. package/src/postgres/providers/rds.ts +48 -0
  895. package/src/postgres/providers/supabase.ts +46 -0
  896. package/src/postgres/providers/types.ts +70 -0
  897. package/src/postgres/testdata/NOTICE +31 -0
  898. package/src/postgres/testdata/postgres-regress-corpus.json +460 -0
  899. package/src/postgres/testing/fake-client.ts +42 -0
  900. package/src/postgres/testing/migration.ts +52 -0
  901. package/src/postgres/testing/roundtrip-schema.ts +46 -0
  902. package/src/postgres/testing/server.ts +58 -0
  903. package/src/postgres/testing/writable-server.ts +188 -0
  904. package/src/postgres/tokens.ts +46 -0
  905. package/src/postgres.ts +75 -0
  906. package/src/serializer.test.ts +128 -0
  907. package/src/serializer.ts +148 -0
  908. package/src/skill-defs.test.ts +81 -0
  909. package/src/skill-defs.ts +102 -0
  910. package/src/skills/chant-sql-plan.md +123 -0
  911. package/src/skills/chant-sql-postgres-migration.md +126 -0
  912. package/src/skills/chant-sql-postgres-plan.md +194 -0
  913. package/src/skills/chant-sql-postgres.md +165 -0
  914. package/src/skills/chant-sql-rebuild.md +68 -0
  915. package/src/skills/chant-sql.md +123 -0
  916. package/src/spec/catalog.e2e.test.ts +29 -0
  917. package/src/spec/catalog.test.ts +56 -0
  918. package/src/spec/catalog.ts +311 -0
  919. package/src/spec/clickhouse-catalog.snapshot.json +5246 -0
  920. package/src/spec/fetch.test.ts +77 -0
  921. package/src/spec/fetch.ts +112 -0
  922. package/src/spec/pin.ts +47 -0
  923. package/src/spec/postgres-catalog-14.snapshot.json +3494 -0
  924. package/src/spec/postgres-catalog-15.snapshot.json +3529 -0
  925. package/src/spec/postgres-catalog-16.snapshot.json +3597 -0
  926. package/src/spec/postgres-catalog-17.snapshot.json +3656 -0
  927. package/src/spec/postgres-catalog-18.snapshot.json +3747 -0
  928. package/src/spec/postgres-catalog.test.ts +43 -0
  929. package/src/spec/postgres-catalog.ts +301 -0
  930. package/src/spec/postgres-fetch.test.ts +75 -0
  931. package/src/spec/postgres-fetch.ts +112 -0
  932. package/src/spec/postgres-pin.test.ts +64 -0
  933. package/src/spec/postgres-pin.ts +101 -0
  934. package/src/typecheck.test.ts +104 -0
  935. package/src/validate-cli.ts +6 -0
  936. package/src/validate.ts +149 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * How a Postgres plan hands an expand-and-contract change to the migration
3
+ * Op (#3281), as the ClickHouse plan hands a rebuild to its Op (#3250).
4
+ *
5
+ * `chant sql plan`, `chant sql diff`, the `classify-change` MCP tool and the
6
+ * applier refuse an expand-and-contract change in place. For a column
7
+ * rename (SQLPG205) and a type change across kinds (SQLPG208) what they hand
8
+ * on is a `PostgresMigrationOp` declaration per column, ready to put in an
9
+ * `*.op.ts` file, reviewed in the pull request like the schema change it
10
+ * carries out. The Op's own Plan phase classifies the change again against
11
+ * the server. The other expand-and-contract rules (a NOT NULL column with no
12
+ * default, partitioning, an object rename, a view's columns, enum labels, a
13
+ * domain's type, an object's kind) have no Op yet, and get none.
14
+ *
15
+ * Nothing is written for the user. The hand-off's shape is the shared
16
+ * core's (`../../core/handoff.ts`).
17
+ */
18
+
19
+ import { migrationOpName, renderMigrationOps, type MigrationOpSuggestion } from "../../core/handoff";
20
+ import type { PgChange } from "../plan/diff";
21
+ import type { PgDiffObject } from "../plan/schema";
22
+
23
+ export interface PostgresMigrationOpSuggestion extends MigrationOpSuggestion {
24
+ /** `schema.table`, the Op's `table`. */
25
+ table: string;
26
+ /** The declared column, the Op's `column`. */
27
+ column: string;
28
+ /** The rule the Op makes the change for. */
29
+ rule: "SQLPG205" | "SQLPG208";
30
+ }
31
+
32
+ /** The rules a refused change is handed to the Op for. */
33
+ export const HANDED_OFF = new Set(["SQLPG205", "SQLPG208"]);
34
+
35
+ const COLUMN = /^columns\.([^.]+)(?:\.type)?$/;
36
+
37
+ /** The table and column a refused change is about, when the Op makes it. */
38
+ export function migrationTarget(c: PgChange, declared: Pick<PgDiffObject, "kind" | "schema" | "name"> | undefined, defaultSchema = "public"): { table: string; column: string } | undefined {
39
+ if (!HANDED_OFF.has(c.rule) || !declared || declared.kind !== "table") return undefined;
40
+ const m = COLUMN.exec(c.field);
41
+ if (!m || (c.rule === "SQLPG208" && !c.field.endsWith(".type")) || (c.rule === "SQLPG205" && c.field.endsWith(".type"))) return undefined;
42
+ return { table: `${declared.schema ?? defaultSchema}.${declared.name}`, column: m[1]! };
43
+ }
44
+
45
+ /** The Op's declaration for one column. */
46
+ export function migrationDeclaration(name: string, env: string, table: string, column: string): string {
47
+ return `export const { op } = PostgresMigrationOp({ name: ${JSON.stringify(name)}, env: ${JSON.stringify(env)}, table: ${JSON.stringify(table)}, column: ${JSON.stringify(column)} });`;
48
+ }
49
+
50
+ /**
51
+ * One suggestion per column the diff refuses for a rename or a type change
52
+ * across kinds. `declared` maps the diff's object keys to the declared
53
+ * definitions.
54
+ */
55
+ export function migrationOpSuggestions(refused: readonly PgChange[], declared: ReadonlyMap<string, PgDiffObject>, env: string, defaultSchema = "public"): PostgresMigrationOpSuggestion[] {
56
+ const out: PostgresMigrationOpSuggestion[] = [];
57
+ const seen = new Set<string>();
58
+ for (const c of refused) {
59
+ const t = migrationTarget(c, declared.get(c.object), defaultSchema);
60
+ if (!t) continue;
61
+ const id = `${t.table}.${t.column}`;
62
+ if (seen.has(id)) continue;
63
+ seen.add(id);
64
+ const name = migrationOpName("migrate", id);
65
+ out.push({ table: t.table, column: t.column, rule: c.rule as "SQLPG205" | "SQLPG208", name, env, declaration: migrationDeclaration(name, env, t.table, t.column) });
66
+ }
67
+ return out;
68
+ }
69
+
70
+ /** The suggestions as text, for the report. */
71
+ export function renderMigrationOpSuggestions(ops: readonly PostgresMigrationOpSuggestion[]): string[] {
72
+ return renderMigrationOps(ops, { what: "the expand-and-contract migration Op", exportName: "PostgresMigrationOp", importPath: "@intentius/chant-lexicon-sql/postgres" });
73
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Identity, serial and partitioned tables in the migration Op (#3322),
3
+ * against the pinned server, and what it refuses among the rest:
4
+ *
5
+ * - `integer` to `bigint` on an identity primary key another table
6
+ * references: the identity moves to the new column under its sequence's
7
+ * name and carries on from where the old one stood.
8
+ * - `serial` to `bigserial`: the sequence is handed to the new column,
9
+ * widened, and stays its default.
10
+ * - A column of a partitioned table: every partition follows the
11
+ * partitioned table.
12
+ * - Refused, each naming why: a partition, an inheritance tree, a column in
13
+ * the partition key, an index on a partitioned table, a generated column,
14
+ * a rename of an identity column.
15
+ *
16
+ * Needs Docker; skips cleanly without it.
17
+ */
18
+
19
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
23
+ import { dockerAvailable, startTestPostgres, type TestPostgres } from "../testing/server";
24
+ import { ApprovingLedger, runMigrationOp, runOutcome } from "../testing/migration";
25
+ import type { PostgresClient } from "../live/client";
26
+ import { planPgAgainstServer } from "../plan/commands";
27
+ import { postgresApply } from "../../op/activities/postgres-apply";
28
+ import { postgresMigrationPlan, type PostgresMigrationDeps } from "../../op/activities/postgres-migration";
29
+ import type { PostgresMigrationOpConfig } from "./op";
30
+
31
+ const enabled = await dockerAvailable();
32
+ let server: TestPostgres | undefined;
33
+ let admin: PostgresClient | undefined;
34
+ const dir = mkdtempSync(join(tmpdir(), "chant-pg-kinds-"));
35
+
36
+ beforeAll(async () => {
37
+ if (!enabled) return;
38
+ server = await startTestPostgres();
39
+ admin = await server.connect();
40
+ }, 600_000);
41
+
42
+ afterAll(async () => {
43
+ await admin?.end();
44
+ await server?.stop();
45
+ rmSync(dir, { recursive: true, force: true });
46
+ });
47
+
48
+ interface Obj {
49
+ export: string;
50
+ type: string;
51
+ ddl: string;
52
+ dependsOn?: string[];
53
+ }
54
+
55
+ function writeBuild(file: string, objects: Obj[]): string {
56
+ writeFileSync(join(dir, file), JSON.stringify({ dialect: "postgres", postgresMajor: 18, applyOrder: objects.map((o) => o.export), objects: objects.map((o) => ({ dependsOn: [], ...o })) }));
57
+ return file;
58
+ }
59
+
60
+ const MARKER = { stack: "e2e", env: "test" };
61
+ const profile = () => ({
62
+ config: { ownership: MARKER, sql: { profiles: { e2e: { url: server!.endpoint().url, password: { env: "PG_E2E_PASSWORD" } } } } },
63
+ env: { PG_E2E_PASSWORD: server!.endpoint().password },
64
+ });
65
+ const deps = (): PostgresMigrationDeps => ({ ...profile(), log: () => undefined });
66
+ const apply = (file: string) => postgresApply({ buildPath: join(dir, file), environment: "e2e" }, undefined, { ...profile(), log: () => undefined });
67
+ const plan = (file: string) => planPgAgainstServer("e2e", join(dir, file), profile());
68
+ const one = async <T>(sql: string): Promise<T> => (await admin!.query<T>(sql))[0]!;
69
+ const schemaObj = (name: string): Obj => ({ export: name, type: "Postgres::Schema", ddl: `CREATE SCHEMA ${name}` });
70
+ const table = (exportName: string, schema: string, ddl: string, dependsOn: string[] = []): Obj => ({ export: exportName, type: "Postgres::Table", dependsOn: [schema, ...dependsOn], ddl });
71
+ const config = (over: Pick<PostgresMigrationOpConfig, "name" | "table" | "column" | "output"> & Partial<PostgresMigrationOpConfig>): PostgresMigrationOpConfig => ({
72
+ env: "e2e",
73
+ build: false,
74
+ path: dir,
75
+ retain: "0s",
76
+ stack: MARKER.stack,
77
+ ownershipEnv: MARKER.env,
78
+ ...over,
79
+ });
80
+ const typeOf = async (table: string, column: string) =>
81
+ (await one<{ t: string }>(`SELECT pg_catalog.format_type(atttypid, atttypmod) AS t FROM pg_catalog.pg_attribute WHERE attrelid = '${table}'::regclass AND attname = '${column}'`)).t;
82
+
83
+ /** Run the Op, approving each gate, until it is done; `between` runs after the first run (writes during the migration). */
84
+ async function runToEnd(c: PostgresMigrationOpConfig, between?: () => Promise<void>) {
85
+ const ledger = new ApprovingLedger();
86
+ const runs = [];
87
+ for (let i = 0; i < 4; i++) {
88
+ const r = await runMigrationOp(c, ledger.port, deps);
89
+ runs.push(r);
90
+ if (i === 0) await between?.();
91
+ if (r.status !== "gated") break;
92
+ ledger.approveLast();
93
+ }
94
+ return runs;
95
+ }
96
+
97
+ describe.skipIf(!enabled)("an identity primary key another table references, integer to bigint", () => {
98
+ test("the identity moves to the bigint column under its sequence's name and carries on where it stood", async () => {
99
+ const IDN = schemaObj("idn");
100
+ const users = (t: string) => table("users", "idn", `CREATE TABLE idn.users (id ${t} GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL)`);
101
+ const POSTS = table("posts", "idn", "CREATE TABLE idn.posts (id bigint PRIMARY KEY, user_id integer NOT NULL REFERENCES idn.users (id))", ["users"]);
102
+ await apply(writeBuild("idn-v1.json", [IDN, users("integer"), POSTS]));
103
+ await admin!.query("INSERT INTO idn.users (name) SELECT 'u' || g FROM generate_series(1, 1500) g");
104
+ await admin!.query("INSERT INTO idn.posts SELECT g, (g % 1500) + 1 FROM generate_series(1, 2000) g");
105
+ writeBuild("idn-v2.json", [IDN, users("bigint"), POSTS]);
106
+
107
+ const runs = await runToEnd(config({ name: "migrate-users-id", table: "idn.users", column: "id", output: "idn-v2.json" }), async () => {
108
+ // A writer during the migration takes its id from the old identity; the trigger fills the new column.
109
+ await admin!.query("INSERT INTO idn.users (name) VALUES ('during')");
110
+ });
111
+ expect(runs.map((r) => r.status)).toEqual(["gated", "gated", "ok"]);
112
+ expect(String(runOutcome(runs[0]!, "Verification"))).toMatch(/foreign-key posts_user_id_fkey, key users_pkey carried over/);
113
+ expect(await typeOf("idn.users", "id")).toBe("bigint");
114
+ expect(await one("SELECT a.attidentity::text AS identity, pg_catalog.pg_get_serial_sequence('idn.users', 'id') AS seq FROM pg_catalog.pg_attribute a WHERE a.attrelid = 'idn.users'::regclass AND a.attname = 'id'")).toEqual({
115
+ identity: "a",
116
+ seq: "idn.users_id_seq",
117
+ });
118
+ expect(await one("SELECT data_type::text AS t FROM pg_catalog.pg_sequences WHERE schemaname = 'idn' AND sequencename = 'users_id_seq'")).toEqual({ t: "bigint" });
119
+ // The next id carries on from the last one the old identity gave.
120
+ expect(await one("INSERT INTO idn.users (name) VALUES ('after') RETURNING id::int AS id")).toEqual({ id: 1502 });
121
+ await admin!.query("INSERT INTO idn.posts VALUES (5000, 1502)");
122
+ expect((await plan("idn-v2.json")).changes).toEqual([]);
123
+ }, 300_000);
124
+ });
125
+
126
+ describe.skipIf(!enabled)("a serial column widened to bigserial", () => {
127
+ test("the sequence is handed to the new column, widened, and stays its default", async () => {
128
+ const SR = schemaObj("sr");
129
+ const items = (t: string) => table("items", "sr", `CREATE TABLE sr.items (id ${t} PRIMARY KEY, v text)`);
130
+ await apply(writeBuild("sr-v1.json", [SR, items("serial")]));
131
+ await admin!.query("INSERT INTO sr.items (v) SELECT 'v' || g FROM generate_series(1, 800) g");
132
+ writeBuild("sr-v2.json", [SR, items("bigserial")]);
133
+ // serial to bigserial is integer to bigint, SQLPG207, with the sequence widened: the plan prints it unqualified.
134
+ expect((await plan("sr-v2.json")).changes.map((c) => [c.rule, c.field, c.before, c.after])).toEqual([["SQLPG207", "columns.id.type", "serial", "bigserial"]]);
135
+
136
+ const runs = await runToEnd(config({ name: "migrate-items-id", table: "sr.items", column: "id", output: "sr-v2.json" }), async () => {
137
+ await admin!.query("INSERT INTO sr.items (v) VALUES ('during')");
138
+ });
139
+ expect(runs.map((r) => r.status)).toEqual(["gated", "gated", "ok"]);
140
+ expect(await typeOf("sr.items", "id")).toBe("bigint");
141
+ expect(await one("SELECT pg_catalog.pg_get_serial_sequence('sr.items', 'id') AS seq, (SELECT data_type::text FROM pg_catalog.pg_sequences WHERE sequencename = 'items_id_seq') AS t")).toEqual({
142
+ seq: "sr.items_id_seq",
143
+ t: "bigint",
144
+ });
145
+ expect(await one("INSERT INTO sr.items (v) VALUES ('after') RETURNING id::int AS id")).toEqual({ id: 802 });
146
+ expect((await plan("sr-v2.json")).changes).toEqual([]);
147
+ }, 300_000);
148
+ });
149
+
150
+ describe.skipIf(!enabled)("a column of a partitioned table", () => {
151
+ test("text to numeric: every partition follows the partitioned table", async () => {
152
+ const PT = schemaObj("pt");
153
+ const events = (t: string) => table("events", "pt", `CREATE TABLE pt.events (id bigint, at date NOT NULL, amount ${t} NOT NULL, PRIMARY KEY (id, at)) PARTITION BY RANGE (at)`);
154
+ const part = (y: number): Obj => table(`events${y}`, "pt", `CREATE TABLE pt.events_${y} PARTITION OF pt.events FOR VALUES FROM ('${y}-01-01') TO ('${y + 1}-01-01')`, ["events"]);
155
+ await apply(writeBuild("pt-v1.json", [PT, events("text"), part(2025), part(2026)]));
156
+ await admin!.query("INSERT INTO pt.events SELECT g, DATE '2025-06-01' + (g % 400), (g % 90) || '.5' FROM generate_series(1, 1200) g");
157
+ writeBuild("pt-v2.json", [PT, events("numeric(10,2)"), part(2025), part(2026)]);
158
+ expect((await plan("pt-v2.json")).changes.map((c) => [c.rule, c.field])).toEqual([["SQLPG208", "columns.amount.type"]]);
159
+
160
+ const runs = await runToEnd(config({ name: "migrate-events-amount", table: "pt.events", column: "amount", output: "pt-v2.json", batchSize: 500 }), async () => {
161
+ await admin!.query("INSERT INTO pt.events VALUES (9000, DATE '2026-03-01', '2.25')");
162
+ });
163
+ expect(runs.map((r) => r.status)).toEqual(["gated", "gated", "ok"]);
164
+ expect(runOutcome(runs[0]!, "Filled")).toBe(3);
165
+ for (const t of ["pt.events", "pt.events_2025", "pt.events_2026"]) expect(await typeOf(t, "amount")).toBe("numeric(10,2)");
166
+ expect(await one("SELECT amount::text AS a FROM pt.events WHERE id = 9000")).toEqual({ a: "2.25" });
167
+ expect(await one("SELECT count(*)::int AS n FROM pg_catalog.pg_attribute WHERE attrelid IN ('pt.events'::regclass, 'pt.events_2025'::regclass, 'pt.events_2026'::regclass) AND attname LIKE 'amount__chant%' AND NOT attisdropped")).toEqual({ n: 0 });
168
+ expect((await plan("pt-v2.json")).changes).toEqual([]);
169
+ }, 300_000);
170
+ });
171
+
172
+ describe.skipIf(!enabled)("what the Op refuses among the rest", () => {
173
+ const RF = schemaObj("rf");
174
+ const refusal = (tableName: string, column: string, output: string) =>
175
+ postgresMigrationPlan({ table: tableName, column, buildPath: output, environment: "e2e", stack: MARKER.stack, ownershipEnv: MARKER.env, cwd: dir }, undefined, deps()).then(
176
+ () => "",
177
+ (e: unknown) => String((e as Error).message),
178
+ );
179
+
180
+ test("a partition, an inheritance tree, the partition key, an index on a partitioned table, a generated column, a renamed identity", async () => {
181
+ const parted = (v: string, key = "at") => table("parted", "rf", `CREATE TABLE rf.parted (id bigint, at date, v ${v}, PRIMARY KEY (id, at)) PARTITION BY RANGE (${key})`);
182
+ const p1 = table("parted1", "rf", "CREATE TABLE rf.parted1 PARTITION OF rf.parted FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')", ["parted"]);
183
+ const parent = (v: string) => table("parent", "rf", `CREATE TABLE rf.parent (id bigint PRIMARY KEY, v ${v})`);
184
+ const gen = (g: string) => table("gen", "rf", `CREATE TABLE rf.gen (id bigint PRIMARY KEY, a integer, g ${g} GENERATED ALWAYS AS (a * 2) STORED)`);
185
+ const ident = (c: string, hint = "") => table("ident", "rf", `CREATE TABLE rf.ident (\n ${c} integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,${hint}\n v text\n)`);
186
+ await apply(writeBuild("rf-v1.json", [RF, parted("integer"), p1, parent("integer"), gen("integer"), ident("id")]));
187
+ await admin!.query("CREATE TABLE rf.child (extra text) INHERITS (rf.parent)");
188
+ await admin!.query("CREATE INDEX parted_v_idx ON rf.parted (v)");
189
+ writeBuild("rf-v2.json", [RF, parted("bigint"), p1, parent("bigint"), gen("bigint"), ident("key", " -- previously: id")]);
190
+
191
+ expect(await refusal("rf.parted1", "v", "rf-v2.json")).toMatch(/^rf\.parted1 is a partition of rf\.parted\. A partition's columns are its partitioned table's: run the Op on rf\.parted/);
192
+ expect(await refusal("rf.parent", "v", "rf-v2.json")).toMatch(/^rf\.parent has inheritance children \(rf\.child\)\. The Op does not migrate inheritance trees: a write to a child table does not fire the parent's dual-write trigger/);
193
+ expect(await refusal("rf.parted", "v", "rf-v2.json")).toMatch(/index rf\.parted_v_idx is a partitioned index, which cannot be built CONCURRENTLY/);
194
+ expect(await refusal("rf.gen", "g", "rf-v2.json")).toMatch(/^rf\.gen\.g is a generated column\. There is no expand and contract for one/);
195
+ expect(await refusal("rf.ident", "key", "rf-v2.json")).toMatch(/sequence rf\.ident_id_seq gives the column its values \(an identity column\); during a rename/);
196
+
197
+ await admin!.query("DROP INDEX rf.parted_v_idx");
198
+ writeBuild("rf-v3.json", [RF, parted("integer", "v"), p1]);
199
+ await admin!.query("DROP TABLE rf.parted");
200
+ await apply(writeBuild("rf-v3.json", [RF, table("parted", "rf", "CREATE TABLE rf.parted (id bigint, at date, v integer, PRIMARY KEY (id, v)) PARTITION BY RANGE (v)")]));
201
+ writeBuild("rf-v4.json", [RF, table("parted", "rf", "CREATE TABLE rf.parted (id bigint, at date, v bigint, PRIMARY KEY (id, v)) PARTITION BY RANGE (v)")]);
202
+ expect(await refusal("rf.parted", "v", "rf-v4.json")).toMatch(/rf\.parted\.v is in the partition key \(RANGE \(v\)\)/);
203
+ }, 300_000);
204
+ });
@@ -0,0 +1,362 @@
1
+ /**
2
+ * The expand-and-contract migration against the pinned server (#3281).
3
+ *
4
+ * - A column rename: the plan and the applier refuse it and name the Op; the
5
+ * Op stops at its gate with the verification on the run record while old
6
+ * writers keep working, switches once approved with both names written,
7
+ * and drops the old name at the contract gate.
8
+ * - A column type change across kinds: the same, with the columns swapped by
9
+ * name at the switch and the old one kept unwritten until the contract.
10
+ * - A failure: a value the new type cannot hold fails the backfill, and
11
+ * onFailure drops what the expand added.
12
+ * - An interrupted backfill: stopped after three batches, then killed before
13
+ * a batch's commit, then resumed: the receipts skip what was filled, and
14
+ * no row is updated twice.
15
+ *
16
+ * Runs the Op through core's local executor with an in-memory gate ledger.
17
+ * Needs Docker; skips cleanly without it.
18
+ */
19
+
20
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
24
+ import { normalizeApply } from "@intentius/chant/apply";
25
+ import { OpRunFailure, type GateLedgerPort, type OpRunResult } from "@intentius/chant/op";
26
+ import { dockerAvailable, startTestPostgres, type TestPostgres } from "../testing/server";
27
+ import type { PostgresClient } from "../live/client";
28
+ import { planPgAgainstServer } from "../plan/commands";
29
+ import { postgresApply } from "../../op/activities/postgres-apply";
30
+ import { toApplyResult } from "../../op/activities";
31
+ import * as migrationActivities from "../../op/activities/postgres-migration";
32
+ import type { PostgresMigrationDeps } from "../../op/activities/postgres-migration";
33
+ import type { PostgresMigrationArgs, PostgresMigrationOpConfig } from "./op";
34
+ import { ApprovingLedger, runMigrationOp, runOutcome } from "../testing/migration";
35
+ import { POSTGRES_RECEIPTS_TABLE } from "./receipts";
36
+
37
+ const enabled = await dockerAvailable();
38
+ let server: TestPostgres | undefined;
39
+ let admin: PostgresClient | undefined;
40
+ const dir = mkdtempSync(join(tmpdir(), "chant-pg-migrate-"));
41
+
42
+ beforeAll(async () => {
43
+ if (!enabled) return;
44
+ server = await startTestPostgres();
45
+ admin = await server.connect();
46
+ }, 600_000);
47
+
48
+ afterAll(async () => {
49
+ await admin?.end();
50
+ await server?.stop();
51
+ rmSync(dir, { recursive: true, force: true });
52
+ });
53
+
54
+ interface Obj {
55
+ export: string;
56
+ type: string;
57
+ ddl: string;
58
+ dependsOn?: string[];
59
+ }
60
+
61
+ function writeBuild(file: string, objects: Obj[]): string {
62
+ writeFileSync(join(dir, file), JSON.stringify({ dialect: "postgres", postgresMajor: 18, applyOrder: objects.map((o) => o.export), objects: objects.map((o) => ({ dependsOn: [], ...o })) }));
63
+ return file;
64
+ }
65
+
66
+ const MARKER = { stack: "e2e", env: "test" };
67
+ const profile = () => ({
68
+ config: {
69
+ ownership: MARKER,
70
+ sql: { profiles: { e2e: { url: server!.endpoint().url, password: { env: "PG_E2E_PASSWORD" } } } },
71
+ },
72
+ env: { PG_E2E_PASSWORD: server!.endpoint().password },
73
+ });
74
+ const deps = (extra: Partial<PostgresMigrationDeps> = {}): PostgresMigrationDeps => ({ ...profile(), log: () => undefined, ...extra });
75
+ const apply = (file: string) => postgresApply({ buildPath: join(dir, file), environment: "e2e" }, undefined, { ...profile(), log: () => undefined });
76
+ const plan = (file: string) => planPgAgainstServer("e2e", join(dir, file), profile());
77
+ const one = async <T>(sql: string): Promise<T> => (await admin!.query<T>(sql))[0]!;
78
+ const columnsOf = async (table: string) =>
79
+ (await admin!.query<{ name: string }>(`SELECT attname AS name FROM pg_attribute WHERE attrelid = '${table}'::regclass AND attnum > 0 AND NOT attisdropped ORDER BY attnum`)).map((r) => r.name);
80
+ const receiptsTableExists = async (schema: string) => (await one<{ t: string | null }>(`SELECT to_regclass('${schema}.${POSTGRES_RECEIPTS_TABLE}')::text AS t`)).t !== null;
81
+
82
+ const runOp = (config: PostgresMigrationOpConfig, gates: GateLedgerPort): Promise<OpRunResult> => runMigrationOp(config, gates, () => deps());
83
+ const Ledger = ApprovingLedger;
84
+ const outcome = runOutcome;
85
+ const failed = async (p: Promise<unknown>): Promise<OpRunFailure> => {
86
+ const e = await p.then(
87
+ () => undefined,
88
+ (err: unknown) => err,
89
+ );
90
+ expect(e).toBeInstanceOf(OpRunFailure);
91
+ return e as OpRunFailure;
92
+ };
93
+
94
+ // Each scenario has a schema of its own, so its plans read only its own table.
95
+ const schemaObj = (name: string): Obj => ({ export: name, type: "Postgres::Schema", ddl: `CREATE SCHEMA ${name}` });
96
+ const APP = schemaObj("app");
97
+ const SHOP = schemaObj("shop");
98
+ const REF = schemaObj("ref");
99
+ const EV = schemaObj("ev");
100
+
101
+ describe.skipIf(!enabled)("a column rename, migrated through its gates", () => {
102
+ const usersDdl = (column: string) => `CREATE TABLE app.users (
103
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
104
+ ${column}
105
+ name text
106
+ );
107
+ COMMENT ON TABLE app.users IS 'One row per account'`;
108
+ const V1: Obj = { export: "users", type: "Postgres::Table", dependsOn: ["app"], ddl: usersDdl("email text NOT NULL,") };
109
+ const V2: Obj = { ...V1, ddl: usersDdl("login text NOT NULL, -- previously: email") };
110
+ const config = (): PostgresMigrationOpConfig => ({
111
+ name: "migrate-users-login",
112
+ env: "e2e",
113
+ table: "app.users",
114
+ column: "login",
115
+ build: false,
116
+ path: dir,
117
+ output: "users-v2.json",
118
+ retain: "0s",
119
+ batchSize: 1000,
120
+ stack: MARKER.stack,
121
+ ownershipEnv: MARKER.env,
122
+ });
123
+ const ledger = new Ledger();
124
+
125
+ test("the plan and the applier refuse the rename in place and name the Op", async () => {
126
+ const applied = normalizeApply(toApplyResult(await apply(writeBuild("users-v1.json", [APP, V1]))));
127
+ expect(applied.notAttempted).toEqual([]);
128
+ await admin!.query("INSERT INTO app.users (email, name) SELECT 'u' || g || '@example.com', 'n' || g FROM generate_series(1, 2500) g");
129
+
130
+ writeBuild("users-v2.json", [APP, V2]);
131
+ const p = await plan("users-v2.json");
132
+ expect(p.refused.map((c) => c.rule)).toEqual(["SQLPG205"]);
133
+ expect(p.migrationOps).toEqual([expect.objectContaining({ table: "app.users", column: "login", env: "e2e", rule: "SQLPG205" })]);
134
+ expect(p.migrationOps![0]!.declaration).toBe('export const { op } = PostgresMigrationOp({ name: "migrate-app-users-login", env: "e2e", table: "app.users", column: "login" });');
135
+
136
+ const refused = normalizeApply(toApplyResult(await apply("users-v2.json")));
137
+ expect(refused.notAttempted).toEqual([expect.objectContaining({ name: "app.users", reason: "unsupported-kind" })]);
138
+ expect(refused.notAttempted[0]!.detail).toContain('PostgresMigrationOp({ table: "app.users", column: "login", ... })');
139
+ }, 180_000);
140
+
141
+ test("the first run fills the new column and stops at the switch gate with the verification attached", async () => {
142
+ const r = await runOp(config(), ledger.port);
143
+ expect(r.status).toBe("gated");
144
+ expect(r.gate).toMatchObject({ gate: "approve-migrate-users-login" });
145
+ expect(r.gate!.planDigest).toMatch(/^jcs1-sha256:/);
146
+ expect(outcome(r, "MigrationState")).toBe("migrate");
147
+ expect(outcome(r, "Change")).toBe("rename");
148
+ expect(outcome(r, "Filled")).toBe(3);
149
+ expect(outcome(r, "BackfilledRows")).toBe(2500);
150
+ expect(outcome(r, "VerifiedRows")).toBe(2500);
151
+ expect(String(outcome(r, "Verification"))).toMatch(/2500 row\(s\), login equal to email in every one/);
152
+ expect(await one("SELECT count(*)::int AS n FROM app.users WHERE login IS DISTINCT FROM email")).toEqual({ n: 0 });
153
+ // Nothing visible to a plan: the working column is left out, and the rename is still what is refused.
154
+ expect((await plan("users-v2.json")).changes.map((c) => c.rule)).toEqual(["SQLPG205"]);
155
+
156
+ // Writers on the old name go on working, and the trigger fills the new one.
157
+ await admin!.query("INSERT INTO app.users (email) VALUES ('late@example.com')");
158
+ expect(await one("SELECT login FROM app.users WHERE email = 'late@example.com'")).toEqual({ login: "late@example.com" });
159
+ await admin!.query("UPDATE app.users SET email = 'first@example.com' WHERE id = 1");
160
+ expect(await one("SELECT login FROM app.users WHERE id = 1")).toEqual({ login: "first@example.com" });
161
+
162
+ // Running again before anyone approves fills nothing again: the three
163
+ // batches are skipped by their receipts, and the gate stands as it was.
164
+ const again = await runOp(config(), ledger.port);
165
+ expect(again.status).toBe("gated");
166
+ expect(outcome(again, "Skipped")).toBe(3);
167
+ expect(outcome(again, "Filled")).toBe(0);
168
+ expect(again.gate!.planDigest).toBe(r.gate!.planDigest);
169
+ }, 300_000);
170
+
171
+ test("approved, it switches: the new name is the declared column, both names are written, and it stops at the contract gate", async () => {
172
+ ledger.approveLast();
173
+ const r = await runOp(config(), ledger.port);
174
+ expect(r.status).toBe("gated");
175
+ expect(r.gate).toMatchObject({ gate: "approve-migrate-users-login-contract" });
176
+ expect(outcome(r, "OldColumn")).toBe("email");
177
+ expect(await one("SELECT attnotnull FROM pg_attribute WHERE attrelid = 'app.users'::regclass AND attname = 'login'")).toEqual({ attnotnull: true });
178
+ expect(await one("SELECT count(*)::int AS n FROM pg_constraint WHERE conrelid = 'app.users'::regclass AND contype = 'c'")).toEqual({ n: 0 });
179
+ // The plan sees the declaration: login, and email is left out until the contract drops it.
180
+ expect((await plan("users-v2.json")).changes).toEqual([]);
181
+
182
+ // A reader or writer still on the old name works until the contract; a new writer fills both.
183
+ await admin!.query("INSERT INTO app.users (login) VALUES ('new@example.com')");
184
+ expect(await one("SELECT email FROM app.users WHERE login = 'new@example.com'")).toEqual({ email: "new@example.com" });
185
+ await admin!.query("INSERT INTO app.users (email) VALUES ('old@example.com')");
186
+ expect(await one("SELECT login FROM app.users WHERE email = 'old@example.com'")).toEqual({ login: "old@example.com" });
187
+ }, 300_000);
188
+
189
+ test("approved again, it drops the old name, its trigger and its receipts, and the server holds the declaration", async () => {
190
+ ledger.approveLast();
191
+ const r = await runOp(config(), ledger.port);
192
+ expect(r.status).toBe("ok");
193
+ expect(outcome(r, "Dropped")).toBe(true);
194
+ expect(await columnsOf("app.users")).toEqual(["id", "name", "login"]);
195
+ expect(await one("SELECT count(*)::int AS n FROM pg_trigger WHERE tgrelid = 'app.users'::regclass AND NOT tgisinternal")).toEqual({ n: 0 });
196
+ expect(await receiptsTableExists("app")).toBe(false);
197
+ expect((await plan("users-v2.json")).changes).toEqual([]);
198
+ expect(await one("SELECT count(*)::int AS n FROM app.users")).toEqual({ n: 2503 });
199
+
200
+ const done = await runOp(config(), ledger.port);
201
+ expect(done.status).toBe("ok");
202
+ expect(outcome(done, "MigrationState")).toBe("done");
203
+ }, 300_000);
204
+ });
205
+
206
+ describe.skipIf(!enabled)("a column type change across kinds, migrated through its gates", () => {
207
+ const ordersDdl = (amount: string) => `CREATE TABLE shop.orders (id bigint PRIMARY KEY, ${amount}, note text)`;
208
+ const V1: Obj = { export: "orders", type: "Postgres::Table", dependsOn: ["shop"], ddl: ordersDdl("amount text NOT NULL") };
209
+ const V2: Obj = { ...V1, ddl: `${ordersDdl("amount numeric(12,2) NOT NULL DEFAULT 0")};\nCOMMENT ON COLUMN shop.orders.amount IS 'In euros'` };
210
+ const config = (): PostgresMigrationOpConfig => ({
211
+ name: "migrate-orders-amount",
212
+ env: "e2e",
213
+ table: "shop.orders",
214
+ column: "amount",
215
+ build: false,
216
+ path: dir,
217
+ output: "orders-v2.json",
218
+ retain: "0s",
219
+ batchSize: 500,
220
+ stack: MARKER.stack,
221
+ ownershipEnv: MARKER.env,
222
+ });
223
+ const ledger = new Ledger();
224
+
225
+ test("refused in place, then filled, switched by name and contracted", async () => {
226
+ await apply(writeBuild("orders-v1.json", [SHOP, V1]));
227
+ await admin!.query("INSERT INTO shop.orders (id, amount) SELECT g, (g % 100) || '.25' FROM generate_series(1, 1200) g");
228
+ writeBuild("orders-v2.json", [SHOP, V2]);
229
+ const p = await plan("orders-v2.json");
230
+ expect(p.refused.map((c) => [c.rule, c.field])).toEqual([["SQLPG208", "columns.amount.type"]]);
231
+ expect(p.migrationOps?.map((o) => [o.table, o.column])).toEqual([["shop.orders", "amount"]]);
232
+
233
+ const first = await runOp(config(), ledger.port);
234
+ expect(first.status).toBe("gated");
235
+ expect(outcome(first, "Change")).toBe("type");
236
+ expect(outcome(first, "Filled")).toBe(3);
237
+ expect(outcome(first, "VerifiedRows")).toBe(1200);
238
+ expect(await one("SELECT format_type(atttypid, atttypmod) AS t FROM pg_attribute WHERE attrelid = 'shop.orders'::regclass AND attname = 'amount__chant_new'")).toEqual({ t: "numeric(12,2)" });
239
+ // A write during the migration is converted by the trigger.
240
+ await admin!.query("INSERT INTO shop.orders (id, amount) VALUES (5000, '7.5')");
241
+ expect(await one("SELECT amount__chant_new::text AS v FROM shop.orders WHERE id = 5000")).toEqual({ v: "7.50" });
242
+
243
+ ledger.approveLast();
244
+ const switched = await runOp(config(), ledger.port);
245
+ expect(switched.status).toBe("gated");
246
+ expect(switched.gate).toMatchObject({ gate: "approve-migrate-orders-amount-contract" });
247
+ expect(outcome(switched, "OldColumn")).toBe("amount__chant_old");
248
+ expect(await one("SELECT format_type(atttypid, atttypmod) AS t, attnotnull AS nn FROM pg_attribute WHERE attrelid = 'shop.orders'::regclass AND attname = 'amount'")).toEqual({
249
+ t: "numeric(12,2)",
250
+ nn: true,
251
+ });
252
+ expect(await one("SELECT col_description('shop.orders'::regclass, (SELECT attnum FROM pg_attribute WHERE attrelid = 'shop.orders'::regclass AND attname = 'amount')) AS c")).toEqual({ c: "In euros" });
253
+ expect(await one("SELECT sum(amount)::text AS s FROM shop.orders")).toEqual({ s: "59707.50" });
254
+ expect((await plan("orders-v2.json")).changes).toEqual([]);
255
+ // Writers now write the new type; the old column is kept as it was and no longer written.
256
+ await admin!.query("INSERT INTO shop.orders (id, amount) VALUES (5001, 3.333)");
257
+ expect(await one("SELECT amount::text AS v, amount__chant_old AS old FROM shop.orders WHERE id = 5001")).toEqual({ v: "3.33", old: null });
258
+ await admin!.query("INSERT INTO shop.orders (id) VALUES (5002)");
259
+ expect(await one("SELECT amount::text AS v FROM shop.orders WHERE id = 5002")).toEqual({ v: "0.00" });
260
+
261
+ ledger.approveLast();
262
+ const contracted = await runOp(config(), ledger.port);
263
+ expect(contracted.status).toBe("ok");
264
+ expect(await columnsOf("shop.orders")).toEqual(["id", "note", "amount"]);
265
+ expect((await plan("orders-v2.json")).changes).toEqual([]);
266
+ }, 300_000);
267
+ });
268
+
269
+ describe.skipIf(!enabled)("a failure runs onFailure, which drops what the expand added", () => {
270
+ const V1: Obj = { export: "codes", type: "Postgres::Table", dependsOn: ["ref"], ddl: "CREATE TABLE ref.codes (id integer PRIMARY KEY, code text)" };
271
+ const V2: Obj = { ...V1, ddl: "CREATE TABLE ref.codes (id integer PRIMARY KEY, code integer)" };
272
+
273
+ test("a value the new type cannot hold fails the backfill; the new column, trigger, function and receipts are dropped", async () => {
274
+ await apply(writeBuild("codes-v1.json", [REF, V1]));
275
+ await admin!.query("INSERT INTO ref.codes SELECT g, g::text FROM generate_series(1, 300) g");
276
+ await admin!.query("UPDATE ref.codes SET code = 'n/a' WHERE id = 250");
277
+ writeBuild("codes-v2.json", [REF, V2]);
278
+
279
+ const failure = await failed(
280
+ runOp({ name: "migrate-codes", env: "e2e", table: "ref.codes", column: "code", build: false, path: dir, output: "codes-v2.json", batchSize: 100, stack: MARKER.stack, ownershipEnv: MARKER.env }, new Ledger().port),
281
+ );
282
+ const records = failure.result.records;
283
+ expect(records.find((x) => x.fn === "postgresMigrationBackfill")?.error).toMatch(/invalid input syntax for type integer: "n\/a"/);
284
+ expect(records.find((x) => x.fn === "postgresMigrationCompensate")?.status).toBe("ok");
285
+ expect(await columnsOf("ref.codes")).toEqual(["id", "code"]);
286
+ expect(await one("SELECT count(*)::int AS n FROM pg_trigger WHERE tgrelid = 'ref.codes'::regclass AND NOT tgisinternal")).toEqual({ n: 0 });
287
+ expect(await one("SELECT count(*)::int AS n FROM pg_proc WHERE proname = 'codes__code__chant_sync'")).toEqual({ n: 0 });
288
+ expect(await receiptsTableExists("ref")).toBe(false);
289
+ expect((await plan("codes-v2.json")).changes.map((c) => c.rule)).toEqual(["SQLPG208"]);
290
+ }, 300_000);
291
+ });
292
+
293
+ describe.skipIf(!enabled)("an interrupted backfill resumes from its receipts", () => {
294
+ const V1: Obj = { export: "events", type: "Postgres::Table", dependsOn: ["ev"], ddl: "CREATE TABLE ev.events (id bigint PRIMARY KEY, kind varchar(20) NOT NULL)" };
295
+ const V2: Obj = { ...V1, ddl: "CREATE TABLE ev.events (id bigint PRIMARY KEY, kind varchar(10) NOT NULL)" };
296
+ const args = (): PostgresMigrationArgs => ({ table: "ev.events", column: "kind", buildPath: "events-v2.json", environment: "e2e", batchSize: 1000, stack: MARKER.stack, ownershipEnv: MARKER.env, cwd: dir });
297
+ const m = migrationActivities;
298
+
299
+ test("stopped after three batches, killed before a commit, then resumed: every row updated once", async () => {
300
+ await apply(writeBuild("events-v1.json", [EV, V1]));
301
+ await admin!.query("INSERT INTO ev.events SELECT g, CASE WHEN g % 2 = 0 THEN 'click' ELSE 'view' END FROM generate_series(0, 5999) g");
302
+ // Every row update the backfill makes, counted by a trigger of the application's own.
303
+ await admin!.query("CREATE SCHEMA audit");
304
+ await admin!.query("CREATE TABLE audit.updates (id bigint)");
305
+ await admin!.query("CREATE FUNCTION audit.count_update() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO audit.updates VALUES (NEW.id); RETURN NEW; END $$");
306
+ await admin!.query("CREATE TRIGGER audit_update AFTER UPDATE ON ev.events FOR EACH ROW EXECUTE FUNCTION audit.count_update()");
307
+ writeBuild("events-v2.json", [EV, V2]);
308
+
309
+ // varchar(20) to varchar(10) is a rewrite in place (SQLPG207), which the Op makes in batches instead.
310
+ expect((await plan("events-v2.json")).changes.map((c) => c.rule)).toEqual(["SQLPG207"]);
311
+ expect(await m.postgresMigrationPlan(args(), undefined, deps())).toMatchObject({ state: "migrate", change: "type" });
312
+ await m.postgresMigrationExpand(args(), undefined, deps());
313
+ await m.postgresMigrationDualWrite(args(), undefined, deps());
314
+
315
+ // Interrupted (Ctrl-C) after the third batch.
316
+ const stop = new AbortController();
317
+ let done = 0;
318
+ const interrupted = await m
319
+ .postgresMigrationBackfill(args(), stop.signal, deps({ backfill: { afterBatch: () => void (++done === 3 && stop.abort()) } }))
320
+ .then(
321
+ () => undefined,
322
+ (e: unknown) => e,
323
+ );
324
+ expect(interrupted).toBeDefined();
325
+ expect(await one("SELECT count(*)::int AS n FROM ev.events WHERE kind__chant_new IS NOT NULL")).toEqual({ n: 3000 });
326
+
327
+ // Killed after the fourth batch's UPDATE and receipt, before its commit: neither is kept.
328
+ await expect(
329
+ m.postgresMigrationBackfill(
330
+ args(),
331
+ undefined,
332
+ deps({
333
+ backfill: {
334
+ beforeCommit: () => {
335
+ throw new Error("killed before the commit");
336
+ },
337
+ },
338
+ }),
339
+ ),
340
+ ).rejects.toThrow(/killed before the commit/);
341
+ expect(await one("SELECT count(*)::int AS n FROM ev.events WHERE kind__chant_new IS NOT NULL")).toEqual({ n: 3000 });
342
+ expect(await one(`SELECT count(*)::int AS n FROM ev.${POSTGRES_RECEIPTS_TABLE}`)).toEqual({ n: 3 });
343
+
344
+ // Resumed: three batches skipped by receipt, three filled.
345
+ const resumed = await m.postgresMigrationBackfill(args(), undefined, deps());
346
+ expect(resumed).toMatchObject({ batches: 6, skipped: 3, filled: 3, rows: 3000 });
347
+ expect(await one("SELECT count(*)::int AS rows, count(DISTINCT id)::int AS ids, max(n)::int AS most FROM (SELECT id, count(*) AS n FROM audit.updates GROUP BY id) s")).toEqual({
348
+ rows: 6000,
349
+ ids: 6000,
350
+ most: 1,
351
+ });
352
+ expect(await one(`SELECT count(*)::int AS n FROM ev.${POSTGRES_RECEIPTS_TABLE} WHERE address LIKE 'e2e/test/migrate/ev.events.kind/%'`)).toEqual({ n: 6 });
353
+ expect(await m.postgresMigrationVerify(args(), undefined, deps())).toMatchObject({ rows: 6000, mismatched: 0 });
354
+
355
+ // The receipts table is chant's own bookkeeping, not a declared schema.
356
+ expect((await plan("events-v2.json")).changes.map((c) => c.object)).not.toContain(`relation ev.${POSTGRES_RECEIPTS_TABLE}`);
357
+
358
+ const dropped = await m.postgresMigrationCompensate(args(), undefined, deps());
359
+ expect(dropped.dropped).toEqual(["trigger kind__chant_sync", "function ev.events__kind__chant_sync", "column ev.events.kind__chant_new", "6 receipt(s)"]);
360
+ expect(await receiptsTableExists("ev")).toBe(false);
361
+ }, 300_000);
362
+ });