@intentius/chant-lexicon-sql 0.124.45 → 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
@@ -215,7 +215,7 @@ function diffUnparsed(key: string, before: PgDiffObject, after: PgDiffObject, ou
215
215
  }
216
216
 
217
217
  /** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
218
- const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger"]);
218
+ const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger", "operator", "statistics"]);
219
219
 
220
220
  /** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
221
221
  const DEFINITION: Readonly<Record<string, readonly string[]>> = {
@@ -225,8 +225,39 @@ const DEFINITION: Readonly<Record<string, readonly string[]>> = {
225
225
  collation: ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"],
226
226
  cast: ["castMethod", "function", "context"],
227
227
  eventTrigger: ["event", "tags", "function"],
228
+ operator: ["definition"],
229
+ statistics: ["table", "kinds", "targets"],
230
+ };
231
+
232
+ /** The kinds (#3911) the server alters in place, with the fields that cannot be altered (the object is dropped and created) and those that can. */
233
+ const ALTERED_KINDS: Readonly<Record<string, { recreate: readonly string[]; inPlace: readonly string[] }>> = {
234
+ foreignDataWrapper: { recreate: [], inPlace: ["handler", "validator", "options"] },
235
+ foreignServer: { recreate: ["serverType", "wrapper"], inPlace: ["version", "options"] },
236
+ publication: { recreate: ["allTables"], inPlace: ["tables", "schemas", "publish", "viaRoot", "generatedColumns"] },
228
237
  };
229
238
 
239
+ /**
240
+ * A foreign data wrapper's, foreign server's or publication's change: the
241
+ * comment and each field ALTER reaches in place (SQLPG303); a field it does
242
+ * not reach (a server's type and wrapper, a publication's FOR ALL TABLES) by
243
+ * dropping and creating the object (SQLPG300), refused while something
244
+ * depends on it (SQLPG301).
245
+ */
246
+ function diffAltered(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
247
+ if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
248
+ const { recreate, inPlace } = ALTERED_KINDS[after.kind]!;
249
+ const replaced = recreate.filter((k) => !same(before.fields[k], after.fields[k]));
250
+ if (replaced.length > 0) {
251
+ const was = replaced.map((k) => `${k} ${show(before.fields[k]) ?? "-"}`).join("; ");
252
+ const now = replaced.map((k) => `${k} ${show(after.fields[k]) ?? "-"}`).join("; ");
253
+ if (before.dependents && before.dependents.length > 0) {
254
+ out.push(change(key, replaced.join(", "), "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
255
+ } else out.push(change(key, replaced.join(", "), "SQLPG300", was, now));
256
+ return;
257
+ }
258
+ for (const k of inPlace) if (!same(before.fields[k], after.fields[k])) out.push(change(key, k, "SQLPG303", show(before.fields[k]), show(after.fields[k])));
259
+ }
260
+
230
261
  /**
231
262
  * A type's, aggregate's, collation's, cast's or event trigger's change: its
232
263
  * comment in place; its definition by CREATE OR REPLACE AGGREGATE where that
@@ -270,6 +301,10 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
270
301
  diffReplaced(key, before, after, out);
271
302
  return;
272
303
  }
304
+ if (after.kind in ALTERED_KINDS) {
305
+ diffAltered(key, before, after, out);
306
+ return;
307
+ }
273
308
  if (before.schema !== after.schema || before.name !== after.name) {
274
309
  out.push(change(key, "name", after.kind === "index" ? "SQLPG229" : "SQLPG228", qualified(before), qualified(after)));
275
310
  }
@@ -51,6 +51,7 @@ import { stripMarker } from "../../core/ownership";
51
51
  import { sequenceDefaults } from "../live/catalog";
52
52
  import type {
53
53
  AggregateArgDef,
54
+ OperatorProps,
54
55
  AggregateProps,
55
56
  CastProps,
56
57
  CheckDef,
@@ -59,6 +60,10 @@ import type {
59
60
  CompositeTypeProps,
60
61
  DomainProps,
61
62
  EventTriggerProps,
63
+ StatisticsProps,
64
+ ForeignDataWrapperProps,
65
+ ForeignServerProps,
66
+ PublicationProps,
62
67
  RangeTypeProps,
63
68
  EnumProps,
64
69
  ExclusionDef,
@@ -348,6 +353,11 @@ export type CanonicalKind =
348
353
  | "collation"
349
354
  | "cast"
350
355
  | "eventTrigger"
356
+ | "operator"
357
+ | "statistics"
358
+ | "foreignDataWrapper"
359
+ | "foreignServer"
360
+ | "publication"
351
361
  | "function"
352
362
  | "procedure"
353
363
  | "trigger"
@@ -393,6 +403,11 @@ const kindOf: Record<string, CanonicalKind> = {
393
403
  "Postgres::Collation": "collation",
394
404
  "Postgres::Cast": "cast",
395
405
  "Postgres::EventTrigger": "eventTrigger",
406
+ "Postgres::Operator": "operator",
407
+ "Postgres::Statistics": "statistics",
408
+ "Postgres::ForeignDataWrapper": "foreignDataWrapper",
409
+ "Postgres::ForeignServer": "foreignServer",
410
+ "Postgres::Publication": "publication",
396
411
  "Postgres::Extension": "extension",
397
412
  "Postgres::Function": "function",
398
413
  "Postgres::Procedure": "procedure",
@@ -617,7 +632,7 @@ function triggerFields(p: TriggerProps, defaultSchema: string, fields: Record<st
617
632
  // ── Types, aggregates, collations, casts, event triggers (#3911) ──────
618
633
 
619
634
  /** The kinds that live in no schema. */
620
- const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger"]);
635
+ const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger", "foreignDataWrapper", "foreignServer", "publication"]);
621
636
 
622
637
  /**
623
638
  * A function named where an object calls it (an aggregate's SFUNC, a cast's
@@ -700,6 +715,80 @@ function collationFields(p: CollationProps, fields: Record<string, unknown>): vo
700
715
  fields.rules = o.rules;
701
716
  }
702
717
 
718
+ /** An operator's identity among its overloads: its operand types, `(integer,integer)`, `NONE` for a missing side. */
719
+ export function operatorSignature(p: Pick<OperatorProps, "leftArg" | "rightArg">, defaultSchema: string): string {
720
+ const side = (t: string | undefined) => (t === undefined ? "NONE" : canonicalArgType(t, defaultSchema));
721
+ return `(${side(p.leftArg)},${side(p.rightArg)})`;
722
+ }
723
+
724
+ /**
725
+ * An operator named where another object names it (a COMMUTATOR or NEGATOR):
726
+ * `OPERATOR(app.===)`, `app.===` or a bare symbol, which the server looks for
727
+ * in the new operator's own schema, as `schema.symbol`.
728
+ */
729
+ export function canonicalOperatorRef(text: string, ownSchema: string): string {
730
+ const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1");
731
+ const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
732
+ return `${quoteIdent(m ? identValue(m[1]!) : ownSchema)}.${(m ? m[2]! : inner).trim()}`;
733
+ }
734
+
735
+ /** An operator's definition as compared: each function and operator named canonically, a flag only when set. */
736
+ function operatorOptions(p: OperatorProps, ownSchema: string, defaultSchema: string): Record<string, string> {
737
+ const out: Record<string, string> = {};
738
+ for (const [k, v] of Object.entries(p.options)) {
739
+ if (k === "function" || k === "restrict" || k === "join") out[k] = canonicalFunctionName(v, defaultSchema);
740
+ else if (k === "commutator" || k === "negator") out[k] = canonicalOperatorRef(v, ownSchema);
741
+ else if (k === "hashes" || k === "merges") {
742
+ if (isTrue(v)) out[k] = "true";
743
+ } else out[k] = defValue(v);
744
+ }
745
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1)));
746
+ }
747
+
748
+ /** An `OPTIONS (...)` list as compared: by name, none as nothing. */
749
+ function sortedOptions(options: Record<string, string> | undefined): Record<string, string> | undefined {
750
+ const entries = Object.entries(options ?? {}).sort(([a], [b]) => (a < b ? -1 : 1));
751
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
752
+ }
753
+
754
+ const STATISTICS_ALL = ["dependencies", "mcv", "ndistinct"];
755
+
756
+ /**
757
+ * The kinds of statistics gathered, sorted. Left out, the server builds all
758
+ * three, except for a lone expression, which gets none of them (only the
759
+ * expression's own statistics); `pg_get_statisticsobjdef` prints the list only
760
+ * when it is not that default.
761
+ */
762
+ export function statisticsKinds(kinds: readonly string[] | undefined, targets: readonly string[]): string[] | undefined {
763
+ if (kinds && kinds.length > 0) {
764
+ const sorted = [...new Set(kinds.map((k) => k.toLowerCase()))].sort();
765
+ return sorted.join() === STATISTICS_ALL.join() ? undefined : sorted;
766
+ }
767
+ const lone = targets.length === 1 && !/^"?[A-Za-z_][A-Za-z0-9_$]*"?$/.test(targets[0]!.trim());
768
+ return lone ? ["none"] : undefined;
769
+ }
770
+
771
+ /** Every action a publication publishes when `publish` is not given. */
772
+ const PUBLISH_ALL = ["delete", "insert", "truncate", "update"];
773
+
774
+ /** A publication as compared: its tables with their column lists and row filters, its schemas, and each option at its default left out. */
775
+ function publicationFields(p: PublicationProps, defaultSchema: string, fields: Record<string, unknown>): void {
776
+ fields.allTables = p.allTables === true;
777
+ const tables = p.tables.map((t) => ({
778
+ table: canonicalName(t.tableName, defaultSchema),
779
+ ...(t.columns && t.columns.length > 0 ? { columns: [...t.columns].sort() } : {}),
780
+ ...(t.where ? { where: canonicalExpr(t.where) } : {}),
781
+ }));
782
+ fields.tables = tables.length > 0 ? tables.sort((a, b) => (a.table < b.table ? -1 : 1)) : undefined;
783
+ fields.schemas = p.schemas.length > 0 ? p.schemas.map((s) => quoteIdent(identValue(s))).sort() : undefined;
784
+ const o = p.options;
785
+ const publish = o.publish === undefined ? PUBLISH_ALL : [...new Set(defValue(o.publish).split(",").map((x) => x.trim().toLowerCase()).filter(Boolean))].sort();
786
+ fields.publish = publish.join() === PUBLISH_ALL.join() ? undefined : publish;
787
+ fields.viaRoot = isTrue(o.publish_via_partition_root);
788
+ const generated = o.publish_generated_columns === undefined ? "none" : defValue(o.publish_generated_columns);
789
+ fields.generatedColumns = generated === "none" ? undefined : generated;
790
+ }
791
+
703
792
  /** A cast's name, `(source AS target)`, its types canonical. */
704
793
  export const castName = (p: Pick<CastProps, "sourceType" | "targetType">, defaultSchema: string): string =>
705
794
  `(${canonicalType(p.sourceType, defaultSchema)} AS ${canonicalType(p.targetType, defaultSchema)})`;
@@ -774,6 +863,38 @@ export function canonicalPgObject(entityType: string, props: Record<string, unkn
774
863
  fields.function = canonicalFunctionName(p.functionName, defaultSchema);
775
864
  break;
776
865
  }
866
+ case "operator": {
867
+ const p = props as unknown as OperatorProps;
868
+ base.signature = operatorSignature(p, defaultSchema);
869
+ fields.definition = operatorOptions(p, base.schema!, defaultSchema);
870
+ break;
871
+ }
872
+ case "statistics": {
873
+ const p = props as unknown as StatisticsProps;
874
+ fields.table = canonicalName(p.tableName, defaultSchema);
875
+ const targets = p.targets.map((t) => canonicalExpr(t)!).sort();
876
+ fields.targets = targets;
877
+ fields.kinds = statisticsKinds(p.kinds, p.targets);
878
+ break;
879
+ }
880
+ case "foreignDataWrapper": {
881
+ const p = props as unknown as ForeignDataWrapperProps;
882
+ fields.handler = p.handlerName && !p.noHandler ? canonicalFunctionName(p.handlerName, defaultSchema) : undefined;
883
+ fields.validator = p.validatorName && !p.noValidator ? canonicalFunctionName(p.validatorName, defaultSchema) : undefined;
884
+ fields.options = sortedOptions(p.options);
885
+ break;
886
+ }
887
+ case "foreignServer": {
888
+ const p = props as unknown as ForeignServerProps;
889
+ fields.serverType = p.serverType;
890
+ fields.version = p.version;
891
+ fields.wrapper = quoteIdent(identValue(p.wrapperName));
892
+ fields.options = sortedOptions(p.options);
893
+ break;
894
+ }
895
+ case "publication":
896
+ publicationFields(props as unknown as PublicationProps, defaultSchema, fields);
897
+ break;
777
898
  case "function":
778
899
  case "procedure": {
779
900
  const p = props as unknown as RoutineProps;
@@ -360,14 +360,14 @@ export const PG_CLASSIFIER_RULES = {
360
360
  SQLPG300: rule(
361
361
  "SQLPG300",
362
362
  "metadata",
363
- "Drop and create a type, aggregate, collation, cast or event trigger",
364
- "A composite or range type's definition, a collation's, a cast's and an event trigger's have no ALTER, and an aggregate's argument types, state type and final function cannot be replaced: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, a function or a cast would make the DROP fail); the locks are on its catalog rows only.",
363
+ "Drop and create a type, aggregate, collation, cast, event trigger, operator, statistics object, foreign server or publication",
364
+ "A composite or range type's definition, a collation's, a cast's, an event trigger's, an operator's function and operands and extended statistics' have no ALTER, an aggregate's argument types, state type and final function cannot be replaced, and neither can a foreign server's type or wrapper or a publication's FOR ALL TABLES: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, a function or a cast would make the DROP fail); the locks are on its catalog rows only. Dropped statistics are empty until the next ANALYZE, and a publication that is dropped stops its subscribers until it is created again.",
365
365
  `${DOCS}/sql-createtype.html`,
366
366
  ),
367
367
  SQLPG301: rule(
368
368
  "SQLPG301",
369
369
  "expand",
370
- "Change a type, aggregate, collation or cast that other objects depend on",
370
+ "Change a type, aggregate, collation, cast, foreign server or publication that other objects depend on",
371
371
  "No ALTER makes the change, and DROP refuses while a column, a view, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.",
372
372
  `${DOCS}/sql-droptype.html`,
373
373
  ),
@@ -378,6 +378,13 @@ export const PG_CLASSIFIER_RULES = {
378
378
  "CREATE OR REPLACE AGGREGATE (14 and later) replaces the aggregate's functions and options in the catalog; no table is locked. Its argument types, its result and its kind (plain, ordered-set, hypothetical-set) have to stay the same, so a change of the state type or the final function is dropped and created instead (SQLPG300).",
379
379
  `${DOCS}/sql-createaggregate.html`,
380
380
  ),
381
+ SQLPG303: rule(
382
+ "SQLPG303",
383
+ "metadata",
384
+ "Alter a foreign data wrapper, foreign server or publication in place",
385
+ "ALTER FOREIGN DATA WRAPPER (handler, validator, options), ALTER SERVER (version, options) and ALTER PUBLICATION (SET options, ADD and DROP TABLE and TABLES IN SCHEMA) change the object's catalog rows under a brief lock; no table is rewritten. A publication's change reaches its subscribers as the walsender next reads it: a table added is not copied to a subscription until the subscriber runs REFRESH PUBLICATION.",
386
+ `${DOCS}/sql-alterpublication.html`,
387
+ ),
381
388
  SQLPG270: rule("SQLPG270", "drop", "Drop an object", "The object and, for a table, a materialized view or a sequence, its data are gone.", `${DOCS}/sql-droptable.html`),
382
389
  } as const satisfies Record<string, PgClassifierRule>;
383
390
 
@@ -106,7 +106,7 @@ export function pgSchemaFromBuildFile(path: string, defaultSchema = "public"): P
106
106
  */
107
107
  export function pgNamespace(kind: string): string {
108
108
  if (kind === "schema" || kind === "extension" || kind === "trigger" || kind === "policy" || kind === "role") return kind;
109
- if (kind === "collation" || kind === "cast" || kind === "eventTrigger") return kind;
109
+ if (kind === "collation" || kind === "cast" || kind === "eventTrigger" || kind === "operator" || kind === "statistics" || kind === "foreignDataWrapper" || kind === "foreignServer" || kind === "publication") return kind;
110
110
  if (kind === "grant" || kind === "defaultPrivileges") return "grant";
111
111
  if (kind === "enum" || kind === "domain" || kind === "composite" || kind === "range") return "type";
112
112
  if (kind === "function" || kind === "procedure" || kind === "aggregate") return "routine";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Options that hold a credential. A foreign data wrapper's or server's
3
+ * `OPTIONS (...)` can carry any name, and a password in one would be written
4
+ * into a schema file by import and sent to every reader of the plan. The
5
+ * environment supplies credentials, in a user mapping (which is not declared)
6
+ * or its own secret store, as for a role's password (`./entities.ts`).
7
+ */
8
+
9
+ /** Whether an option name reads as a credential: `password`, `sslpassword`, `secret_key`, `api_token`. */
10
+ export const isSecretOption = (name: string): boolean => /pass(word|wd)?|secret|token|api[_-]?key|credential/i.test(name);
@@ -5,7 +5,8 @@
5
5
  * sequence options, storage parameters, comments), an extension and the
6
6
  * domain over its type, a collation, composite and range types (and a
7
7
  * multirange column), plain and ordered-set aggregates, a cast and an event
8
- * trigger (#3911), a partitioned table and a partition, an ORM's
8
+ * trigger, an operator, extended statistics, a foreign data wrapper and server, and a
9
+ * publication (#3911), a partitioned table and a partition, an ORM's
9
10
  * revision table that import leaves out, and an object carrying chant's
10
11
  * ownership trailer.
11
12
  */
@@ -60,5 +61,16 @@ CREATE CAST (app.pair AS text) WITH FUNCTION app.pair_text(app.pair) AS ASSIGNME
60
61
  COMMENT ON CAST (app.pair AS text) IS 'a:b';
61
62
  CREATE FUNCTION app.audit() RETURNS event_trigger LANGUAGE plpgsql AS $$ BEGIN END $$;
62
63
  CREATE EVENT TRIGGER app_audit ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION app.audit();
64
+ CREATE FUNCTION app.int_same(a integer, b integer) RETURNS boolean LANGUAGE sql IMMUTABLE AS $$ SELECT a = b $$;
65
+ CREATE OPERATOR app.=== (FUNCTION = app.int_same, LEFTARG = integer, RIGHTARG = integer, COMMUTATOR = OPERATOR(app.===), RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES);
66
+ COMMENT ON OPERATOR app.===(integer, integer) IS 'Same value';
67
+ CREATE STATISTICS app.orders_stats (dependencies) ON user_id, status FROM app.orders;
68
+ COMMENT ON STATISTICS app.orders_stats IS 'Users and their order states';
69
+ CREATE FOREIGN DATA WRAPPER app_fdw OPTIONS (debug 'true');
70
+ COMMENT ON FOREIGN DATA WRAPPER app_fdw IS 'Remote tables';
71
+ CREATE SERVER app_replica TYPE 'pg' VERSION '16' FOREIGN DATA WRAPPER app_fdw OPTIONS (host 'db.internal', port '5432');
72
+ COMMENT ON SERVER app_replica IS 'The read replica';
73
+ CREATE PUBLICATION app_pub FOR TABLE app.orders, app.users WITH (publish = 'insert, update');
74
+ COMMENT ON PUBLICATION app_pub IS 'Order changes';
63
75
  CREATE TABLE public._prisma_migrations (id varchar(36) PRIMARY KEY, checksum varchar(64) NOT NULL);
64
76
  `;
package/src/postgres.ts CHANGED
@@ -20,6 +20,11 @@ export {
20
20
  aggregate,
21
21
  trigger,
22
22
  eventTrigger,
23
+ operator,
24
+ statistics,
25
+ foreignDataWrapper,
26
+ foreignServer,
27
+ publication,
23
28
  collation,
24
29
  cast,
25
30
  policy,
@@ -49,6 +54,11 @@ export {
49
54
  type PostgresCollation,
50
55
  type PostgresCast,
51
56
  type PostgresEventTrigger,
57
+ type PostgresOperator,
58
+ type PostgresStatistics,
59
+ type PostgresForeignDataWrapper,
60
+ type PostgresForeignServer,
61
+ type PostgresPublication,
52
62
  type PostgresExtension,
53
63
  type PostgresFunction,
54
64
  type PostgresProcedure,
@@ -75,6 +85,12 @@ export {
75
85
  type CollationProps,
76
86
  type CastProps,
77
87
  type EventTriggerProps,
88
+ type OperatorProps,
89
+ type StatisticsProps,
90
+ type ForeignDataWrapperProps,
91
+ type ForeignServerProps,
92
+ type PublicationProps,
93
+ type PublicationTableDef,
78
94
  type DomainProps,
79
95
  type ExtensionProps,
80
96
  type RoutineProps,
package/src/serializer.ts CHANGED
@@ -21,6 +21,11 @@
21
21
  * of each. Two databases of different dialects are two projects (two
22
22
  * workspace members, #3047 question 1).
23
23
  *
24
+ * A ClickHouse object that names a named collection (an engine's first
25
+ * argument, a table function's, a dictionary source's `NAME`) carries
26
+ * `namedCollections`, and the document lists every one the build names, so
27
+ * a plan can check each exists on the server (#4395).
28
+ *
24
29
  * A Postgres document also carries `postgresMajor`, the major the project
25
30
  * targets (`sql.postgresMajor`, else the newest pinned major). Post-synth
26
31
  * checks and the lock classifier read it; ClickHouse output has no such field.
@@ -43,6 +48,7 @@ import { sqlFileLineOf, sqlFileOf, sqlFileReferences } from "./sql-files";
43
48
  import { relative } from "node:path";
44
49
  import { getProvenance } from "@intentius/chant/provenance";
45
50
  import type { UnparsedClause } from "./core/interpolation";
51
+ import { namedCollectionReferences, type CollectionReferrer } from "./clickhouse/named-collections";
46
52
 
47
53
  export { applyOrder } from "./core/references";
48
54
 
@@ -89,8 +95,9 @@ function objectJson(name: string, obj: ClickHouseObject, names: Map<Declarable,
89
95
  props.lineage = lineageJson(props.lineage as LineageEdge[], names);
90
96
  }
91
97
  const dependsOn = [...new Set([...obj.dependsOn.map((r) => referenceName(r, names)).filter((n) => n !== undefined), ...extra])];
98
+ const collections = namedCollectionReferences(props as CollectionReferrer);
92
99
  return JSON.parse(
93
- JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...props, dependsOn, ddl }),
100
+ JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...props, dependsOn, ...(collections.length > 0 ? { namedCollections: collections } : {}), ddl }),
94
101
  ) as Record<string, unknown>;
95
102
  }
96
103
 
@@ -170,10 +177,14 @@ export const sqlSerializer: Serializer = {
170
177
 
171
178
  const extra = sqlFileReferences("clickhouse", objects);
172
179
  const order = applyOrder(objects, names, extra);
180
+ const json = order.map((n) => objectJson(n, objects.get(n)!, names, extra.get(n)));
181
+ // The named collections the build references (#4395), each once, by name: a plan checks each exists.
182
+ const collections = [...new Set(json.flatMap((o) => (o.namedCollections as string[] | undefined) ?? []))].sort();
173
183
  const doc = {
174
184
  dialect: "clickhouse",
175
185
  applyOrder: order,
176
- objects: order.map((n) => objectJson(n, objects.get(n)!, names, extra.get(n))),
186
+ ...(collections.length > 0 ? { namedCollections: collections } : {}),
187
+ objects: json,
177
188
  };
178
189
  const ddl = order
179
190
  .map((n) => `${(objects.get(n)!.props as { ddl: string }).ddl};`)
@@ -114,12 +114,12 @@ or set `replicationLag: false` to run without the check.
114
114
  The Plan phase refuses, naming each object and what to do, a table or column with:
115
115
 
116
116
  - something on the column it does not carry over: an exclusion constraint, a constraint trigger, a materialized view, a rule, a trigger, a policy, a statistics object, a generated column computed from it, an INVALID index, or a view the build does not declare;
117
- - on a partitioned table, an index, key or foreign key on the column, or the column in the partition key (chant #3333);
117
+ - on a partitioned table, a key or foreign key on the column, or the column in the partition key;
118
118
  - a partition (run the Op on its partitioned table) or an inheritance tree (chant #3333);
119
119
  - a generated column itself, which the applier changes in place;
120
120
  - no primary key;
121
- - a rename combined with a type change: run the Op twice, the rename first (chant #3331);
122
- - a rename of a column with a default, an identity or a serial sequence: drop the default, rename, declare it again (chant #3331);
121
+ - a rename combined with a type change, unless the Op gives `reverse` (the old column's value from the new one); with a default, a serial or an identity as well, run the Op twice, the rename first;
122
+ - a rename of a column with a default, an identity or a serial sequence, unless the Op gives `defaultAtContract: true` (the default lands on the new column at the contract; an explicit NULL into the new name gets the default until then);
123
123
  - a column a logical replication publication sends; a column list that leaves it out does not stop the Op (chant #3332);
124
124
  - a column that needs no migration, where the message says to use the applier.
125
125
 
@@ -42,6 +42,11 @@ The export name (`users`) is the object's identity in chant. The name in the SQL
42
42
  | `aggregate` | `CREATE AGGREGATE` | `Postgres::Aggregate` |
43
43
  | `trigger` | `CREATE TRIGGER` | `Postgres::Trigger` |
44
44
  | `eventTrigger` | `CREATE EVENT TRIGGER` | `Postgres::EventTrigger` |
45
+ | `operator` | `CREATE OPERATOR` | `Postgres::Operator` |
46
+ | `statistics` | `CREATE STATISTICS` | `Postgres::Statistics` |
47
+ | `foreignDataWrapper` | `CREATE FOREIGN DATA WRAPPER` | `Postgres::ForeignDataWrapper` |
48
+ | `foreignServer` | `CREATE SERVER` | `Postgres::ForeignServer` |
49
+ | `publication` | `CREATE PUBLICATION` | `Postgres::Publication` |
45
50
  | `collation` | `CREATE COLLATION` | `Postgres::Collation` |
46
51
  | `cast` | `CREATE CAST` | `Postgres::Cast` |
47
52
  | `policy` | `CREATE POLICY` | `Postgres::Policy` |
@@ -189,4 +194,4 @@ chant build src --lexicon sql -o dist/schema.json
189
194
 
190
195
  ## Not covered
191
196
 
192
- `ALTER` (other than a table's row-level security), role memberships, rules, operators, extended statistics, foreign data objects, publications and subscriptions, text search objects and base types are not declared with the tags. An aggregate is identified by its argument types; a cast is named `(source AS target)` and an event trigger lives in no schema. A definition change to a composite or range type, collation, cast or event trigger is a drop and a create, refused while something depends on it. Planning a schema change (`chant sql diff`, `chant sql plan`, the lock classes) is the `chant-sql-postgres-plan` skill. Apply and the expand-and-contract migration Op have their own skills, which arrive with the code they describe.
197
+ `ALTER` (other than a table's row-level security), role memberships, rules, operator classes and families, foreign tables, user mappings, subscriptions, text search objects and base types are not declared with the tags. Import names an object of such a kind and stops; `docs/pages/object-coverage.mdx` lists every `CREATE` command and why. An aggregate is identified by its argument types; a cast is named `(source AS target)` and an event trigger lives in no schema. A definition change to a composite or range type, collation, cast or event trigger is a drop and a create, refused while something depends on it. Planning a schema change (`chant sql diff`, `chant sql plan`, the lock classes) is the `chant-sql-postgres-plan` skill. Apply and the expand-and-contract migration Op have their own skills, which arrive with the code they describe.
@@ -114,6 +114,7 @@ When chant interprets a composite's factory at build time, each field records th
114
114
  | SQLCH002 | lint | a `Nullable` column in the sort key or primary key, which ClickHouse refuses without `allow_nullable_key` |
115
115
  | SQLCH003 | lint | `${events.kind}` where `${events.columns.kind}` was meant |
116
116
  | SQLCH101 | build | an engine the pinned ClickHouse server does not have |
117
+ | SQLCH128 | build | a literal credential in an integration engine's or table function's arguments; name a named collection instead |
117
118
 
118
119
  ## Commands
119
120
 
@@ -126,6 +127,6 @@ chant build src --lexicon sql -o dist/schema.json
126
127
 
127
128
  ## Not covered
128
129
 
129
- `CREATE TABLE ... AS` and `CREATE TABLE ... AS SELECT` are not declared with the tags. Dictionaries, functions, named collections and access control are not modelled. A second dialect is not supported.
130
+ `CREATE TABLE ... AS` and `CREATE TABLE ... AS SELECT` are not declared with the tags. A named collection is never declared: name one in an integration engine, a table function or a dictionary `SOURCE` (`ENGINE = S3(lake_creds, format = 'Parquet')`), and `chant sql plan` checks that the server has it. A second dialect is not supported.
130
131
 
131
132
  Note that `chant migrate` does not run schema migrations. Schema changes go through `chant sql plan`, apply and the rebuild Op, described in the `chant-sql-plan` and `chant-sql-rebuild` skills.
@@ -18,6 +18,12 @@ import { basename, dirname, join, relative, resolve } from "node:path";
18
18
  import type { CommandGroupContext } from "@intentius/chant/cli/command-group";
19
19
  import { renderSqlTypes, SQL_TYPES_FILE } from "./sql-types";
20
20
 
21
+ export interface ProjectLocation {
22
+ config: Awaited<ReturnType<typeof import("@intentius/chant/config").loadChantConfigUpward>>["config"];
23
+ projectDir: string;
24
+ sourceDir: string;
25
+ }
26
+
21
27
  const USAGE = "usage: chant sql types [--output <path>] [--check]";
22
28
 
23
29
  /** The `--output` value, or an error message. */
@@ -43,37 +49,53 @@ export function shadowingSource(path: string): string | undefined {
43
49
  return existsSync(source) ? source : undefined;
44
50
  }
45
51
 
46
- export async function runTypes(ctx: CommandGroupContext): Promise<number> {
47
- const args = parseArgs(ctx.rawArgs);
48
- if ("error" in args) {
49
- console.error(`${args.error}\n${USAGE}`);
50
- return 1;
51
- }
52
- const cwd = process.cwd();
52
+ /** The project the working directory belongs to: its config, its root and its source directory. */
53
+ export async function loadProject(cwd: string): Promise<ProjectLocation> {
53
54
  const { loadChantConfigUpward } = await import("@intentius/chant/config");
54
55
  const { config, configPath } = await loadChantConfigUpward(cwd);
55
56
  const projectDir = configPath ? dirname(configPath) : cwd;
56
- const sourceDir = resolve(projectDir, config.sourceDir ?? ".");
57
- const output = args.output !== undefined ? resolve(cwd, args.output) : join(sourceDir, SQL_TYPES_FILE);
58
- const shadow = shadowingSource(output);
59
- if (shadow) {
60
- console.error(`chant sql types: ${relative(cwd, shadow) || basename(shadow)} sits beside ${relative(cwd, output) || basename(output)}, and tsc reads it instead of the declarations; rename one`);
61
- return 1;
62
- }
57
+ return { config, projectDir, sourceDir: resolve(projectDir, config.sourceDir ?? ".") };
58
+ }
63
59
 
60
+ /**
61
+ * The fold build's entities: what `chant build` declares, `.sql` files and
62
+ * composite members included. Prints the build's errors and returns
63
+ * undefined when it fails.
64
+ */
65
+ export async function buildFolded(project: ProjectLocation, command: string, consequence: string) {
66
+ const { config, projectDir, sourceDir } = project;
64
67
  const { build } = await import("@intentius/chant/build");
65
68
  const { resolveBuildModes, resolveProjectBuildOptions } = await import("@intentius/chant/cli/build-options");
66
69
  const { loadPlugins, resolveProjectLexicons } = await import("@intentius/chant/cli/plugins");
67
70
  const { sqlSerializer } = await import("./serializer");
68
71
  const plugins = await loadPlugins(await resolveProjectLexicons(projectDir, { lexicon: "sql" }));
69
- // The fold build's entities: what `chant build` declares, `.sql` files and composite members included.
70
72
  const options = resolveProjectBuildOptions({ config, configDir: projectDir, plugins, modes: resolveBuildModes(config, { fold: true }) });
71
73
  const result = await build(sourceDir, [sqlSerializer], undefined, options);
72
74
  if (result.errors.length > 0) {
73
75
  for (const e of result.errors) console.error(e instanceof Error ? e.message : String(e));
74
- console.error("chant sql types: the build failed, so nothing was written");
76
+ console.error(`${command}: the build failed, so ${consequence}`);
77
+ return undefined;
78
+ }
79
+ return result;
80
+ }
81
+
82
+ export async function runTypes(ctx: CommandGroupContext): Promise<number> {
83
+ const args = parseArgs(ctx.rawArgs);
84
+ if ("error" in args) {
85
+ console.error(`${args.error}\n${USAGE}`);
75
86
  return 1;
76
87
  }
88
+ const cwd = process.cwd();
89
+ const { config, projectDir, sourceDir } = await loadProject(cwd);
90
+ const output = args.output !== undefined ? resolve(cwd, args.output) : join(sourceDir, SQL_TYPES_FILE);
91
+ const shadow = shadowingSource(output);
92
+ if (shadow) {
93
+ console.error(`chant sql types: ${relative(cwd, shadow) || basename(shadow)} sits beside ${relative(cwd, output) || basename(output)}, and tsc reads it instead of the declarations; rename one`);
94
+ return 1;
95
+ }
96
+
97
+ const result = await buildFolded({ config, projectDir, sourceDir }, "chant sql types", "nothing was written");
98
+ if (!result) return 1;
77
99
  const text = renderSqlTypes(result.entities, { projectDir, config: config as unknown as Record<string, unknown> });
78
100
  const shown = relative(cwd, output);
79
101
  const named = shown && !shown.startsWith("..") ? shown : output;
package/src/sql-types.ts CHANGED
@@ -133,7 +133,7 @@ function originOf(entity: Declarable, exportName: string, projectDir: string): s
133
133
  }
134
134
 
135
135
  /** The names a relation's `.columns` record holds at run time, in order. */
136
- const runtimeColumns = (entity: Declarable | undefined): string[] => Object.keys((entity as { columns?: object } | undefined)?.columns ?? {});
136
+ export const runtimeColumns = (entity: Declarable | undefined): string[] => Object.keys((entity as { columns?: object } | undefined)?.columns ?? {});
137
137
 
138
138
  /** Whether `expr` is the column `column` alone, bare or qualified (`o.user_id`). */
139
139
  function isBareColumn(expr: string, quoted: string): boolean {
@@ -148,7 +148,7 @@ interface LineageJson {
148
148
  }
149
149
 
150
150
  /** A view's column that passes one column through: that column's export and name. */
151
- function passthrough(o: OutputObject, name: string, quote: (c: string) => string): { export: string; column: string } | undefined {
151
+ export function passthrough(o: OutputObject, name: string, quote: (c: string) => string): { export: string; column: string } | undefined {
152
152
  const edge = ((o.lineage as LineageJson[] | undefined) ?? []).find((e) => e.output === name);
153
153
  const ref = edge?.from.length === 1 ? edge.from[0] : undefined;
154
154
  if (!edge || !ref) return undefined;
@@ -177,7 +177,7 @@ function spell(canonical: string, t: ParsedType): string {
177
177
  const arrayOf = (ts: string): string => `${/[|&]/.test(ts) && !/^\{.*\}$/s.test(ts) ? `(${ts})` : ts}[]`;
178
178
 
179
179
  /** A Postgres type's spelling, row type, and whether the type itself forbids NULL (a serial, a NOT NULL domain). */
180
- function pgType(p: PgProject, text: string | undefined, depth = 0): { sqlType: string; ts: string; notNull: boolean } {
180
+ export function pgType(p: PgProject, text: string | undefined, depth = 0): { sqlType: string; ts: string; notNull: boolean } {
181
181
  const t = parseType(text);
182
182
  if (!t || depth > 5) return { sqlType: text?.trim() || "unknown", ts: "unknown", notNull: false };
183
183
  const r = resolveType(p, t, { serial: true });
@@ -320,7 +320,7 @@ function renderEntry(e: RelationEntry, lines: string[]): void {
320
320
  }
321
321
 
322
322
  /** The dialect a build's entities declare, else the config's, else ClickHouse. */
323
- function dialectOf(entities: ReadonlyMap<string, Declarable>, config: Record<string, unknown>): SqlDialect {
323
+ export function dialectOf(entities: ReadonlyMap<string, Declarable>, config: Record<string, unknown>): SqlDialect {
324
324
  try {
325
325
  return filesDialect(entities, config);
326
326
  } catch {
@@ -328,6 +328,12 @@ function dialectOf(entities: ReadonlyMap<string, Declarable>, config: Record<str
328
328
  }
329
329
  }
330
330
 
331
+ /** The build's sql output as `chant build` writes it: the objects, and the Postgres major they were built for. */
332
+ export function serializedObjects(entities: ReadonlyMap<string, Declarable>, config: Record<string, unknown>): { objects: OutputObject[]; postgresMajor?: number } {
333
+ const out = sqlSerializer.serialize(new Map(entities), undefined, { config }) as SerializerResult | string;
334
+ return JSON.parse(typeof out === "string" ? out : out.primary) as { objects: OutputObject[]; postgresMajor?: number };
335
+ }
336
+
331
337
  /**
332
338
  * The `sql-types.d.ts` for a build's entities: one module that augments the
333
339
  * dialect's `SqlSchema` with each table, view and dictionary, in key order.
@@ -340,8 +346,7 @@ export function renderSqlTypes(entities: ReadonlyMap<string, Declarable>, opts:
340
346
  const sqlEntities = new Map([...entities].filter(([, e]) => isPostgresObject(e) || isClickHouseObject(e)));
341
347
  let entries: RelationEntry[] = [];
342
348
  if (sqlEntities.size > 0) {
343
- const out = sqlSerializer.serialize(new Map(entities), undefined, { config }) as SerializerResult | string;
344
- const doc = JSON.parse(typeof out === "string" ? out : out.primary) as { objects: OutputObject[]; postgresMajor?: number };
349
+ const doc = serializedObjects(entities, config);
345
350
  entries = dialect === "postgres" ? postgresEntries(doc.objects, doc.postgresMajor ?? PINNED_MAJOR, entities, projectDir) : clickhouseEntries(doc.objects, entities, projectDir);
346
351
  }
347
352
  const byKey = new Map<string, RelationEntry>();