@intentius/chant-lexicon-sql 0.124.46 → 0.124.49

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 (402) 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/statements.d.ts +29 -0
  37. package/dist/clickhouse/plan/statements.d.ts.map +1 -0
  38. package/dist/clickhouse/plan/statements.js +334 -0
  39. package/dist/clickhouse/plan/stream.d.ts +40 -0
  40. package/dist/clickhouse/plan/stream.d.ts.map +1 -0
  41. package/dist/clickhouse/plan/stream.js +107 -0
  42. package/dist/clickhouse/rebuild/observe.d.ts +3 -1
  43. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
  44. package/dist/clickhouse/rebuild/observe.js +14 -1
  45. package/dist/clickhouse/review-catalog.d.ts +213 -0
  46. package/dist/clickhouse/review-catalog.d.ts.map +1 -0
  47. package/dist/clickhouse/review-catalog.js +330 -0
  48. package/dist/codegen/classifier-page.d.ts.map +1 -1
  49. package/dist/codegen/classifier-page.js +44 -4
  50. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  51. package/dist/codegen/clickhouse-module.js +3 -0
  52. package/dist/codegen/coverage-page.d.ts.map +1 -1
  53. package/dist/codegen/coverage-page.js +5 -3
  54. package/dist/codegen/docs.js +1 -1
  55. package/dist/codegen/postgres-classifier-page.js +2 -2
  56. package/dist/codegen/registry.d.ts.map +1 -1
  57. package/dist/codegen/registry.js +10 -0
  58. package/dist/composites/catalog.d.ts.map +1 -1
  59. package/dist/composites/catalog.js +100 -0
  60. package/dist/composites/clickhouse.d.ts +2 -0
  61. package/dist/composites/clickhouse.d.ts.map +1 -1
  62. package/dist/composites/clickhouse.js +2 -0
  63. package/dist/composites/otel-tables.d.ts +100 -0
  64. package/dist/composites/otel-tables.d.ts.map +1 -0
  65. package/dist/composites/otel-tables.js +282 -0
  66. package/dist/composites/stream-ingest.d.ts +63 -0
  67. package/dist/composites/stream-ingest.d.ts.map +1 -0
  68. package/dist/composites/stream-ingest.js +39 -0
  69. package/dist/core/statement-class.d.ts +55 -0
  70. package/dist/core/statement-class.d.ts.map +1 -0
  71. package/dist/core/statement-class.js +43 -0
  72. package/dist/coverage/clickhouse.json +1 -1
  73. package/dist/coverage/postgres.json +12 -12
  74. package/dist/coverage.d.ts +16 -6
  75. package/dist/coverage.d.ts.map +1 -1
  76. package/dist/coverage.js +10 -5
  77. package/dist/drizzle-command.d.ts +17 -0
  78. package/dist/drizzle-command.d.ts.map +1 -0
  79. package/dist/drizzle-command.js +91 -0
  80. package/dist/drizzle.d.ts +70 -0
  81. package/dist/drizzle.d.ts.map +1 -0
  82. package/dist/drizzle.js +689 -0
  83. package/dist/files/clickhouse.d.ts.map +1 -1
  84. package/dist/files/clickhouse.js +8 -1
  85. package/dist/files/common.d.ts +13 -0
  86. package/dist/files/common.d.ts.map +1 -1
  87. package/dist/files/common.js +5 -0
  88. package/dist/files/postgres.d.ts.map +1 -1
  89. package/dist/files/postgres.js +35 -3
  90. package/dist/generated/clickhouse.d.ts +2 -0
  91. package/dist/generated/clickhouse.d.ts.map +1 -1
  92. package/dist/generated/clickhouse.js +2 -0
  93. package/dist/import-parser.d.ts +21 -1
  94. package/dist/import-parser.d.ts.map +1 -1
  95. package/dist/import-parser.js +79 -5
  96. package/dist/index.d.ts +1 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +1 -1
  99. package/dist/integrity.json +7 -6
  100. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  101. package/dist/lint/post-synth/audit-entries.js +1 -0
  102. package/dist/lint/post-synth/index.d.ts.map +1 -1
  103. package/dist/lint/post-synth/index.js +2 -0
  104. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  105. package/dist/lint/post-synth/postgres-names.js +3 -0
  106. package/dist/lint/post-synth/sqlch128.d.ts +18 -0
  107. package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
  108. package/dist/lint/post-synth/sqlch128.js +222 -0
  109. package/dist/lint/rules/postgres-templates.js +2 -2
  110. package/dist/lsp/postgres-template.d.ts.map +1 -1
  111. package/dist/lsp/postgres-template.js +21 -0
  112. package/dist/manifest.json +1 -1
  113. package/dist/mcp/postgres.js +1 -1
  114. package/dist/meta.json +50 -0
  115. package/dist/migrate/from-postgres/detect.d.ts +4 -0
  116. package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
  117. package/dist/migrate/from-postgres/detect.js +7 -0
  118. package/dist/migrate/from-postgres/emit.d.ts +26 -0
  119. package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
  120. package/dist/migrate/from-postgres/emit.js +86 -0
  121. package/dist/migrate/from-postgres/index.d.ts +51 -0
  122. package/dist/migrate/from-postgres/index.d.ts.map +1 -0
  123. package/dist/migrate/from-postgres/index.js +115 -0
  124. package/dist/migrate/from-postgres/translate.d.ts +73 -0
  125. package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
  126. package/dist/migrate/from-postgres/translate.js +261 -0
  127. package/dist/migrate/from-postgres/types.d.ts +51 -0
  128. package/dist/migrate/from-postgres/types.d.ts.map +1 -0
  129. package/dist/migrate/from-postgres/types.js +211 -0
  130. package/dist/migration-classify.d.ts +23 -0
  131. package/dist/migration-classify.d.ts.map +1 -0
  132. package/dist/migration-classify.js +44 -0
  133. package/dist/migration-replay.d.ts +73 -0
  134. package/dist/migration-replay.d.ts.map +1 -0
  135. package/dist/migration-replay.js +294 -0
  136. package/dist/okf/index.md +11 -0
  137. package/dist/okf/rules/SQLCH128.md +11 -0
  138. package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
  139. package/dist/okf/types/PostgresForeignServer.md +9 -0
  140. package/dist/okf/types/PostgresForeignTable.md +9 -0
  141. package/dist/okf/types/PostgresOperator.md +9 -0
  142. package/dist/okf/types/PostgresOperatorClass.md +9 -0
  143. package/dist/okf/types/PostgresOperatorFamily.md +9 -0
  144. package/dist/okf/types/PostgresPublication.md +9 -0
  145. package/dist/okf/types/PostgresStatistics.md +9 -0
  146. package/dist/okf/types/PostgresTextSearchConfiguration.md +9 -0
  147. package/dist/okf/types/PostgresTextSearchDictionary.md +9 -0
  148. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  149. package/dist/op/activities/postgres-migration.js +4 -0
  150. package/dist/op/activity-contracts.d.ts +20 -0
  151. package/dist/op/activity-contracts.d.ts.map +1 -1
  152. package/dist/op/activity-contracts.js +2 -0
  153. package/dist/plugin.d.ts.map +1 -1
  154. package/dist/plugin.js +49 -2
  155. package/dist/postgres/access/acl.js +1 -1
  156. package/dist/postgres/access/import.js +1 -1
  157. package/dist/postgres/apply/apply.js +13 -1
  158. package/dist/postgres/apply/statements.d.ts.map +1 -1
  159. package/dist/postgres/apply/statements.js +243 -4
  160. package/dist/postgres/constraint-names.d.ts +30 -0
  161. package/dist/postgres/constraint-names.d.ts.map +1 -0
  162. package/dist/postgres/constraint-names.js +72 -0
  163. package/dist/postgres/entities.d.ts +229 -2
  164. package/dist/postgres/entities.d.ts.map +1 -1
  165. package/dist/postgres/entities.js +326 -5
  166. package/dist/postgres/entity-types.d.ts +10 -0
  167. package/dist/postgres/entity-types.d.ts.map +1 -1
  168. package/dist/postgres/entity-types.js +10 -0
  169. package/dist/postgres/import/generator.d.ts.map +1 -1
  170. package/dist/postgres/import/generator.js +65 -2
  171. package/dist/postgres/import/live-export.d.ts.map +1 -1
  172. package/dist/postgres/import/live-export.js +2 -0
  173. package/dist/postgres/live/catalog-kinds.d.ts +20 -0
  174. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
  175. package/dist/postgres/live/catalog-kinds.js +452 -0
  176. package/dist/postgres/live/catalog.d.ts +2 -0
  177. package/dist/postgres/live/catalog.d.ts.map +1 -1
  178. package/dist/postgres/live/catalog.js +3 -1
  179. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  180. package/dist/postgres/live/describe-resources.js +12 -2
  181. package/dist/postgres/migrate/backfill.d.ts +2 -0
  182. package/dist/postgres/migrate/backfill.d.ts.map +1 -1
  183. package/dist/postgres/migrate/backfill.js +7 -1
  184. package/dist/postgres/migrate/carry.d.ts +45 -1
  185. package/dist/postgres/migrate/carry.d.ts.map +1 -1
  186. package/dist/postgres/migrate/carry.js +72 -8
  187. package/dist/postgres/migrate/observe.d.ts +28 -4
  188. package/dist/postgres/migrate/observe.d.ts.map +1 -1
  189. package/dist/postgres/migrate/observe.js +45 -13
  190. package/dist/postgres/migrate/op.d.ts +26 -1
  191. package/dist/postgres/migrate/op.d.ts.map +1 -1
  192. package/dist/postgres/migrate/op.js +3 -1
  193. package/dist/postgres/migrate/replication.d.ts +42 -0
  194. package/dist/postgres/migrate/replication.d.ts.map +1 -1
  195. package/dist/postgres/migrate/replication.js +53 -0
  196. package/dist/postgres/migrate/steps.d.ts +35 -2
  197. package/dist/postgres/migrate/steps.d.ts.map +1 -1
  198. package/dist/postgres/migrate/steps.js +158 -12
  199. package/dist/postgres/parser.d.ts +144 -1
  200. package/dist/postgres/parser.d.ts.map +1 -1
  201. package/dist/postgres/parser.js +370 -4
  202. package/dist/postgres/plan/deep.d.ts.map +1 -1
  203. package/dist/postgres/plan/deep.js +43 -4
  204. package/dist/postgres/plan/diff.d.ts.map +1 -1
  205. package/dist/postgres/plan/diff.js +154 -1
  206. package/dist/postgres/plan/normalize.d.ts +28 -2
  207. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  208. package/dist/postgres/plan/normalize.js +260 -1
  209. package/dist/postgres/plan/rules.d.ts +4 -0
  210. package/dist/postgres/plan/rules.d.ts.map +1 -1
  211. package/dist/postgres/plan/rules.js +6 -2
  212. package/dist/postgres/plan/schema.js +1 -1
  213. package/dist/postgres/plan/statements.d.ts +26 -0
  214. package/dist/postgres/plan/statements.d.ts.map +1 -0
  215. package/dist/postgres/plan/statements.js +364 -0
  216. package/dist/postgres/secret-options.d.ts +10 -0
  217. package/dist/postgres/secret-options.d.ts.map +1 -0
  218. package/dist/postgres/secret-options.js +9 -0
  219. package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
  220. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  221. package/dist/postgres/testing/roundtrip-schema.js +24 -1
  222. package/dist/postgres.d.ts +1 -1
  223. package/dist/postgres.d.ts.map +1 -1
  224. package/dist/postgres.js +1 -1
  225. package/dist/rules/audit-entries.ts +1 -0
  226. package/dist/rules/postgres-names.ts +2 -0
  227. package/dist/rules/postgres-templates.ts +2 -2
  228. package/dist/rules/sqlch128.ts +230 -0
  229. package/dist/serializer.d.ts +5 -0
  230. package/dist/serializer.d.ts.map +1 -1
  231. package/dist/serializer.js +13 -2
  232. package/dist/sql-types-command.d.ts +13 -0
  233. package/dist/sql-types-command.d.ts.map +1 -1
  234. package/dist/sql-types-command.js +31 -16
  235. package/dist/sql-types.d.ts +22 -0
  236. package/dist/sql-types.d.ts.map +1 -1
  237. package/dist/sql-types.js +10 -6
  238. package/dist/sqlserver/lexer.d.ts +37 -0
  239. package/dist/sqlserver/lexer.d.ts.map +1 -0
  240. package/dist/sqlserver/lexer.js +254 -0
  241. package/dist/sqlserver/parse.d.ts +185 -0
  242. package/dist/sqlserver/parse.d.ts.map +1 -0
  243. package/dist/sqlserver/parse.js +767 -0
  244. package/dist/sqlserver/translate.d.ts +34 -0
  245. package/dist/sqlserver/translate.d.ts.map +1 -0
  246. package/dist/sqlserver/translate.js +495 -0
  247. package/dist/sqlserver/types.d.ts +44 -0
  248. package/dist/sqlserver/types.d.ts.map +1 -0
  249. package/dist/sqlserver/types.js +133 -0
  250. package/dist/sqlserver/validate.d.ts +31 -0
  251. package/dist/sqlserver/validate.d.ts.map +1 -0
  252. package/dist/sqlserver/validate.js +67 -0
  253. package/dist/validate.d.ts.map +1 -1
  254. package/dist/validate.js +10 -0
  255. package/package.json +2 -2
  256. package/src/change-set.ts +2 -1
  257. package/src/clickhouse/apply/statements.ts +25 -0
  258. package/src/clickhouse/import/live-export.ts +20 -5
  259. package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
  260. package/src/clickhouse/live/named-collections.ts +61 -0
  261. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
  262. package/src/clickhouse/live/undeclarable.ts +20 -4
  263. package/src/clickhouse/named-collections.test.ts +187 -0
  264. package/src/clickhouse/named-collections.ts +224 -0
  265. package/src/clickhouse/parser.ts +2 -1
  266. package/src/clickhouse/plan/commands.ts +17 -2
  267. package/src/clickhouse/plan/diff.ts +57 -1
  268. package/src/clickhouse/plan/disruption.ts +5 -0
  269. package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
  270. package/src/clickhouse/plan/report.ts +6 -1
  271. package/src/clickhouse/plan/rules.ts +14 -4
  272. package/src/clickhouse/plan/statements.test.ts +61 -0
  273. package/src/clickhouse/plan/statements.ts +306 -0
  274. package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
  275. package/src/clickhouse/plan/stream.test.ts +237 -0
  276. package/src/clickhouse/plan/stream.ts +111 -0
  277. package/src/clickhouse/rebuild/observe.ts +15 -1
  278. package/src/clickhouse/review-catalog.test.ts +181 -0
  279. package/src/clickhouse/review-catalog.ts +489 -0
  280. package/src/codegen/classifier-page.ts +44 -4
  281. package/src/codegen/clickhouse-module.ts +3 -0
  282. package/src/codegen/coverage-page.ts +5 -3
  283. package/src/codegen/docs.ts +1 -1
  284. package/src/codegen/postgres-classifier-page.ts +2 -2
  285. package/src/codegen/registry.ts +10 -0
  286. package/src/composites/catalog.test.ts +6 -1
  287. package/src/composites/catalog.ts +100 -0
  288. package/src/composites/clickhouse.ts +2 -0
  289. package/src/composites/composites.test.ts +1 -1
  290. package/src/composites/otel-tables.test.ts +118 -0
  291. package/src/composites/otel-tables.ts +339 -0
  292. package/src/composites/stream-ingest.test.ts +114 -0
  293. package/src/composites/stream-ingest.ts +82 -0
  294. package/src/core/statement-class.ts +88 -0
  295. package/src/core/undeclarable.test.ts +6 -5
  296. package/src/coverage/clickhouse.json +1 -1
  297. package/src/coverage/postgres.json +12 -12
  298. package/src/coverage.test.ts +27 -2
  299. package/src/coverage.ts +18 -7
  300. package/src/drizzle-command.ts +94 -0
  301. package/src/drizzle-kit.e2e.test.ts +89 -0
  302. package/src/drizzle-types.e2e.test.ts +67 -0
  303. package/src/drizzle.test.ts +358 -0
  304. package/src/drizzle.ts +742 -0
  305. package/src/files/clickhouse.ts +8 -1
  306. package/src/files/common.ts +14 -1
  307. package/src/files/postgres.ts +34 -3
  308. package/src/generated/clickhouse.ts +3 -0
  309. package/src/generated/lexicon-sql.json +50 -0
  310. package/src/import-parser.sqlserver.test.ts +92 -0
  311. package/src/import-parser.ts +79 -5
  312. package/src/index.ts +1 -1
  313. package/src/lint/post-synth/audit-entries.ts +1 -0
  314. package/src/lint/post-synth/index.ts +2 -0
  315. package/src/lint/post-synth/postgres-checks.test.ts +8 -1
  316. package/src/lint/post-synth/postgres-names.ts +2 -0
  317. package/src/lint/post-synth/sqlch128.test.ts +107 -0
  318. package/src/lint/post-synth/sqlch128.ts +230 -0
  319. package/src/lint/rules/postgres-templates.ts +2 -2
  320. package/src/lsp/postgres-template.ts +20 -0
  321. package/src/mcp/postgres.ts +1 -1
  322. package/src/migrate/from-postgres/cli.test.ts +165 -0
  323. package/src/migrate/from-postgres/detect.ts +7 -0
  324. package/src/migrate/from-postgres/emit.ts +96 -0
  325. package/src/migrate/from-postgres/example.test.ts +205 -0
  326. package/src/migrate/from-postgres/index.ts +122 -0
  327. package/src/migrate/from-postgres/translate.test.ts +242 -0
  328. package/src/migrate/from-postgres/translate.ts +359 -0
  329. package/src/migrate/from-postgres/types.test.ts +116 -0
  330. package/src/migrate/from-postgres/types.ts +236 -0
  331. package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
  332. package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
  333. package/src/migration-classify.test.ts +150 -0
  334. package/src/migration-classify.ts +45 -0
  335. package/src/migration-replay.e2e.test.ts +155 -0
  336. package/src/migration-replay.test.ts +173 -0
  337. package/src/migration-replay.ts +289 -0
  338. package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
  339. package/src/op/activities/postgres-migration.ts +4 -0
  340. package/src/op/activity-contracts.ts +2 -0
  341. package/src/op/watch-pipeline.test.ts +8 -1
  342. package/src/plugin.ts +51 -2
  343. package/src/postgres/access/acl.ts +1 -1
  344. package/src/postgres/access/import.ts +1 -1
  345. package/src/postgres/apply/apply.ts +14 -1
  346. package/src/postgres/apply/statements.ts +225 -4
  347. package/src/postgres/constraint-names.ts +71 -0
  348. package/src/postgres/database-kinds.test.ts +865 -0
  349. package/src/postgres/entities.ts +541 -5
  350. package/src/postgres/entity-types.ts +10 -0
  351. package/src/postgres/import/generator.ts +54 -2
  352. package/src/postgres/import/live-export.ts +1 -0
  353. package/src/postgres/live/catalog-kinds.ts +494 -0
  354. package/src/postgres/live/catalog.ts +6 -1
  355. package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
  356. package/src/postgres/live/describe-resources.ts +13 -2
  357. package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
  358. package/src/postgres/migrate/backfill.ts +13 -1
  359. package/src/postgres/migrate/carry.ts +103 -8
  360. package/src/postgres/migrate/followups.e2e.test.ts +218 -0
  361. package/src/postgres/migrate/followups.test.ts +199 -0
  362. package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
  363. package/src/postgres/migrate/observe.ts +56 -15
  364. package/src/postgres/migrate/op.ts +28 -1
  365. package/src/postgres/migrate/partition.e2e.test.ts +129 -0
  366. package/src/postgres/migrate/partition.test.ts +206 -0
  367. package/src/postgres/migrate/replication.ts +66 -0
  368. package/src/postgres/migrate/steps.ts +157 -12
  369. package/src/postgres/migrate/subscriber.e2e.test.ts +141 -0
  370. package/src/postgres/migrate/subscriber.test.ts +59 -0
  371. package/src/postgres/parser.ts +455 -4
  372. package/src/postgres/plan/deep.ts +42 -4
  373. package/src/postgres/plan/diff.ts +137 -1
  374. package/src/postgres/plan/normalize.ts +277 -1
  375. package/src/postgres/plan/rules.ts +32 -4
  376. package/src/postgres/plan/schema.ts +1 -1
  377. package/src/postgres/plan/statements.test.ts +78 -0
  378. package/src/postgres/plan/statements.ts +299 -0
  379. package/src/postgres/secret-options.ts +10 -0
  380. package/src/postgres/testing/roundtrip-schema.ts +24 -1
  381. package/src/postgres.ts +33 -0
  382. package/src/serializer.ts +13 -2
  383. package/src/skills/chant-sql-postgres-migration.md +4 -3
  384. package/src/skills/chant-sql-postgres.md +11 -1
  385. package/src/skills/chant-sql.md +2 -1
  386. package/src/sql-files.test.ts +33 -0
  387. package/src/sql-types-command.ts +38 -16
  388. package/src/sql-types.ts +11 -6
  389. package/src/sqlserver/fixture.test.ts +186 -0
  390. package/src/sqlserver/import-cli.test.ts +61 -0
  391. package/src/sqlserver/lexer.ts +260 -0
  392. package/src/sqlserver/parse.ts +865 -0
  393. package/src/sqlserver/translate.test.ts +396 -0
  394. package/src/sqlserver/translate.ts +558 -0
  395. package/src/sqlserver/types.test.ts +102 -0
  396. package/src/sqlserver/types.ts +154 -0
  397. package/src/sqlserver/validate.e2e.test.ts +32 -0
  398. package/src/sqlserver/validate.test.ts +49 -0
  399. package/src/sqlserver/validate.ts +76 -0
  400. package/src/validate.ts +10 -0
  401. package/src/workspace-kinds.test.ts +11 -0
  402. package/workspace-kinds.json +100 -2
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `StreamIngest` (#4396): the stream table, the view and the MergeTree
3
+ * target it declares, the references between them, and what the lexicon's own
4
+ * checks say about a chain that names a named collection and about one that
5
+ * writes a password.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { expandComposite } from "@intentius/chant/composite";
10
+ import type { Declarable } from "@intentius/chant/declarable";
11
+ import type { SerializerResult } from "@intentius/chant/serializer";
12
+ import { makePostSynthCtx } from "@intentius/chant-test-utils";
13
+ import { StreamIngest } from "./index";
14
+ import { CLICKHOUSE_ENTITY_TYPES, type ClickHouseObject } from "../clickhouse/entities";
15
+ import { sqlSerializer } from "../serializer";
16
+ import { postSynthChecks } from "../lint/post-synth";
17
+ import { diffStatements } from "../migration-statements";
18
+
19
+ const flat = (ddl: string) => ddl.replace(/\s+/g, " ").trim();
20
+ const ddlOf = (entity: unknown) => flat((entity as ClickHouseObject & { props: { ddl: string } }).props.ddl);
21
+
22
+ const ingest = (over: Partial<Parameters<typeof StreamIngest>[0]> = {}) =>
23
+ StreamIngest({
24
+ name: "events",
25
+ columns: "ts DateTime, user_id UInt64, kind LowCardinality(String)",
26
+ engine: "Kafka(kafka_creds)",
27
+ settings: "kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'",
28
+ orderBy: "(kind, user_id, ts)",
29
+ ...over,
30
+ });
31
+
32
+ describe("StreamIngest", () => {
33
+ test("a target, a stream table named after it, and a view from the one into the other", () => {
34
+ const { target, stream, view } = ingest();
35
+ expect(target.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.table);
36
+ expect(stream.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.table);
37
+ expect(view.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.materializedView);
38
+ expect([target.props.name, stream.props.name, view.props.name]).toEqual(["events", "events_stream", "events_mv"]);
39
+ expect(target.props.engine).toEqual({ name: "MergeTree" });
40
+ expect(stream.props.engine).toMatchObject({ name: "Kafka" });
41
+ });
42
+
43
+ test("the DDL: the named collection is the engine's argument, the settings follow, the view selects everything", () => {
44
+ const { stream, view, target } = ingest();
45
+ expect(ddlOf(stream)).toBe(
46
+ "CREATE TABLE events_stream (ts DateTime, user_id UInt64, kind LowCardinality(String)) ENGINE = Kafka(kafka_creds) SETTINGS kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'",
47
+ );
48
+ expect(ddlOf(view)).toBe("CREATE MATERIALIZED VIEW events_mv TO events AS SELECT * FROM events_stream");
49
+ expect(ddlOf(target)).toBe("CREATE TABLE events (ts DateTime, user_id UInt64, kind LowCardinality(String)) ENGINE = MergeTree ORDER BY (kind, user_id, ts)");
50
+ });
51
+
52
+ test("the view writes TO the target and reads the stream table, by reference, so the build orders all three", () => {
53
+ const { target, stream, view } = ingest();
54
+ expect(view.props.to).toBe(target);
55
+ expect(view.props.reads).toEqual([stream]);
56
+ expect(view.dependsOn).toEqual([target, stream]);
57
+ });
58
+
59
+ test("the message columns, the select list, the target engine and the partition key are parameters", () => {
60
+ const { stream, view, target } = ingest({
61
+ streamColumns: "payload String",
62
+ select: "parseDateTimeBestEffort(JSONExtractString(payload, 'ts')) AS ts, JSONExtractUInt(payload, 'user_id') AS user_id, JSONExtractString(payload, 'kind') AS kind",
63
+ targetEngine: "ReplacingMergeTree",
64
+ partitionBy: "toYYYYMM(ts)",
65
+ engine: "S3Queue(lake_creds)",
66
+ settings: "mode = 'unordered', keeper_path = '/chant/events'",
67
+ });
68
+ expect(ddlOf(stream)).toBe("CREATE TABLE events_stream (payload String) ENGINE = S3Queue(lake_creds) SETTINGS mode = 'unordered', keeper_path = '/chant/events'");
69
+ expect(ddlOf(view)).toMatch(/^CREATE MATERIALIZED VIEW events_mv TO events AS SELECT parseDateTimeBestEffort\(/);
70
+ expect(target.props.engine).toEqual({ name: "ReplacingMergeTree" });
71
+ expect(target.props.partitionBy).toBe("toYYYYMM(ts)");
72
+ });
73
+
74
+ test("an engine with no settings declares none", () => {
75
+ expect(ddlOf(ingest({ engine: "Kafka(kafka_creds)", settings: undefined }).stream)).toMatch(/ENGINE = Kafka\(kafka_creds\)$/);
76
+ });
77
+
78
+ function build(props: Parameters<typeof ingest>[0]) {
79
+ const entities = new Map<string, Declarable>();
80
+ for (const [k, v] of expandComposite("events", ingest(props))) entities.set(k, v);
81
+ const out = sqlSerializer.serialize(entities) as SerializerResult;
82
+ return { out, doc: JSON.parse(out.primary) as { applyOrder: string[]; namedCollections?: string[] }, entities };
83
+ }
84
+ const findings = (b: ReturnType<typeof build>) => {
85
+ const ctx = makePostSynthCtx("sql", b.out.primary, b.entities);
86
+ return postSynthChecks.filter((c) => c.id.startsWith("SQLCH") || c.id === "SQL101").flatMap((check) => check.check(ctx));
87
+ };
88
+
89
+ test("the build applies the target and the stream table before the view, records the collection, and the lexicon's checks find nothing", () => {
90
+ const b = build({ columns: "ts DateTime, user_id UInt64, kind LowCardinality(String)", select: "ts, user_id, kind", streamColumns: "ts DateTime, user_id UInt64, kind LowCardinality(String)" });
91
+ const order = b.doc.applyOrder;
92
+ expect(order.indexOf("eventsTarget")).toBeLessThan(order.indexOf("eventsView"));
93
+ expect(order.indexOf("eventsStream")).toBeLessThan(order.indexOf("eventsView"));
94
+ expect(b.doc.namedCollections).toEqual(["kafka_creds"]);
95
+ expect(findings(b)).toEqual([]);
96
+ });
97
+
98
+ test("a view that writes a column the target lacks is SQLCH110, and a password in the engine is SQLCH128", () => {
99
+ const wrong = findings(build({ select: "ts, user_id, kind, extra" }));
100
+ expect(wrong.map((f) => f.checkId)).toContain("SQLCH110");
101
+ const inline = findings(build({ engine: "Kafka('broker:9092', 'events', 'ingest', 'JSONEachRow')", settings: "kafka_sasl_password = 'hunter2'" }));
102
+ expect(inline.map((f) => f.checkId)).toContain("SQLCH128");
103
+ });
104
+
105
+ test("a change to the stream table between two builds is a replacement in the migration's statements, not an Op", () => {
106
+ const settings = (topic: string) => `kafka_topic_list = '${topic}', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'`;
107
+ const doc = diffStatements(build({ settings: settings("events") }).out.primary, build({ settings: settings("events,clicks") }).out.primary);
108
+ expect(doc.refused).toBeFalsy();
109
+ const steps = doc.steps.filter((s) => s.kind === "statement") as { sql: string; rule: string; class: string }[];
110
+ expect(steps.map((s) => s.sql.replace(/\s+/g, " ").split(" ").slice(0, 2).join(" "))).toEqual(["DETACH TABLE", "DROP TABLE", "CREATE TABLE", "ATTACH TABLE"]);
111
+ expect(steps.every((s) => s.rule === "SQLCH225" && s.class === "replace")).toBe(true);
112
+ expect(doc.steps.every((s) => s.kind === "statement")).toBe(true);
113
+ });
114
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `StreamIngest`: the usual ClickHouse ingestion chain, a stream table, a materialized view reading it and a MergeTree target.
3
+ *
4
+ * The stream table (Kafka, S3Queue, AzureQueue, RabbitMQ, NATS or FileLog)
5
+ * stores no rows: it is the consumer. The materialized view pulls each batch
6
+ * through it with `TO` and writes the target, which is an ordinary MergeTree
7
+ * table that can be queried, altered and backfilled on its own. Every column
8
+ * the view's select list writes has to be one the target declares (SQLCH110).
9
+ *
10
+ * The stream engine usually carries credentials. Name a named collection as
11
+ * its first argument (`Kafka(kafka_creds)`, `S3Queue(lake_creds)`) and keep
12
+ * the broker, bucket and keys in the collection on the server: chant never
13
+ * declares or reads one (#4395), and SQLCH128 fails a build that writes a
14
+ * password in the engine's arguments.
15
+ *
16
+ * A change to the stream table is planned as SQLCH225, not as a rebuild: the
17
+ * view is detached, the table replaced and the view attached again. A
18
+ * different `kafka_group_name` or S3Queue `keeper_path` loses the table's
19
+ * position, and the plan says so (#4396).
20
+ */
21
+
22
+ import { Composite } from "@intentius/chant/composite";
23
+ import { table, view, type ClickHouseTable, type ClickHouseView } from "../clickhouse/entities";
24
+ import type { ClickHouseMemberName } from "../core/typed";
25
+ import type { Instance, TypedComposite } from "./typed";
26
+
27
+ export interface StreamIngestProps {
28
+ /** The target table's name. The stream table is `<name>_stream` and the view `<name>_mv`. */
29
+ name: string;
30
+ /** The target's columns, as SQL: `ts DateTime, user_id UInt64, kind LowCardinality(String)`. */
31
+ columns: string;
32
+ /**
33
+ * The stream table's engine, with a named collection as its first argument
34
+ * rather than inline credentials: `Kafka(kafka_creds)`, `S3Queue(lake_creds)`.
35
+ */
36
+ engine: string;
37
+ /**
38
+ * The engine's settings, the body of a `SETTINGS` clause:
39
+ * `kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'`.
40
+ * Settings the named collection already holds are left out.
41
+ */
42
+ settings?: string;
43
+ /** The stream table's columns when they differ from the target's: the message as it arrives. Default: `columns`. */
44
+ streamColumns?: string;
45
+ /** The view's select list over the stream table, each item aliased to a target column. Default: `*`. */
46
+ select?: string;
47
+ /** The target's sort key (a column list or an expression). */
48
+ orderBy: string;
49
+ /** The target's engine (default `MergeTree`). */
50
+ targetEngine?: string;
51
+ /** The target's partition key. Left out by default. */
52
+ partitionBy?: string;
53
+ }
54
+
55
+ /** `Target`, `Stream` and `View` are the members' names, when the call's props give them (chant#3787). */
56
+ export type StreamIngestMembers<Target extends string = string, Stream extends string = string, View extends string = string> = {
57
+ target: ClickHouseTable<Target>;
58
+ stream: ClickHouseTable<Stream>;
59
+ view: ClickHouseView<View>;
60
+ };
61
+
62
+ type StreamIngestCall = <const N extends string>(
63
+ props: StreamIngestProps & { name: N },
64
+ ) => Instance<StreamIngestMembers<ClickHouseMemberName<N>, ClickHouseMemberName<N, "_stream">, ClickHouseMemberName<N, "_mv">>>;
65
+
66
+ /** A stream table, the materialized view that reads it, and the MergeTree target the view writes. */
67
+ export const StreamIngest = Composite<StreamIngestProps, StreamIngestMembers, TypedComposite<StreamIngestProps, StreamIngestMembers, StreamIngestCall>>((props) => {
68
+ const target = table`
69
+ CREATE TABLE ${props.name} (${props.columns})
70
+ ENGINE = ${props.targetEngine ?? "MergeTree"}
71
+ ${props.partitionBy ? `PARTITION BY ${props.partitionBy}` : ""}
72
+ ORDER BY ${props.orderBy}`;
73
+ const stream = table`
74
+ CREATE TABLE ${`${props.name}_stream`} (${props.streamColumns ?? props.columns})
75
+ ENGINE = ${props.engine}
76
+ ${props.settings ? `SETTINGS ${props.settings}` : ""}`;
77
+ const ingest = view`
78
+ CREATE MATERIALIZED VIEW ${`${props.name}_mv`} TO ${target} AS
79
+ SELECT ${props.select ?? "*"}
80
+ FROM ${stream}`;
81
+ return { target, stream, view: ingest };
82
+ }, "StreamIngest");
@@ -0,0 +1,88 @@
1
+ /**
2
+ * What the statement classifiers (`../clickhouse/plan/statements.ts`,
3
+ * `../postgres/plan/statements.ts`) answer, in one shape for both dialects
4
+ * (#4407). They read a migration file's statements offline and give each the
5
+ * class the schema-diff classifier gives the same change, citing its rule.
6
+ * Nothing runs against a server.
7
+ */
8
+
9
+ import type { ClassifierRule } from "./classifier";
10
+
11
+ /** One change a statement makes, under one rule. */
12
+ export interface StatementClassified {
13
+ rule: string;
14
+ /** The rule's class, e.g. `rebuild`, `concurrently`. */
15
+ class: string;
16
+ /** The class as a report names it, e.g. `REBUILD`. */
17
+ label: string;
18
+ title: string;
19
+ /** Postgres: the lock the rule's restriction names, e.g. `SHARE`. */
20
+ lock?: string;
21
+ /** Data is removed and not recoverable. */
22
+ destructive?: boolean;
23
+ note?: string;
24
+ }
25
+
26
+ /**
27
+ * - `classified`: every change has a rule.
28
+ * - `undecided`: the statement alone cannot say which of `candidates` applies
29
+ * (a column type change is a rewrite, or a rebuild when the column is in a
30
+ * key); the answer `classifyDisruption()` calls `unknown`.
31
+ * - `unclassified`: a schema change no rule covers; `note` says what.
32
+ * - `data`: the statement reads or writes rows or the session, not the schema.
33
+ */
34
+ export type StatementOutcome = "classified" | "undecided" | "unclassified" | "data";
35
+
36
+ export interface StatementVerdict {
37
+ /** 1-based position among the file's up statements. */
38
+ index: number;
39
+ /** The statement's first line, cut to 120 characters. */
40
+ sql: string;
41
+ outcome: StatementOutcome;
42
+ /** `classified`: one entry per change in the statement (an `ALTER TABLE` can hold several). */
43
+ changes: StatementClassified[];
44
+ /** `undecided`: the rules that could apply, in the order a reader should weigh them. */
45
+ candidates?: StatementClassified[];
46
+ note?: string;
47
+ }
48
+
49
+ /** A statement's own text cut for a note: the first line, at most 120 characters. */
50
+ export function sqlHead(sql: string): string {
51
+ const line = sql.split(/\r?\n/).find((l) => l.trim() !== "" && !l.trim().startsWith("--")) ?? sql;
52
+ const t = line.trim().replace(/\s+/g, " ");
53
+ return t.length > 120 ? `${t.slice(0, 117)}...` : t;
54
+ }
55
+
56
+ /** The pieces of a classified change from a dialect's rule table. */
57
+ export function classifiedOf<C extends string>(
58
+ rules: Readonly<Record<string, ClassifierRule<C>>>,
59
+ labels: Readonly<Record<C, string>>,
60
+ id: string,
61
+ extra: Partial<StatementClassified> = {},
62
+ ): StatementClassified {
63
+ const r = rules[id];
64
+ if (!r) throw new Error(`no classifier rule ${id}`);
65
+ return { rule: id, class: r.class, label: labels[r.class], title: r.title, ...extra };
66
+ }
67
+
68
+ /** Lock names, longest first so `SHARE UPDATE EXCLUSIVE` is not read as `SHARE`. */
69
+ const LOCK = /\b(ACCESS EXCLUSIVE|SHARE UPDATE EXCLUSIVE|SHARE ROW EXCLUSIVE|ROW EXCLUSIVE|ROW SHARE|EXCLUSIVE|SHARE)\b/;
70
+
71
+ /** The first lock a rule's restriction names, or undefined when it names none. */
72
+ export const lockOf = (restriction: string): string | undefined => LOCK.exec(restriction)?.[1];
73
+
74
+ /**
75
+ * Split a statement's tokens' text into the pieces between top-level commas
76
+ * (an `ALTER TABLE`'s actions). Parentheses and brackets nest.
77
+ */
78
+ export function splitTopLevel(words: readonly string[]): string[][] {
79
+ const out: string[][] = [[]];
80
+ let depth = 0;
81
+ for (const w of words) {
82
+ if (w === "(" || w === "[") depth++;
83
+ else if (w === ")" || w === "]") depth--;
84
+ if (w === "," && depth === 0) out.push([]);
85
+ else out[out.length - 1]!.push(w);
86
+ }
87
+ return out.filter((a) => a.length > 0);
88
+ }
@@ -100,14 +100,15 @@ describe("ClickHouse live import", () => {
100
100
  afterAll(() => server.close());
101
101
  const run = (extra: Record<string, unknown> = {}) => exportClickHouse({ environment: "x", config: {}, env: { CLICKHOUSE_URL: server.url }, ...extra });
102
102
 
103
- test("fails naming them", async () => {
104
- await expect(run()).rejects.toThrow(/ - named collection nc1\n - window view analytics\.wv\n - workload all\n/);
103
+ test("fails naming them; a named collection is a prerequisite, not among them (#4395)", async () => {
104
+ await expect(run()).rejects.toThrow(/:\n - window view analytics\.wv\n - workload all\nWhy/);
105
105
  });
106
106
 
107
107
  test("with skipUndeclarable, warns, and the window view is not imported as a table", async () => {
108
108
  const ir = await run({ skipUndeclarable: true });
109
109
  expect(ir.resources.map((r) => r.properties.name)).toEqual(["analytics", "events"]);
110
- expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(3);
110
+ expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(2);
111
+ expect((ir.warnings ?? []).filter((w) => w.startsWith("named collection nc1 is a prerequisite"))).toHaveLength(1);
111
112
  });
112
113
  });
113
114
 
@@ -134,8 +135,8 @@ CREATE COLLATION app.ci (provider = icu, locale = 'und');`;
134
135
  ]);
135
136
  });
136
137
 
137
- test("ClickHouse: a named collection in a file is refused too; a quota is declared (#3911)", () => {
138
- expect(() => new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE NAMED COLLECTION c AS url = 'x'")).toThrow(/named collection: chant cannot declare it/);
138
+ test("ClickHouse: a named collection in a file is a prerequisite (#4395); a quota is declared (#3911)", () => {
139
+ expect(new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE NAMED COLLECTION c AS url = 'x'").warnings).toEqual([expect.stringMatching(/^named collection c is a prerequisite/)]);
139
140
  expect(new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE QUOTA q1").resources.map((r) => r.type)).toEqual(["ClickHouse::Database", "ClickHouse::Quota"]);
140
141
  });
141
142
  });
@@ -8,7 +8,7 @@
8
8
  { "command": "CREATE HANDLER", "kind": "HTTP handler", "status": "refused", "reason": "an HTTP handler is server-level configuration, not a schema object" },
9
9
  { "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "ClickHouse::Table", "note": "an index is part of its table: SHOW CREATE TABLE prints it, and the table's declaration holds it" },
10
10
  { "command": "CREATE MASKING POLICY", "kind": "masking policy", "status": "refused", "reason": "masking policies are available only in ClickHouse Cloud, not on the pinned server" },
11
- { "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "refused", "reason": "a named collection holds credentials (a password, a secret key), which are the environment's and never in a build; the server hides its values, so a plan could not compare them either" },
11
+ { "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "referenced", "reason": "a named collection holds credentials (a password, a secret key), which are the environment's: chant never declares one or reads its values. An engine, a table function or a dictionary SOURCE names it, a plan checks that it exists, and import lists the server's as prerequisites" },
12
12
  { "command": "CREATE QUOTA", "kind": "quota", "status": "declared", "entity": "ClickHouse::Quota", "note": "import reads only the access objects a build declares" },
13
13
  { "command": "CREATE RESOURCE", "kind": "workload resource", "status": "refused", "reason": "a resource is server-level scheduling (a disk's reads or writes), configured with the server, not a schema object" },
14
14
  { "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "ClickHouse::Role", "note": "import reads only the access objects a build declares" },
@@ -10,32 +10,32 @@
10
10
  { "command": "CREATE DOMAIN", "kind": "domain", "status": "declared", "entity": "Postgres::Domain" },
11
11
  { "command": "CREATE EVENT TRIGGER", "kind": "event trigger", "status": "declared", "entity": "Postgres::EventTrigger", "note": "the database's, not a schema's; creating one takes a superuser" },
12
12
  { "command": "CREATE EXTENSION", "kind": "extension", "status": "declared", "entity": "Postgres::Extension" },
13
- { "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "refused", "reason": "no tag declares a foreign data wrapper yet; one an extension installs comes with the extension" },
14
- { "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "refused", "reason": "no tag declares a foreign table yet" },
13
+ { "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "declared", "entity": "Postgres::ForeignDataWrapper", "note": "the database's, not a schema's; creating one takes a superuser; one an extension installs comes with the extension; an option that holds a credential is refused" },
14
+ { "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "declared", "entity": "Postgres::ForeignTable", "note": "its own tag, not table: another CREATE and ALTER, no key, index or storage clauses; its server cannot be altered; one that is a partition, inherits or has a generated column is not declared; an option that holds a credential is refused" },
15
15
  { "command": "CREATE FUNCTION", "kind": "function", "status": "declared", "entity": "Postgres::Function" },
16
16
  { "command": "CREATE FUNCTION", "form": "RETURN or BEGIN ATOMIC body", "match": "\\b(?:RETURN|BEGIN\\s+ATOMIC)\\b", "kind": "routine with a SQL-standard body", "status": "refused", "reason": "the server stores a SQL-standard body parsed and prints it back rewritten, so a declaration cannot be compared with it; declare the routine with a string body (AS $$ ... $$)" },
17
17
  { "command": "CREATE GROUP", "kind": "group", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE" },
18
18
  { "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "Postgres::Index" },
19
19
  { "command": "CREATE LANGUAGE", "kind": "procedural language", "status": "refused", "reason": "no tag declares a procedural language; an extension installs one, and the extension is declared instead" },
20
20
  { "command": "CREATE MATERIALIZED VIEW", "kind": "materialized view", "status": "declared", "entity": "Postgres::MaterializedView" },
21
- { "command": "CREATE OPERATOR", "kind": "operator", "status": "refused", "reason": "no tag declares an operator yet" },
22
- { "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "refused", "reason": "no tag declares an operator class yet" },
23
- { "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "refused", "reason": "no tag declares an operator family yet" },
21
+ { "command": "CREATE OPERATOR", "kind": "operator", "status": "declared", "entity": "Postgres::Operator", "note": "identified by its name and operand types; RESTRICT and JOIN change in place, and from 17 so does setting a commutator or negator or turning on HASHES or MERGES; anything else is a drop and a create" },
22
+ { "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "declared", "entity": "Postgres::OperatorClass", "note": "identified by its name and index method; a change is a drop and a create, since ALTER OPERATOR CLASS only renames or moves it; its own family, named as the class, comes with it" },
23
+ { "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "declared", "entity": "Postgres::OperatorFamily", "note": "identified by its name and index method; its members come with the classes that name it, and loose members added with ALTER OPERATOR FAMILY are not declared" },
24
24
  { "command": "CREATE POLICY", "kind": "policy", "status": "declared", "entity": "Postgres::Policy", "note": "read where the profile manages access (sql.profiles.<env>.access)" },
25
25
  { "command": "CREATE PROCEDURE", "kind": "procedure", "status": "declared", "entity": "Postgres::Procedure" },
26
- { "command": "CREATE PUBLICATION", "kind": "publication", "status": "refused", "reason": "no tag declares a publication yet" },
26
+ { "command": "CREATE PUBLICATION", "kind": "publication", "status": "declared", "entity": "Postgres::Publication", "note": "the database's, not a schema's; a table of it may carry a column list and row filter from 15" },
27
27
  { "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "Postgres::Role", "note": "import reads only the roles a build declares, since a role is the cluster's" },
28
28
  { "command": "CREATE RULE", "kind": "rule", "status": "refused", "reason": "no tag declares a rewrite rule; a view's own rule is the view's" },
29
29
  { "command": "CREATE SCHEMA", "kind": "schema", "status": "declared", "entity": "Postgres::Schema" },
30
30
  { "command": "CREATE SEQUENCE", "kind": "sequence", "status": "declared", "entity": "Postgres::Sequence" },
31
- { "command": "CREATE SERVER", "kind": "foreign server", "status": "refused", "reason": "no tag declares a foreign server yet" },
32
- { "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "refused", "reason": "no tag declares extended statistics yet" },
33
- { "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "no tag declares a subscription yet" },
31
+ { "command": "CREATE SERVER", "kind": "foreign server", "status": "declared", "entity": "Postgres::ForeignServer", "note": "the database's, not a schema's; its user mappings are refused, so credentials stay out of the declaration" },
32
+ { "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "declared", "entity": "Postgres::Statistics" },
33
+ { "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "a subscription's connection string carries a credential, and creating one opens a replication slot on the publisher, outside this database; the environment makes it" },
34
34
  { "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "Postgres::Table" },
35
35
  { "command": "CREATE TABLE AS", "kind": "table from a query", "status": "declared", "entity": "Postgres::Table", "note": "the server keeps no query, so the table it made reads as a CREATE TABLE" },
36
36
  { "command": "CREATE TABLESPACE", "kind": "tablespace", "status": "out-of-scope", "reason": "a tablespace is the cluster's, a directory on the server; a table's TABLESPACE clause is kept" },
37
- { "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "refused", "reason": "no tag declares a text search configuration yet" },
38
- { "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "refused", "reason": "no tag declares a text search dictionary yet" },
37
+ { "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "declared", "entity": "Postgres::TextSearchConfiguration", "note": "PARSER and its ADD MAPPING statements; COPY is refused, since it copies mappings chant does not read; mappings change in place, the parser by drop and create" },
38
+ { "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "declared", "entity": "Postgres::TextSearchDictionary", "note": "a template and its options; options change in place, the template by drop and create" },
39
39
  { "command": "CREATE TEXT SEARCH PARSER", "kind": "text search parser", "status": "refused", "reason": "no tag declares a text search parser; one is C code an extension installs" },
40
40
  { "command": "CREATE TEXT SEARCH TEMPLATE", "kind": "text search template", "status": "refused", "reason": "no tag declares a text search template; one is C code an extension installs" },
41
41
  { "command": "CREATE TRANSFORM", "kind": "transform", "status": "refused", "reason": "no tag declares a transform; an extension installs one" },
@@ -45,7 +45,7 @@
45
45
  { "command": "CREATE TYPE", "form": "AS RANGE", "match": "\\bAS\\s+RANGE\\b", "kind": "range type", "status": "declared", "entity": "Postgres::RangeType", "note": "its multirange type comes with it (MULTIRANGE_TYPE_NAME)" },
46
46
  { "command": "CREATE TYPE", "form": "INPUT, OUTPUT, or a shell type", "kind": "base type", "status": "refused", "reason": "no tag declares a base type; its input and output functions are C code an extension installs" },
47
47
  { "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE ... LOGIN" },
48
- { "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "no tag declares a user mapping yet" },
48
+ { "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "a user mapping carries the remote user and password, which the environment supplies and a declaration never holds" },
49
49
  { "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "Postgres::View" }
50
50
  ]
51
51
  }
@@ -3,6 +3,8 @@ import { KIND_COVERAGE, PINNED_COMMANDS, kindCoverageProblems, pinnedCommandsCur
3
3
  import { POSTGRES_PROBES } from "./postgres/live/undeclarable";
4
4
  import { CLICKHOUSE_PROBES } from "./clickhouse/live/undeclarable";
5
5
  import { SQL_DIALECTS } from "./dialects";
6
+ import { POSTGRES_ENTITY_TYPES } from "./postgres/entity-types";
7
+ import { CLICKHOUSE_ENTITY_TYPES } from "./clickhouse/entities";
6
8
 
7
9
  describe("the object coverage table (#3911)", () => {
8
10
  test.each(SQL_DIALECTS)("%s: every CREATE command a pinned server has is marked, and nothing else", (dialect) => {
@@ -29,10 +31,32 @@ describe("the object coverage table (#3911)", () => {
29
31
  ]);
30
32
  });
31
33
 
32
- test("every refused kind has the live query that finds it, and every query a refused-or-declared row", () => {
34
+ test("a declared row names an entity type the dialect has, and every entity type but a grant has a declared row", () => {
35
+ // A grant is a GRANT statement, not a CREATE command, so no row is about it.
36
+ const types = {
37
+ postgres: { all: Object.values(POSTGRES_ENTITY_TYPES) as string[], notCreate: ["Postgres::Grant", "Postgres::DefaultPrivileges"] },
38
+ clickhouse: { all: Object.values(CLICKHOUSE_ENTITY_TYPES) as string[], notCreate: ["ClickHouse::Grant"] },
39
+ };
40
+ for (const dialect of SQL_DIALECTS) {
41
+ const entities = KIND_COVERAGE[dialect].filter((r) => r.status === "declared").map((r) => r.entity!);
42
+ for (const e of entities) expect(types[dialect].all, `${dialect} row entity ${e}`).toContain(e);
43
+ for (const e of types[dialect].all.filter((t) => !types[dialect].notCreate.includes(t))) expect(entities, `${dialect} entity type ${e} has no declared row`).toContain(e);
44
+ }
45
+ });
46
+
47
+ test("a kind that is declared, referenced, refused or out of scope is told apart: only a declared row names an entity", () => {
48
+ for (const dialect of SQL_DIALECTS) {
49
+ for (const r of KIND_COVERAGE[dialect]) {
50
+ if (r.status === "declared") expect(r.reason, `${dialect} ${r.kind}`).toBeUndefined();
51
+ else expect(r.entity, `${dialect} ${r.kind}`).toBeUndefined();
52
+ }
53
+ }
54
+ });
55
+
56
+ test("every refused or referenced kind has the live query that finds it, and every query a row", () => {
33
57
  const probes = { postgres: POSTGRES_PROBES, clickhouse: CLICKHOUSE_PROBES };
34
58
  for (const dialect of SQL_DIALECTS) {
35
- const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused").map((r) => r.kind);
59
+ const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused" || r.status === "referenced").map((r) => r.kind);
36
60
  for (const kind of refused) expect(Object.keys(probes[dialect]), `${dialect} ${kind}`).toContain(kind);
37
61
  const kinds = KIND_COVERAGE[dialect].map((r) => r.kind);
38
62
  for (const kind of Object.keys(probes[dialect])) expect(kinds, `${dialect} probe ${kind}`).toContain(kind);
@@ -68,6 +92,7 @@ describe("a statement's kind", () => {
68
92
  ["CREATE SETTINGS PROFILE p SETTINGS max_threads = 1", "settings profile"],
69
93
  ["CREATE OR REPLACE FUNCTION f AS (x) -> x + 1", "SQL function"],
70
94
  ["CREATE QUOTA q FOR INTERVAL 1 hour MAX queries = 10", "quota"],
95
+ ["CREATE NAMED COLLECTION IF NOT EXISTS c AS url = 'x'", "named collection"],
71
96
  ])("clickhouse: %s is a %s", (sql, kind) => {
72
97
  expect(statementKind("clickhouse", sql)?.kind).toBe(kind);
73
98
  });
package/src/coverage.ts CHANGED
@@ -4,8 +4,10 @@
4
4
  * Which kinds of object each dialect declares (#3911): every `CREATE` command
5
5
  * a pinned server has (`./coverage/<dialect>-commands.json`, written by
6
6
  * `scripts/sql-create-commands.ts` from the server's own sources) has a row
7
- * in `./coverage/<dialect>.json` marking it declared, refused with a reason,
8
- * or out of scope. A command with several forms that differ in what chant can
7
+ * in `./coverage/<dialect>.json` marking it declared, referenced, refused
8
+ * with a reason, or out of scope. A referenced kind (a ClickHouse named
9
+ * collection, #4395) is never declared: a declaration names one, a plan
10
+ * checks it exists, and import lists the server's as prerequisites. A command with several forms that differ in what chant can
9
11
  * hold (`CREATE TYPE ... AS ENUM` and `CREATE TYPE ... AS RANGE`) has a row
10
12
  * per form. Import refuses an object of a refused kind by that row's `kind`
11
13
  * (`./core/undeclarable.ts`), and the docs page is rendered from the rows
@@ -31,8 +33,16 @@ import clickhouseCommands from "./coverage/clickhouse-commands.json" with { type
31
33
 
32
34
  // ── Object kinds ───────────────────────────────────────────────────────
33
35
 
34
- /** `declared`: a tag holds it. `refused`: import names it and stops. `out-of-scope`: the environment's, never imported. */
35
- export type KindStatus = "declared" | "refused" | "out-of-scope";
36
+ /**
37
+ * `declared`: a tag holds it. `referenced`: the environment's, never declared
38
+ * and its values never read; a declaration names it, a plan checks it exists,
39
+ * and import lists the server's as prerequisites (#4395). `refused`: import
40
+ * names it and stops. `out-of-scope`: the environment's, never imported.
41
+ */
42
+ export type KindStatus = "declared" | "referenced" | "refused" | "out-of-scope";
43
+
44
+ /** Every status a coverage row may have. */
45
+ export const KIND_STATUSES: readonly KindStatus[] = ["declared", "referenced", "refused", "out-of-scope"];
36
46
 
37
47
  /** One row of a dialect's coverage table: a `CREATE` command, or one form of it. */
38
48
  export interface KindRow {
@@ -51,7 +61,7 @@ export interface KindRow {
51
61
  status: KindStatus;
52
62
  /** The entity type that declares it, when declared. */
53
63
  entity?: string;
54
- /** Why it is refused or out of scope. */
64
+ /** Why it is referenced, refused or out of scope. */
55
65
  reason?: string;
56
66
  /** Anything else a reader should know. */
57
67
  note?: string;
@@ -80,7 +90,8 @@ export function kindRow(dialect: SqlDialect, kind: string): KindRow {
80
90
  * What is wrong with a dialect's coverage table, against its pinned servers'
81
91
  * commands: a command a pinned server has and no row covers, a row for a
82
92
  * command no pinned server has, a kind named twice, a declared row with no
83
- * entity, a refused or out-of-scope row with no reason. Empty when complete.
93
+ * entity, a referenced, refused or out-of-scope row with no reason. Empty
94
+ * when complete.
84
95
  */
85
96
  export function kindCoverageProblems(dialect: SqlDialect, opts: { pins?: ReadonlyArray<{ version: string; commands: readonly string[] }>; rows?: readonly KindRow[] } = {}): string[] {
86
97
  const rows = opts.rows ?? KIND_COVERAGE[dialect];
@@ -96,7 +107,7 @@ export function kindCoverageProblems(dialect: SqlDialect, opts: { pins?: Readonl
96
107
  for (const r of rows) {
97
108
  if (seen.has(r.kind)) problems.push(`the kind "${r.kind}" has two rows`);
98
109
  seen.add(r.kind);
99
- if (!["declared", "refused", "out-of-scope"].includes(r.status)) problems.push(`${r.kind}: status "${r.status}" is not declared, refused or out-of-scope`);
110
+ if (!KIND_STATUSES.includes(r.status)) problems.push(`${r.kind}: status "${r.status}" is not ${KIND_STATUSES.slice(0, -1).join(", ")} or ${KIND_STATUSES.at(-1)}`);
100
111
  if (r.status === "declared" && !r.entity) problems.push(`${r.kind} is declared, and names no entity type`);
101
112
  if (r.status !== "declared" && !r.reason) problems.push(`${r.kind} is ${r.status}, and gives no reason`);
102
113
  }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `chant sql drizzle [--output <path>] [--check] [--bigint number|bigint]`
3
+ * (#4401): build the project the way `chant build` does, folded, and write the
4
+ * Drizzle schema module {@link renderDrizzle} renders from its entities.
5
+ *
6
+ * The file goes to `<project>/drizzle-schema.ts` by default, outside the
7
+ * source directory the build reads, and starts with chant's generated-file
8
+ * marker either way. `-o` and `--output` put it elsewhere, which is also the
9
+ * flag a workspace `generated` entry's generator passes. `--check` renders the
10
+ * same text and compares it with the file instead of writing it: exit 1,
11
+ * naming the file, when they differ.
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { dirname, join, relative, resolve } from "node:path";
16
+ import type { CommandGroupContext } from "@intentius/chant/cli/command-group";
17
+ import { DrizzleDialectError, renderDrizzle } from "./drizzle";
18
+ import { buildFolded, loadProject } from "./sql-types-command";
19
+
20
+ /** The file `chant sql drizzle` writes by default, in the project directory. */
21
+ export const DRIZZLE_SCHEMA_FILE = "drizzle-schema.ts";
22
+
23
+ const USAGE = "usage: chant sql drizzle [--output <path>] [--check] [--bigint number|bigint]";
24
+
25
+ interface Args {
26
+ output?: string;
27
+ check: boolean;
28
+ bigint?: "number" | "bigint";
29
+ }
30
+
31
+ function parseArgs(args: readonly string[]): Args | { error: string } {
32
+ const out: Args = { check: false };
33
+ for (let i = 0; i < args.length; i++) {
34
+ const a = args[i]!;
35
+ if (a === "--output" || a === "-o") {
36
+ const v = args[++i];
37
+ if (!v) return { error: `${a} needs a path` };
38
+ out.output = v;
39
+ } else if (a.startsWith("--output=")) out.output = a.slice("--output=".length);
40
+ else if (a === "--check") out.check = true;
41
+ else if (a === "--bigint" || a.startsWith("--bigint=")) {
42
+ const v = a === "--bigint" ? args[++i] : a.slice("--bigint=".length);
43
+ if (v !== "number" && v !== "bigint") return { error: "--bigint takes number or bigint" };
44
+ out.bigint = v;
45
+ } else return { error: `unknown argument ${a}` };
46
+ }
47
+ return out;
48
+ }
49
+
50
+ export async function runDrizzle(ctx: CommandGroupContext): Promise<number> {
51
+ const args = parseArgs(ctx.rawArgs);
52
+ if ("error" in args) {
53
+ console.error(`${args.error}\n${USAGE}`);
54
+ return 1;
55
+ }
56
+ const cwd = process.cwd();
57
+ const project = await loadProject(cwd);
58
+ const output = args.output !== undefined ? resolve(cwd, args.output) : join(project.projectDir, DRIZZLE_SCHEMA_FILE);
59
+ const result = await buildFolded(project, "chant sql drizzle", "nothing was written");
60
+ if (!result) return 1;
61
+
62
+ let rendered;
63
+ try {
64
+ rendered = renderDrizzle(result.entities, { config: project.config as unknown as Record<string, unknown>, ...(args.bigint ? { bigintMode: args.bigint } : {}) });
65
+ } catch (err) {
66
+ if (!(err instanceof DrizzleDialectError)) throw err;
67
+ console.error(`chant sql drizzle: ${err.message}`);
68
+ return 1;
69
+ }
70
+ const shown = relative(cwd, output);
71
+ const named = shown && !shown.startsWith("..") ? shown : output;
72
+ if (args.check) {
73
+ // Compares only; the file is never written.
74
+ const onDisk = existsSync(output) ? readFileSync(output, "utf8") : undefined;
75
+ if (onDisk === rendered.text) {
76
+ console.log(`${named} is current`);
77
+ return 0;
78
+ }
79
+ console.error(
80
+ onDisk === undefined
81
+ ? `chant sql drizzle: ${named} does not exist; run \`chant sql drizzle\` to write it`
82
+ : `chant sql drizzle: ${named} differs from what the build declares; run \`chant sql drizzle\` to rewrite it`,
83
+ );
84
+ return 1;
85
+ }
86
+ mkdirSync(dirname(output), { recursive: true });
87
+ writeFileSync(output, rendered.text);
88
+ const { tables, views, enums, sequences } = rendered.counts;
89
+ const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
90
+ console.log(`wrote ${named}: ${[plural(tables, "table"), plural(views, "view"), plural(enums, "enum"), plural(sequences, "sequence")].join(", ")}`);
91
+ for (const s of rendered.skipped) console.log(`not written: ${s.kind} ${s.names.join(", ")} (${s.reason})`);
92
+ for (const n of rendered.approximated) console.log(`partly written: ${n}`);
93
+ return 0;
94
+ }