@intentius/chant-lexicon-sql 0.124.46 → 0.124.48

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 (360) hide show
  1. package/dist/change-set.d.ts +1 -1
  2. package/dist/change-set.d.ts.map +1 -1
  3. package/dist/change-set.js +2 -1
  4. package/dist/clickhouse/apply/statements.d.ts +9 -0
  5. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  6. package/dist/clickhouse/apply/statements.js +24 -0
  7. package/dist/clickhouse/import/live-export.d.ts +7 -3
  8. package/dist/clickhouse/import/live-export.d.ts.map +1 -1
  9. package/dist/clickhouse/import/live-export.js +21 -5
  10. package/dist/clickhouse/live/named-collections.d.ts +27 -0
  11. package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
  12. package/dist/clickhouse/live/named-collections.js +53 -0
  13. package/dist/clickhouse/live/undeclarable.d.ts +15 -3
  14. package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
  15. package/dist/clickhouse/live/undeclarable.js +18 -4
  16. package/dist/clickhouse/named-collections.d.ts +83 -0
  17. package/dist/clickhouse/named-collections.d.ts.map +1 -0
  18. package/dist/clickhouse/named-collections.js +203 -0
  19. package/dist/clickhouse/parser.d.ts.map +1 -1
  20. package/dist/clickhouse/parser.js +1 -1
  21. package/dist/clickhouse/plan/commands.d.ts +2 -1
  22. package/dist/clickhouse/plan/commands.d.ts.map +1 -1
  23. package/dist/clickhouse/plan/commands.js +17 -2
  24. package/dist/clickhouse/plan/diff.d.ts +15 -0
  25. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  26. package/dist/clickhouse/plan/diff.js +43 -1
  27. package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
  28. package/dist/clickhouse/plan/disruption.js +5 -0
  29. package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
  30. package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
  31. package/dist/clickhouse/plan/report.d.ts.map +1 -1
  32. package/dist/clickhouse/plan/report.js +6 -1
  33. package/dist/clickhouse/plan/rules.d.ts +5 -1
  34. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  35. package/dist/clickhouse/plan/rules.js +7 -3
  36. package/dist/clickhouse/plan/stream.d.ts +40 -0
  37. package/dist/clickhouse/plan/stream.d.ts.map +1 -0
  38. package/dist/clickhouse/plan/stream.js +107 -0
  39. package/dist/clickhouse/rebuild/observe.d.ts +3 -1
  40. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
  41. package/dist/clickhouse/rebuild/observe.js +14 -1
  42. package/dist/clickhouse/review-catalog.d.ts +213 -0
  43. package/dist/clickhouse/review-catalog.d.ts.map +1 -0
  44. package/dist/clickhouse/review-catalog.js +330 -0
  45. package/dist/codegen/classifier-page.d.ts.map +1 -1
  46. package/dist/codegen/classifier-page.js +21 -4
  47. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  48. package/dist/codegen/clickhouse-module.js +3 -0
  49. package/dist/codegen/coverage-page.d.ts.map +1 -1
  50. package/dist/codegen/coverage-page.js +5 -3
  51. package/dist/codegen/docs.js +1 -1
  52. package/dist/codegen/postgres-classifier-page.js +1 -1
  53. package/dist/codegen/registry.d.ts.map +1 -1
  54. package/dist/codegen/registry.js +5 -0
  55. package/dist/composites/catalog.d.ts.map +1 -1
  56. package/dist/composites/catalog.js +100 -0
  57. package/dist/composites/clickhouse.d.ts +2 -0
  58. package/dist/composites/clickhouse.d.ts.map +1 -1
  59. package/dist/composites/clickhouse.js +2 -0
  60. package/dist/composites/otel-tables.d.ts +100 -0
  61. package/dist/composites/otel-tables.d.ts.map +1 -0
  62. package/dist/composites/otel-tables.js +282 -0
  63. package/dist/composites/stream-ingest.d.ts +63 -0
  64. package/dist/composites/stream-ingest.d.ts.map +1 -0
  65. package/dist/composites/stream-ingest.js +39 -0
  66. package/dist/coverage/clickhouse.json +1 -1
  67. package/dist/coverage/postgres.json +7 -7
  68. package/dist/coverage.d.ts +16 -6
  69. package/dist/coverage.d.ts.map +1 -1
  70. package/dist/coverage.js +10 -5
  71. package/dist/drizzle-command.d.ts +17 -0
  72. package/dist/drizzle-command.d.ts.map +1 -0
  73. package/dist/drizzle-command.js +91 -0
  74. package/dist/drizzle.d.ts +70 -0
  75. package/dist/drizzle.d.ts.map +1 -0
  76. package/dist/drizzle.js +684 -0
  77. package/dist/files/clickhouse.d.ts.map +1 -1
  78. package/dist/files/clickhouse.js +8 -1
  79. package/dist/files/common.d.ts +13 -0
  80. package/dist/files/common.d.ts.map +1 -1
  81. package/dist/files/common.js +5 -0
  82. package/dist/files/postgres.js +12 -2
  83. package/dist/generated/clickhouse.d.ts +2 -0
  84. package/dist/generated/clickhouse.d.ts.map +1 -1
  85. package/dist/generated/clickhouse.js +2 -0
  86. package/dist/import-parser.d.ts +21 -1
  87. package/dist/import-parser.d.ts.map +1 -1
  88. package/dist/import-parser.js +79 -5
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/integrity.json +6 -5
  93. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  94. package/dist/lint/post-synth/audit-entries.js +1 -0
  95. package/dist/lint/post-synth/index.d.ts.map +1 -1
  96. package/dist/lint/post-synth/index.js +2 -0
  97. package/dist/lint/post-synth/sqlch128.d.ts +18 -0
  98. package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
  99. package/dist/lint/post-synth/sqlch128.js +222 -0
  100. package/dist/lint/rules/postgres-templates.js +2 -2
  101. package/dist/lsp/postgres-template.d.ts.map +1 -1
  102. package/dist/lsp/postgres-template.js +11 -0
  103. package/dist/manifest.json +1 -1
  104. package/dist/mcp/postgres.js +1 -1
  105. package/dist/meta.json +25 -0
  106. package/dist/migrate/from-postgres/detect.d.ts +4 -0
  107. package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
  108. package/dist/migrate/from-postgres/detect.js +7 -0
  109. package/dist/migrate/from-postgres/emit.d.ts +26 -0
  110. package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
  111. package/dist/migrate/from-postgres/emit.js +86 -0
  112. package/dist/migrate/from-postgres/index.d.ts +51 -0
  113. package/dist/migrate/from-postgres/index.d.ts.map +1 -0
  114. package/dist/migrate/from-postgres/index.js +115 -0
  115. package/dist/migrate/from-postgres/translate.d.ts +73 -0
  116. package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
  117. package/dist/migrate/from-postgres/translate.js +261 -0
  118. package/dist/migrate/from-postgres/types.d.ts +51 -0
  119. package/dist/migrate/from-postgres/types.d.ts.map +1 -0
  120. package/dist/migrate/from-postgres/types.js +211 -0
  121. package/dist/migration-replay.d.ts +73 -0
  122. package/dist/migration-replay.d.ts.map +1 -0
  123. package/dist/migration-replay.js +294 -0
  124. package/dist/okf/index.md +6 -0
  125. package/dist/okf/rules/SQLCH128.md +11 -0
  126. package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
  127. package/dist/okf/types/PostgresForeignServer.md +9 -0
  128. package/dist/okf/types/PostgresOperator.md +9 -0
  129. package/dist/okf/types/PostgresPublication.md +9 -0
  130. package/dist/okf/types/PostgresStatistics.md +9 -0
  131. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  132. package/dist/op/activities/postgres-migration.js +4 -0
  133. package/dist/op/activity-contracts.d.ts +20 -0
  134. package/dist/op/activity-contracts.d.ts.map +1 -1
  135. package/dist/op/activity-contracts.js +2 -0
  136. package/dist/plugin.d.ts.map +1 -1
  137. package/dist/plugin.js +36 -2
  138. package/dist/postgres/apply/apply.js +8 -1
  139. package/dist/postgres/apply/statements.d.ts.map +1 -1
  140. package/dist/postgres/apply/statements.js +87 -2
  141. package/dist/postgres/constraint-names.d.ts +30 -0
  142. package/dist/postgres/constraint-names.d.ts.map +1 -0
  143. package/dist/postgres/constraint-names.js +72 -0
  144. package/dist/postgres/entities.d.ts +110 -1
  145. package/dist/postgres/entities.d.ts.map +1 -1
  146. package/dist/postgres/entities.js +146 -0
  147. package/dist/postgres/entity-types.d.ts +5 -0
  148. package/dist/postgres/entity-types.d.ts.map +1 -1
  149. package/dist/postgres/entity-types.js +5 -0
  150. package/dist/postgres/import/generator.d.ts.map +1 -1
  151. package/dist/postgres/import/generator.js +50 -2
  152. package/dist/postgres/import/live-export.d.ts.map +1 -1
  153. package/dist/postgres/import/live-export.js +2 -0
  154. package/dist/postgres/live/catalog-kinds.d.ts +14 -0
  155. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
  156. package/dist/postgres/live/catalog-kinds.js +206 -0
  157. package/dist/postgres/live/catalog.d.ts +2 -0
  158. package/dist/postgres/live/catalog.d.ts.map +1 -1
  159. package/dist/postgres/live/catalog.js +3 -1
  160. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  161. package/dist/postgres/live/describe-resources.js +10 -2
  162. package/dist/postgres/migrate/backfill.d.ts +2 -0
  163. package/dist/postgres/migrate/backfill.d.ts.map +1 -1
  164. package/dist/postgres/migrate/backfill.js +7 -1
  165. package/dist/postgres/migrate/carry.d.ts +45 -1
  166. package/dist/postgres/migrate/carry.d.ts.map +1 -1
  167. package/dist/postgres/migrate/carry.js +72 -8
  168. package/dist/postgres/migrate/observe.d.ts +28 -4
  169. package/dist/postgres/migrate/observe.d.ts.map +1 -1
  170. package/dist/postgres/migrate/observe.js +41 -12
  171. package/dist/postgres/migrate/op.d.ts +26 -1
  172. package/dist/postgres/migrate/op.d.ts.map +1 -1
  173. package/dist/postgres/migrate/op.js +3 -1
  174. package/dist/postgres/migrate/steps.d.ts +35 -2
  175. package/dist/postgres/migrate/steps.d.ts.map +1 -1
  176. package/dist/postgres/migrate/steps.js +158 -12
  177. package/dist/postgres/parser.d.ts +72 -1
  178. package/dist/postgres/parser.d.ts.map +1 -1
  179. package/dist/postgres/parser.js +194 -2
  180. package/dist/postgres/plan/deep.d.ts.map +1 -1
  181. package/dist/postgres/plan/deep.js +29 -1
  182. package/dist/postgres/plan/diff.d.ts.map +1 -1
  183. package/dist/postgres/plan/diff.js +39 -1
  184. package/dist/postgres/plan/normalize.d.ts +17 -2
  185. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  186. package/dist/postgres/plan/normalize.js +109 -1
  187. package/dist/postgres/plan/rules.d.ts +1 -0
  188. package/dist/postgres/plan/rules.d.ts.map +1 -1
  189. package/dist/postgres/plan/rules.js +3 -2
  190. package/dist/postgres/plan/schema.js +1 -1
  191. package/dist/postgres/secret-options.d.ts +10 -0
  192. package/dist/postgres/secret-options.d.ts.map +1 -0
  193. package/dist/postgres/secret-options.js +9 -0
  194. package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
  195. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  196. package/dist/postgres/testing/roundtrip-schema.js +13 -1
  197. package/dist/postgres.d.ts +1 -1
  198. package/dist/postgres.d.ts.map +1 -1
  199. package/dist/postgres.js +1 -1
  200. package/dist/rules/audit-entries.ts +1 -0
  201. package/dist/rules/postgres-templates.ts +2 -2
  202. package/dist/rules/sqlch128.ts +230 -0
  203. package/dist/serializer.d.ts +5 -0
  204. package/dist/serializer.d.ts.map +1 -1
  205. package/dist/serializer.js +13 -2
  206. package/dist/sql-types-command.d.ts +13 -0
  207. package/dist/sql-types-command.d.ts.map +1 -1
  208. package/dist/sql-types-command.js +31 -16
  209. package/dist/sql-types.d.ts +22 -0
  210. package/dist/sql-types.d.ts.map +1 -1
  211. package/dist/sql-types.js +10 -6
  212. package/dist/sqlserver/lexer.d.ts +37 -0
  213. package/dist/sqlserver/lexer.d.ts.map +1 -0
  214. package/dist/sqlserver/lexer.js +254 -0
  215. package/dist/sqlserver/parse.d.ts +185 -0
  216. package/dist/sqlserver/parse.d.ts.map +1 -0
  217. package/dist/sqlserver/parse.js +767 -0
  218. package/dist/sqlserver/translate.d.ts +34 -0
  219. package/dist/sqlserver/translate.d.ts.map +1 -0
  220. package/dist/sqlserver/translate.js +495 -0
  221. package/dist/sqlserver/types.d.ts +44 -0
  222. package/dist/sqlserver/types.d.ts.map +1 -0
  223. package/dist/sqlserver/types.js +133 -0
  224. package/dist/sqlserver/validate.d.ts +31 -0
  225. package/dist/sqlserver/validate.d.ts.map +1 -0
  226. package/dist/sqlserver/validate.js +67 -0
  227. package/dist/validate.d.ts.map +1 -1
  228. package/dist/validate.js +5 -0
  229. package/package.json +2 -2
  230. package/src/change-set.ts +2 -1
  231. package/src/clickhouse/apply/statements.ts +25 -0
  232. package/src/clickhouse/import/live-export.ts +20 -5
  233. package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
  234. package/src/clickhouse/live/named-collections.ts +61 -0
  235. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
  236. package/src/clickhouse/live/undeclarable.ts +20 -4
  237. package/src/clickhouse/named-collections.test.ts +187 -0
  238. package/src/clickhouse/named-collections.ts +224 -0
  239. package/src/clickhouse/parser.ts +2 -1
  240. package/src/clickhouse/plan/commands.ts +17 -2
  241. package/src/clickhouse/plan/diff.ts +57 -1
  242. package/src/clickhouse/plan/disruption.ts +5 -0
  243. package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
  244. package/src/clickhouse/plan/report.ts +6 -1
  245. package/src/clickhouse/plan/rules.ts +14 -4
  246. package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
  247. package/src/clickhouse/plan/stream.test.ts +237 -0
  248. package/src/clickhouse/plan/stream.ts +111 -0
  249. package/src/clickhouse/rebuild/observe.ts +15 -1
  250. package/src/clickhouse/review-catalog.test.ts +181 -0
  251. package/src/clickhouse/review-catalog.ts +489 -0
  252. package/src/codegen/classifier-page.ts +21 -4
  253. package/src/codegen/clickhouse-module.ts +3 -0
  254. package/src/codegen/coverage-page.ts +5 -3
  255. package/src/codegen/docs.ts +1 -1
  256. package/src/codegen/postgres-classifier-page.ts +1 -1
  257. package/src/codegen/registry.ts +5 -0
  258. package/src/composites/catalog.test.ts +6 -1
  259. package/src/composites/catalog.ts +100 -0
  260. package/src/composites/clickhouse.ts +2 -0
  261. package/src/composites/composites.test.ts +1 -1
  262. package/src/composites/otel-tables.test.ts +118 -0
  263. package/src/composites/otel-tables.ts +339 -0
  264. package/src/composites/stream-ingest.test.ts +114 -0
  265. package/src/composites/stream-ingest.ts +82 -0
  266. package/src/core/undeclarable.test.ts +6 -5
  267. package/src/coverage/clickhouse.json +1 -1
  268. package/src/coverage/postgres.json +7 -7
  269. package/src/coverage.test.ts +27 -2
  270. package/src/coverage.ts +18 -7
  271. package/src/drizzle-command.ts +94 -0
  272. package/src/drizzle-kit.e2e.test.ts +89 -0
  273. package/src/drizzle-types.e2e.test.ts +67 -0
  274. package/src/drizzle.test.ts +358 -0
  275. package/src/drizzle.ts +737 -0
  276. package/src/files/clickhouse.ts +8 -1
  277. package/src/files/common.ts +14 -1
  278. package/src/files/postgres.ts +12 -2
  279. package/src/generated/clickhouse.ts +3 -0
  280. package/src/generated/lexicon-sql.json +25 -0
  281. package/src/import-parser.sqlserver.test.ts +92 -0
  282. package/src/import-parser.ts +79 -5
  283. package/src/index.ts +1 -1
  284. package/src/lint/post-synth/audit-entries.ts +1 -0
  285. package/src/lint/post-synth/index.ts +2 -0
  286. package/src/lint/post-synth/sqlch128.test.ts +107 -0
  287. package/src/lint/post-synth/sqlch128.ts +230 -0
  288. package/src/lint/rules/postgres-templates.ts +2 -2
  289. package/src/lsp/postgres-template.ts +11 -0
  290. package/src/mcp/postgres.ts +1 -1
  291. package/src/migrate/from-postgres/cli.test.ts +165 -0
  292. package/src/migrate/from-postgres/detect.ts +7 -0
  293. package/src/migrate/from-postgres/emit.ts +96 -0
  294. package/src/migrate/from-postgres/example.test.ts +205 -0
  295. package/src/migrate/from-postgres/index.ts +122 -0
  296. package/src/migrate/from-postgres/translate.test.ts +242 -0
  297. package/src/migrate/from-postgres/translate.ts +359 -0
  298. package/src/migrate/from-postgres/types.test.ts +116 -0
  299. package/src/migrate/from-postgres/types.ts +236 -0
  300. package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
  301. package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
  302. package/src/migration-replay.e2e.test.ts +155 -0
  303. package/src/migration-replay.test.ts +173 -0
  304. package/src/migration-replay.ts +289 -0
  305. package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
  306. package/src/op/activities/postgres-migration.ts +4 -0
  307. package/src/op/activity-contracts.ts +2 -0
  308. package/src/plugin.ts +37 -2
  309. package/src/postgres/apply/apply.ts +9 -1
  310. package/src/postgres/apply/statements.ts +74 -1
  311. package/src/postgres/constraint-names.ts +71 -0
  312. package/src/postgres/database-kinds.test.ts +384 -0
  313. package/src/postgres/entities.ts +266 -1
  314. package/src/postgres/entity-types.ts +5 -0
  315. package/src/postgres/import/generator.ts +42 -2
  316. package/src/postgres/import/live-export.ts +1 -0
  317. package/src/postgres/live/catalog-kinds.ts +223 -0
  318. package/src/postgres/live/catalog.ts +6 -1
  319. package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
  320. package/src/postgres/live/describe-resources.ts +11 -2
  321. package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
  322. package/src/postgres/migrate/backfill.ts +13 -1
  323. package/src/postgres/migrate/carry.ts +103 -8
  324. package/src/postgres/migrate/followups.e2e.test.ts +218 -0
  325. package/src/postgres/migrate/followups.test.ts +199 -0
  326. package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
  327. package/src/postgres/migrate/observe.ts +53 -14
  328. package/src/postgres/migrate/op.ts +28 -1
  329. package/src/postgres/migrate/partition.e2e.test.ts +129 -0
  330. package/src/postgres/migrate/partition.test.ts +206 -0
  331. package/src/postgres/migrate/steps.ts +157 -12
  332. package/src/postgres/parser.ts +234 -2
  333. package/src/postgres/plan/deep.ts +28 -1
  334. package/src/postgres/plan/diff.ts +36 -1
  335. package/src/postgres/plan/normalize.ts +122 -1
  336. package/src/postgres/plan/rules.ts +10 -3
  337. package/src/postgres/plan/schema.ts +1 -1
  338. package/src/postgres/secret-options.ts +10 -0
  339. package/src/postgres/testing/roundtrip-schema.ts +13 -1
  340. package/src/postgres.ts +16 -0
  341. package/src/serializer.ts +13 -2
  342. package/src/skills/chant-sql-postgres-migration.md +3 -3
  343. package/src/skills/chant-sql-postgres.md +6 -1
  344. package/src/skills/chant-sql.md +2 -1
  345. package/src/sql-types-command.ts +38 -16
  346. package/src/sql-types.ts +11 -6
  347. package/src/sqlserver/fixture.test.ts +186 -0
  348. package/src/sqlserver/import-cli.test.ts +61 -0
  349. package/src/sqlserver/lexer.ts +260 -0
  350. package/src/sqlserver/parse.ts +865 -0
  351. package/src/sqlserver/translate.test.ts +396 -0
  352. package/src/sqlserver/translate.ts +558 -0
  353. package/src/sqlserver/types.test.ts +102 -0
  354. package/src/sqlserver/types.ts +154 -0
  355. package/src/sqlserver/validate.e2e.test.ts +32 -0
  356. package/src/sqlserver/validate.test.ts +49 -0
  357. package/src/sqlserver/validate.ts +76 -0
  358. package/src/validate.ts +5 -0
  359. package/src/workspace-kinds.test.ts +11 -0
  360. package/workspace-kinds.json +100 -2
@@ -0,0 +1,236 @@
1
+ /**
2
+ * The Postgres to ClickHouse type map (#4399).
3
+ *
4
+ * A type either maps with no judgment (`note` absent) or carries a `note`
5
+ * naming what the map could not keep. Only the first kind is mechanical: the
6
+ * translator writes no diagnostic for it. A type this file does not know (an
7
+ * enum, a domain, an extension type) maps to `String` with a note, because a
8
+ * mirror still has to hold the value.
9
+ *
10
+ * Source types and defaults are from the Postgres data types chapter
11
+ * (https://www.postgresql.org/docs/current/datatype.html): `timestamp` and
12
+ * `time` take a precision of 0 to 6 and default to microseconds, `numeric`
13
+ * with no precision holds any value, `money` is a fixed-point type with a
14
+ * locale-set scale, `float(n)` is `real` for n up to 24 and `double
15
+ * precision` above.
16
+ *
17
+ * Target types are ClickHouse types of the pinned server (`../../spec/pin.ts`);
18
+ * `types.test.ts` checks every family this file writes against the generated
19
+ * catalog.
20
+ */
21
+
22
+ export interface MappedType {
23
+ ok: true;
24
+ /** The ClickHouse type, before `Nullable`. */
25
+ type: string;
26
+ /** The declared maximum length of a character type, when it has one. */
27
+ maxLength?: number;
28
+ /** The column is `serial`, `bigserial` or `smallserial`: an integer with a sequence behind it. */
29
+ serial?: boolean;
30
+ /** The column is an array. ClickHouse cannot make an `Array` `Nullable`. */
31
+ array?: boolean;
32
+ /** Present when the map is a judgment: what was lost or chosen. */
33
+ note?: { message: string; evidence: string[] };
34
+ }
35
+
36
+ export type TypeMapping = MappedType;
37
+
38
+ interface ParsedType {
39
+ /** Lower case words of the type, schema prefix and quotes removed: `timestamp with time zone`. */
40
+ base: string;
41
+ args: number[];
42
+ /** Array dimensions. */
43
+ dims: number;
44
+ raw: string;
45
+ }
46
+
47
+ /** Split a column type as written into its words, its parenthesised arguments and its array dimensions. */
48
+ export function parseType(text: string): ParsedType {
49
+ let rest = text.trim();
50
+ let dims = 0;
51
+ for (;;) {
52
+ const m = /\s*\[\s*\d*\s*\]\s*$/.exec(rest);
53
+ if (!m) break;
54
+ rest = rest.slice(0, m.index);
55
+ dims++;
56
+ }
57
+ const arrayWord = /\s+array(\s*\[\s*\d*\s*\])?$/i.exec(rest);
58
+ if (arrayWord) {
59
+ rest = rest.slice(0, arrayWord.index);
60
+ dims++;
61
+ }
62
+ const args: number[] = [];
63
+ rest = rest.replace(/\(([^)]*)\)/g, (_all, inner: string) => {
64
+ for (const part of inner.split(",")) {
65
+ const n = Number(part.trim());
66
+ if (Number.isFinite(n)) args.push(n);
67
+ }
68
+ return " ";
69
+ });
70
+ const base = rest
71
+ .replace(/"/g, "")
72
+ .replace(/^pg_catalog\./i, "")
73
+ .replace(/\s+/g, " ")
74
+ .trim()
75
+ .toLowerCase();
76
+ return { base, args, dims, raw: text.trim() };
77
+ }
78
+
79
+ const STRING_NOTE = (what: string, evidence: string[]) => ({ message: `${what} is read as String.`, evidence });
80
+
81
+ function mapScalar(t: ParsedType): MappedType {
82
+ const { base, args } = t;
83
+ switch (base) {
84
+ case "smallint":
85
+ case "int2":
86
+ return { ok: true, type: "Int16" };
87
+ case "integer":
88
+ case "int":
89
+ case "int4":
90
+ return { ok: true, type: "Int32" };
91
+ case "bigint":
92
+ case "int8":
93
+ return { ok: true, type: "Int64" };
94
+ case "smallserial":
95
+ case "serial2":
96
+ return { ok: true, type: "Int16", serial: true };
97
+ case "serial":
98
+ case "serial4":
99
+ return { ok: true, type: "Int32", serial: true };
100
+ case "bigserial":
101
+ case "serial8":
102
+ return { ok: true, type: "Int64", serial: true };
103
+ case "real":
104
+ case "float4":
105
+ return { ok: true, type: "Float32" };
106
+ case "double precision":
107
+ case "float8":
108
+ return { ok: true, type: "Float64" };
109
+ case "float":
110
+ return { ok: true, type: (args[0] ?? 53) <= 24 ? "Float32" : "Float64" };
111
+ case "numeric":
112
+ case "decimal": {
113
+ if (args.length === 0) {
114
+ return {
115
+ ok: true,
116
+ type: "Decimal(38, 18)",
117
+ note: {
118
+ message: "numeric with no precision is read as Decimal(38, 18).",
119
+ evidence: ["Postgres stores up to 131072 digits before the point and 16383 after; a ClickHouse Decimal has a fixed precision and scale", "a value that does not fit fails the insert"],
120
+ },
121
+ };
122
+ }
123
+ const p = args[0]!;
124
+ const s = args[1] ?? 0;
125
+ if (p > 76 || s < 0 || s > p) {
126
+ return {
127
+ ok: true,
128
+ type: "Decimal(76, 38)",
129
+ note: { message: `numeric(${String(p)}, ${String(s)}) is read as Decimal(76, 38).`, evidence: ["ClickHouse Decimal holds at most 76 digits"] },
130
+ };
131
+ }
132
+ return { ok: true, type: `Decimal(${String(p)}, ${String(s)})` };
133
+ }
134
+ case "money":
135
+ return {
136
+ ok: true,
137
+ type: "Decimal(19, 2)",
138
+ note: { message: "money is read as Decimal(19, 2).", evidence: ["money takes its scale from the lc_monetary setting of the source server, and two decimals is the common one"] },
139
+ };
140
+ case "boolean":
141
+ case "bool":
142
+ return { ok: true, type: "Bool" };
143
+ case "text":
144
+ case "varchar":
145
+ case "character varying":
146
+ case "name":
147
+ case "citext":
148
+ return { ok: true, type: "String", ...(args[0] !== undefined ? { maxLength: args[0] } : {}) };
149
+ case "char":
150
+ case "character":
151
+ case "bpchar":
152
+ return {
153
+ ok: true,
154
+ type: "String",
155
+ ...(args[0] !== undefined ? { maxLength: args[0] } : {}),
156
+ ...(args[0] !== undefined && args[0] > 1 ? { note: STRING_NOTE("char(n)", ["Postgres pads a char(n) value with spaces to n characters; a String keeps the value as it is loaded"]) } : {}),
157
+ };
158
+ case "bytea":
159
+ return { ok: true, type: "String" };
160
+ case "uuid":
161
+ return { ok: true, type: "UUID" };
162
+ case "date":
163
+ return { ok: true, type: "Date32" };
164
+ case "time":
165
+ case "time without time zone":
166
+ return { ok: true, type: `Time64(${String(args[0] ?? 6)})` };
167
+ case "timetz":
168
+ case "time with time zone":
169
+ return { ok: true, type: "String", note: STRING_NOTE("timetz", ["a time of day with an offset has no ClickHouse type; the offset is kept in the text"]) };
170
+ case "timestamp":
171
+ case "timestamp without time zone":
172
+ return { ok: true, type: `DateTime64(${String(args[0] ?? 6)})` };
173
+ case "timestamptz":
174
+ case "timestamp with time zone":
175
+ return { ok: true, type: `DateTime64(${String(args[0] ?? 6)}, 'UTC')` };
176
+ case "interval":
177
+ return { ok: true, type: "String", note: STRING_NOTE("interval", ["a ClickHouse Interval type cannot be stored in a column", "the value is kept as Postgres prints it"]) };
178
+ case "json":
179
+ case "jsonb":
180
+ return { ok: true, type: "JSON" };
181
+ case "oid":
182
+ return { ok: true, type: "UInt32" };
183
+ case "inet":
184
+ case "cidr":
185
+ return {
186
+ ok: true,
187
+ type: "IPv6",
188
+ note: { message: `${base} is read as IPv6.`, evidence: ["an IPv6 column holds an IPv4 address in its mapped form", "the prefix length of a network is not kept"] },
189
+ };
190
+ case "xml":
191
+ case "macaddr":
192
+ case "macaddr8":
193
+ case "tsvector":
194
+ case "tsquery":
195
+ case "bit":
196
+ case "bit varying":
197
+ case "varbit":
198
+ case "point":
199
+ case "line":
200
+ case "lseg":
201
+ case "box":
202
+ case "path":
203
+ case "polygon":
204
+ case "circle":
205
+ return { ok: true, type: "String", note: STRING_NOTE(base, [`ClickHouse has no ${base} type`, "the value is kept as Postgres prints it"]) };
206
+ default:
207
+ return {
208
+ ok: true,
209
+ type: "String",
210
+ note: STRING_NOTE(t.raw, [`${t.raw} is not a built-in type chant maps (an enum, a domain, a composite or an extension type)`, "the value is kept as Postgres prints it; an enum could be an Enum8 or Enum16 once its labels are settled"]),
211
+ };
212
+ }
213
+ }
214
+
215
+ /** Map a column type as the declaration wrote it. */
216
+ export function mapPostgresType(text: string): TypeMapping {
217
+ const t = parseType(text);
218
+ const inner = mapScalar(t);
219
+ if (t.dims === 0) return inner;
220
+ let type = inner.type;
221
+ for (let i = 0; i < t.dims; i++) type = `Array(${type})`;
222
+ return {
223
+ ok: true,
224
+ type,
225
+ array: true,
226
+ ...(inner.serial ? { serial: true } : {}),
227
+ note: {
228
+ message: `${t.raw} is read as ${type}.`,
229
+ evidence: [
230
+ ...(inner.note ? [inner.note.message] : []),
231
+ "ClickHouse cannot make an Array Nullable, so a NULL array is stored as an empty one",
232
+ "Postgres does not enforce the declared dimensions or lengths of an array",
233
+ ],
234
+ },
235
+ };
236
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Fourth Done-when box of #4399: `--validate` creates the translated example
3
+ * on the pinned ClickHouse server, as a bare table and as a `CdcMirror` call
4
+ * with its view, and reports a statement the server refuses by name.
5
+ *
6
+ * Needs Docker; skips cleanly without it. One throwaway container per call,
7
+ * removed afterwards even on failure. Written, not run, in the session that
8
+ * added it (no e2e and no Docker, by the brief).
9
+ */
10
+
11
+ import { join } from "node:path";
12
+ import { describe, expect, test } from "vitest";
13
+ import { dockerAvailable } from "../../clickhouse/container";
14
+ import type { Declarable } from "@intentius/chant/declarable";
15
+ import { transform } from "./index";
16
+ import * as source from "../../../examples/postgres-cdc-source/src/shop";
17
+ import { translatePostgres } from "./translate";
18
+
19
+ const enabled = await dockerAvailable();
20
+ const sourceFile = join(import.meta.dirname, "../../../examples/postgres-cdc-source");
21
+
22
+ describe.skipIf(!enabled)("the emulator accepts the translated example", () => {
23
+ test.each([
24
+ ["bare tables", false],
25
+ ["CdcMirror calls and their views", true],
26
+ ])("every statement of %s is created", async (_name, useComposites) => {
27
+ const r = await transform("", { emit: "ts", useComposites, sourceFile });
28
+ const found = await r.validate!();
29
+ expect(found, found.map((d) => String(d.message)).join("\n")).toEqual([]);
30
+ }, 300_000);
31
+
32
+ test("a statement the server refuses is an error naming the table", async () => {
33
+ // A key column the table does not have is caught by the server, not by the translator.
34
+ const entities = new Map(Object.entries(source) as Array<[string, Declarable]>);
35
+ const t = translatePostgres(entities, { composite: false });
36
+ t.mirrors[0]!.primaryKey = ["no_such_column"];
37
+ const { validateOnEmulator } = await import("../../sqlserver/validate");
38
+ const { objectsToIR } = await import("../../clickhouse/import/ir");
39
+ const { mirrorObjects } = await import("./emit");
40
+ const found = await validateOnEmulator(objectsToIR(mirrorObjects(t.mirrors, false)));
41
+ expect(found).toHaveLength(1);
42
+ expect(found[0]).toMatchObject({ ruleId: "SQLRV012", severity: "error", entity: "customers" });
43
+ }, 300_000);
44
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Third Done-when box of #4399, through `chant workspace check`: a workspace
3
+ * with the Postgres member `db` (the DDL of `examples/postgres-cdc-source`)
4
+ * and the ClickHouse member `mirror`. `chant migrate` writes the mirror and its
5
+ * links; dropping a mirrored column from the Postgres side then fails the
6
+ * check, naming the mirror, until the mirror follows.
7
+ *
8
+ * The unit version of the same facts, on the example's own declarations, is
9
+ * `example.test.ts`. This one reads a real declaration file and folds the
10
+ * member's `.sql` file the way `chant workspace check` does, so it is e2e
11
+ * under the 5 s unit budget (#2817). Written, not run, in the session that
12
+ * added it.
13
+ */
14
+
15
+ import { execFileSync } from "node:child_process";
16
+ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { dirname, join } from "node:path";
19
+ import { afterAll, afterEach, describe, expect, test, vi } from "vitest";
20
+ import { runDeclarationChecks } from "@intentius/chant/workspace/checks";
21
+ import { parseArgs } from "@intentius/chant/cli/parse-args";
22
+ import { runMigrate } from "@intentius/chant/cli/handlers/migrate";
23
+
24
+ const scratch: string[] = [];
25
+ afterAll(() => {
26
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
27
+ });
28
+ afterEach(() => vi.restoreAllMocks());
29
+
30
+ function repo(files: Record<string, string>): string {
31
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-4399-")));
32
+ scratch.push(root);
33
+ execFileSync("git", ["init", "-q"], { cwd: root });
34
+ for (const [path, text] of Object.entries(files)) {
35
+ mkdirSync(dirname(join(root, path)), { recursive: true });
36
+ writeFileSync(join(root, path), text);
37
+ }
38
+ return root;
39
+ }
40
+
41
+ const schema = (email: string) => `CREATE SCHEMA shop;
42
+
43
+ CREATE TABLE shop.customers (
44
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
45
+ ${email} country text NOT NULL,
46
+ created_at timestamptz NOT NULL DEFAULT now()
47
+ );
48
+
49
+ CREATE TABLE shop.orders (
50
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
51
+ customer_id bigint NOT NULL REFERENCES shop.customers (id),
52
+ total numeric(12, 2) NOT NULL,
53
+ status text NOT NULL,
54
+ placed_at timestamptz NOT NULL DEFAULT now()
55
+ );
56
+ `;
57
+
58
+ const EMAIL = " email text NOT NULL,\n";
59
+ const config = (dialect: string) => `import type { ChantConfig } from "@intentius/chant";\nexport default { lexicons: ["sql"], sql: { dialect: "${dialect}" } } satisfies ChantConfig;\n`;
60
+
61
+ function shop(): string {
62
+ return repo({
63
+ "chant.workspace.json": `${JSON.stringify({ name: "shop", schema: 1, members: [{ name: "db", dir: "db", kind: "chant" }, { name: "mirror", dir: "mirror", kind: "chant" }] }, null, 2)}\n`,
64
+ "db/chant.config.ts": config("postgres"),
65
+ "db/schema.sql": schema(EMAIL),
66
+ "mirror/chant.config.ts": config("clickhouse"),
67
+ });
68
+ }
69
+
70
+ async function migrate(root: string): Promise<number> {
71
+ vi.spyOn(console, "error").mockImplementation(() => {});
72
+ return runMigrate({
73
+ args: parseArgs(["migrate", join(root, "db/schema.sql"), "--from", "sql/postgres", "--to", "sql/clickhouse", "--use-composites", "--output", join(root, "mirror/src/mirrors.ts")]),
74
+ plugins: [],
75
+ serializers: [],
76
+ } as never);
77
+ }
78
+
79
+ describe("chant migrate links the mirror to the Postgres member, and chant workspace check holds them", () => {
80
+ test("the links resolve; dropping a mirrored column fails the check naming the mirror; unlinking it and migrating again passes", async () => {
81
+ const root = shop();
82
+ expect(await migrate(root)).toBe(0);
83
+ const declared = JSON.parse(readFileSync(join(root, "chant.workspace.json"), "utf8")) as { members: Array<{ name: string; links?: Array<{ member: string; output: string }> }> };
84
+ const links = declared.members.find((m) => m.name === "mirror")!.links!;
85
+ expect(links).toHaveLength(9);
86
+ expect(links).toContainEqual({ member: "db", output: "shop.customers.email" });
87
+
88
+ const ok = await runDeclarationChecks(root);
89
+ expect(ok.diagnostics.filter((d) => d.ruleId.startsWith("WSP09"))).toEqual([]);
90
+ expect(ok.links.filter((r) => r.origin === "declared" && r.status === "resolved")).toHaveLength(9);
91
+
92
+ // The Postgres side drops email.
93
+ writeFileSync(join(root, "db/schema.sql"), schema(""));
94
+ const failed = await runDeclarationChecks(root);
95
+ expect(failed.ok).toBe(false);
96
+ expect(failed.diagnostics.map((d) => `${d.ruleId}:${d.entity}`)).toEqual(["WSP093:mirror"]);
97
+ expect(failed.diagnostics[0]!.message).toContain("db has no output shop.customers.email");
98
+
99
+ // The mirror follows: the link goes with the column, and migrating again adds nothing it should not.
100
+ const edited = { ...declared, members: declared.members.map((m) => (m.name === "mirror" ? { ...m, links: m.links!.filter((l) => l.output !== "shop.customers.email") } : m)) };
101
+ writeFileSync(join(root, "chant.workspace.json"), `${JSON.stringify(edited, null, 2)}\n`);
102
+ expect(await migrate(root)).toBe(0);
103
+ const after = await runDeclarationChecks(root);
104
+ expect(after.diagnostics.filter((d) => d.ruleId.startsWith("WSP09"))).toEqual([]);
105
+ });
106
+
107
+ test("a renamed column with a previously hint tells the mirror where it went", async () => {
108
+ const root = shop();
109
+ await migrate(root);
110
+ writeFileSync(join(root, "db/schema.sql"), schema(" email_hash text NOT NULL, -- previously: email\n"));
111
+ const report = await runDeclarationChecks(root);
112
+ expect(report.diagnostics[0]!.message).toContain("it was renamed to shop.customers.email_hash, so link to that instead");
113
+ });
114
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Goose and golang-migrate directories replayed into a Postgres emulator
3
+ * (#4400): the replay records the tables and columns, a dbt member links to
4
+ * one of them, `chant workspace check` passes, and a migration that drops the
5
+ * linked column fails it after the next replay. Also the `--check` of the
6
+ * recorded file.
7
+ *
8
+ * Needs Docker; skips cleanly without it. One throwaway `postgres` container,
9
+ * a fresh database for each replay. Not part of the unit suite.
10
+ */
11
+
12
+ import { spawnSync } from "node:child_process";
13
+ import { readFileSync, symlinkSync, writeFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { pathToFileURL } from "node:url";
16
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
17
+ import { cleanScratch, declaration, REPO, repo } from "../../../packages/core/src/workspace/__fixtures__/contract-repo";
18
+ import { workingTree } from "@intentius/chant/workspace/tree";
19
+ import { REPLAY_FILE } from "@intentius/chant/workspace/migration-dirs";
20
+ import { dockerAvailable, startTestPostgres, type TestPostgres } from "./postgres/testing/server";
21
+ import { renderSnapshot, replayDirectory, type ReplayDb, type ReplayKind } from "./migration-replay";
22
+
23
+ const enabled = await dockerAvailable();
24
+ const TIMEOUT = 600_000;
25
+ const LEXICON = "@intentius/chant-lexicon-sql";
26
+ const VERSION = (JSON.parse(readFileSync(join(REPO, "lexicons", "sql", "package.json"), "utf-8")) as { version: string }).version;
27
+ let server: TestPostgres | undefined;
28
+ let databases = 0;
29
+
30
+ beforeAll(async () => {
31
+ if (enabled) server = await startTestPostgres();
32
+ }, TIMEOUT);
33
+
34
+ afterAll(async () => {
35
+ await server?.stop();
36
+ cleanScratch();
37
+ });
38
+
39
+ function chant(cwd: string, ...argv: string[]) {
40
+ return spawnSync(process.execPath, ["--import", pathToFileURL(join(REPO, "node_modules/tsx/dist/loader.mjs")).href, join(REPO, "packages/core/src/cli/main.ts"), ...argv], {
41
+ cwd,
42
+ encoding: "utf-8",
43
+ timeout: TIMEOUT,
44
+ env: { ...process.env, NO_COLOR: "1", TSX_DISABLE_CACHE: "1" },
45
+ });
46
+ }
47
+
48
+ /** An empty database on the emulator, as a {@link ReplayDb}. */
49
+ async function emptyDb(): Promise<ReplayDb> {
50
+ const admin = await server!.connect();
51
+ const name = `replay_${String((databases += 1))}`;
52
+ await admin.query(`CREATE DATABASE ${name}`);
53
+ await admin.end();
54
+ const client = await server!.connect(name);
55
+ return { dialect: "postgres", run: async (sql) => void (await client.query(sql)), rows: (sql) => client.query(sql), close: () => client.end() };
56
+ }
57
+
58
+ const SOURCES = "version: 2\n\nsources:\n - name: shop\n schema: public\n tables:\n - name: orders\n columns:\n - name: id\n - name: customer_id\n";
59
+
60
+ const FIXTURES: Record<"goose" | "golang-migrate", { first: Record<string, string>; drop: Record<string, string> }> = {
61
+ goose: {
62
+ first: {
63
+ "db/00001_orders.sql": "-- +goose Up\nCREATE TABLE orders (id bigint PRIMARY KEY, customer_id bigint NOT NULL);\n-- +goose Down\nDROP TABLE orders;\n",
64
+ },
65
+ drop: { "db/00002_drop_customer.sql": "-- +goose Up\nALTER TABLE orders DROP COLUMN customer_id;\n-- +goose Down\nALTER TABLE orders ADD COLUMN customer_id bigint;\n" },
66
+ },
67
+ "golang-migrate": {
68
+ first: {
69
+ "db/1_orders.up.sql": "CREATE TABLE orders (id bigint PRIMARY KEY, customer_id bigint NOT NULL);\n",
70
+ "db/1_orders.down.sql": "DROP TABLE orders;\n",
71
+ },
72
+ drop: { "db/2_drop_customer.up.sql": "ALTER TABLE orders DROP COLUMN customer_id;\n", "db/2_drop_customer.down.sql": "ALTER TABLE orders ADD COLUMN customer_id bigint;\n" },
73
+ },
74
+ };
75
+
76
+ async function workspace(kind: "goose" | "golang-migrate", extra: Record<string, string>): Promise<string> {
77
+ const root = repo({
78
+ "chant.workspace.json": declaration(
79
+ [
80
+ { name: "db", dir: "db", kind },
81
+ { name: "analytics", dir: "analytics", kind: "dbt", fields: { sqlMember: "db" } },
82
+ ],
83
+ { pins: [{ package: LEXICON, version: VERSION }] },
84
+ ),
85
+ "analytics/dbt_project.yml": "name: analytics\nversion: '1.0'\nprofile: analytics\n",
86
+ "analytics/models/staging/sources.yml": SOURCES,
87
+ ".gitignore": "node_modules\n",
88
+ ...FIXTURES[kind].first,
89
+ ...extra,
90
+ });
91
+ symlinkSync(join(REPO, "node_modules"), join(root, "node_modules"), "dir");
92
+ return root;
93
+ }
94
+
95
+ /** Replay `root/db` into a fresh database and write the snapshot beside the migrations. */
96
+ async function record(root: string, kind: ReplayKind): Promise<void> {
97
+ const db = await emptyDb();
98
+ try {
99
+ const snapshot = await replayDirectory(db, kind, workingTree(join(root, "db")), "");
100
+ writeFileSync(join(root, "db", REPLAY_FILE), renderSnapshot(snapshot));
101
+ } finally {
102
+ await db.close();
103
+ }
104
+ }
105
+
106
+ describe.skipIf(!enabled)("migration directories replayed into the emulator (#4400)", () => {
107
+ for (const kind of ["goose", "golang-migrate"] as const) {
108
+ test(
109
+ `${kind}: the replayed columns are outputs a dbt member links to, and a migration that drops a linked column fails workspace check`,
110
+ async () => {
111
+ const ok = await workspace(kind, {});
112
+ await record(ok, kind);
113
+ expect(JSON.parse(readFileSync(join(ok, "db", REPLAY_FILE), "utf-8")).outputs).toEqual(["public.orders", "public.orders.id", "public.orders.customer_id"]);
114
+ const passed = chant(ok, "workspace", "check");
115
+ expect(passed.status, passed.stdout + passed.stderr).toBe(0);
116
+
117
+ const dropped = await workspace(kind, FIXTURES[kind].drop);
118
+ await record(dropped, kind);
119
+ const failed = chant(dropped, "workspace", "check");
120
+ expect(failed.status).toBe(1);
121
+ const text = failed.stdout + failed.stderr;
122
+ expect(text).toContain("analytics/models/staging/sources.yml:10");
123
+ expect(text).toContain("WSP093");
124
+ expect(text).toMatch(/link to db output public\.orders\.customer_id .*does not resolve/);
125
+ },
126
+ TIMEOUT,
127
+ );
128
+
129
+ test(
130
+ `${kind}: a migration added after the replay leaves the outputs unresolved until the replay runs again`,
131
+ async () => {
132
+ const root = await workspace(kind, {});
133
+ await record(root, kind);
134
+ for (const [path, text] of Object.entries(FIXTURES[kind].drop)) writeFileSync(join(root, path), text);
135
+ const stale = chant(root, "workspace", "check");
136
+ expect(stale.stdout + stale.stderr).toContain("changed since chant.replay.json was recorded");
137
+ },
138
+ TIMEOUT,
139
+ );
140
+ }
141
+
142
+ test(
143
+ "a migration that fails stops the replay and names its file",
144
+ async () => {
145
+ const root = await workspace("golang-migrate", { "db/2_bad.up.sql": "ALTER TABLE nowhere ADD COLUMN x int;\n" });
146
+ const db = await emptyDb();
147
+ try {
148
+ await expect(replayDirectory(db, "golang-migrate", workingTree(join(root, "db")), "")).rejects.toThrow(/2_bad\.up\.sql: statement 1 failed/);
149
+ } finally {
150
+ await db.close();
151
+ }
152
+ },
153
+ TIMEOUT,
154
+ );
155
+ });