@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
@@ -21,7 +21,8 @@ import { isAccessStatement, parseAccess, type AccessNode } from "../clickhouse/a
21
21
  import type { Token } from "../clickhouse/tokens";
22
22
  import { CLICKHOUSE_TAG_OF } from "../clickhouse/import/generator";
23
23
  import { describeStatement, type ImportedObject } from "../clickhouse/import/ir";
24
- import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
24
+ import { fileUnparsed, firstLine, lineAt, quoteBare, referencedStatement, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
25
+ import { createdCollectionName } from "../clickhouse/named-collections";
25
26
  import { templateSafe, templateStrings } from "./postgres";
26
27
 
27
28
  type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
@@ -95,6 +96,12 @@ export function readClickHouse(ddl: string, options: ReadOptions, problems: stri
95
96
  for (const stmt of splitTokens(tokenizeText(ddl, 0), isTrivia)) {
96
97
  let sql = tokensText(stmt);
97
98
  const line = lineAt(ddl, stmt[0]!.start);
99
+ const referenced = options.prerequisites ? referencedStatement("clickhouse", sql) : undefined;
100
+ const collection = referenced ? createdCollectionName(sql) : undefined;
101
+ if (referenced && collection !== undefined) {
102
+ options.prerequisites!.push({ kind: referenced.kind, name: collection });
103
+ continue;
104
+ }
98
105
  const undeclarable = undeclarableStatement("clickhouse", sql);
99
106
  if (undeclarable) {
100
107
  (options.undeclarable ?? problems).push(undeclarable);
@@ -3,7 +3,7 @@
3
3
  import type { Token } from "../core/tokens";
4
4
  import type { UnknownClause } from "../core/cursor";
5
5
  import type { SqlDialect } from "../dialects";
6
- import { statementKind } from "../coverage";
6
+ import { statementKind, type KindRow } from "../coverage";
7
7
 
8
8
  export interface ReadOptions {
9
9
  /** Names the DDL in errors: a file's path, a command. */
@@ -19,6 +19,19 @@ export interface ReadOptions {
19
19
  * --parser-option skipUndeclarable` lists them as warnings.
20
20
  */
21
21
  undeclarable?: string[];
22
+ /**
23
+ * Where a statement of a referenced kind goes (#4395), as the name it
24
+ * creates: `chant import` lists a named collection's `CREATE` as an
25
+ * environment prerequisite. Without it, such a statement is a problem, as
26
+ * in a build's `.sql` file, which never declares one.
27
+ */
28
+ prerequisites?: Array<{ kind: string; name: string }>;
29
+ }
30
+
31
+ /** The coverage row of a `CREATE` of a referenced kind (#4395), or undefined for any other statement. */
32
+ export function referencedStatement(dialect: SqlDialect, sql: string): KindRow | undefined {
33
+ const row = statementKind(dialect, sql);
34
+ return row?.status === "referenced" ? row : undefined;
22
35
  }
23
36
 
24
37
  /**
@@ -138,7 +138,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
138
138
  };
139
139
  switch (node.statement) {
140
140
  case "comment":
141
- if (!["SCHEMA", "EXTENSION", "ROLE", "TRIGGER", "POLICY", "CONSTRAINT", "COLUMN", "CAST", "EVENT TRIGGER"].includes(node.objectType)) mark(node.target);
141
+ if (!["SCHEMA", "EXTENSION", "ROLE", "TRIGGER", "POLICY", "CONSTRAINT", "COLUMN", "CAST", "EVENT TRIGGER", "FOREIGN DATA WRAPPER", "SERVER", "PUBLICATION"].includes(node.objectType)) mark(node.target);
142
142
  if (node.objectType === "COLUMN" && nameOf(tokens, node.target).length === 2 && options.schema) {
143
143
  const first = tokens.slice(node.target.span.from, node.target.span.to).find((t) => !isTrivia(t));
144
144
  if (first) inserts.add(tokens.indexOf(first));
@@ -150,6 +150,10 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
150
150
  mark(node.table);
151
151
  followOns.push({ tokens, node, inserts });
152
152
  continue;
153
+ case "textSearchMapping":
154
+ mark(node.name);
155
+ followOns.push({ tokens, node, inserts });
156
+ continue;
153
157
  case "index":
154
158
  case "trigger":
155
159
  case "policy":
@@ -177,7 +181,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
177
181
  problems.push(`an index with no name (its declaration needs one): ${firstLine(tokensText(tokens))}`);
178
182
  continue;
179
183
  }
180
- const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant", "cast", "eventTrigger"].includes(node.statement);
184
+ const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant", "cast", "eventTrigger", "foreignDataWrapper", "foreignServer", "publication"].includes(node.statement);
181
185
  if (ownName && "name" in node) mark(node.name);
182
186
  const object: PgFileObject = { node, statements: [rewrite(tokens, inserts, options.schema)], constraints: [], line, unparsed: fileUnparsed("unknown" in node ? node.unknown : undefined, tokens, tokensText(tokens), line) };
183
187
  objects.push(object);
@@ -189,7 +193,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
189
193
  const k = followOnOwner(f.node, f.tokens, nameOf, qualify);
190
194
  const owner = k ? owners.get(k) : undefined;
191
195
  if (!owner) {
192
- const what = f.node.statement === "comment" ? `COMMENT ON ${f.node.objectType}` : "ALTER TABLE ... ROW LEVEL SECURITY";
196
+ const what = f.node.statement === "comment" ? `COMMENT ON ${f.node.objectType}` : f.node.statement === "textSearchMapping" ? "ALTER TEXT SEARCH CONFIGURATION ... ADD MAPPING" : "ALTER TABLE ... ROW LEVEL SECURITY";
193
197
  problems.push(`${what} for an object the file does not create: ${firstLine(tokensText(f.tokens))}`);
194
198
  continue;
195
199
  }
@@ -279,11 +283,23 @@ function ownerKey(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify:
279
283
  return `${node.statement} ${nameOf(tokens, node.name).at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.table)))}`;
280
284
  case "eventTrigger":
281
285
  return `eventTrigger ${nameOf(tokens, node.name).at(-1) ?? ""}`;
286
+ case "foreignDataWrapper":
287
+ case "foreignServer":
288
+ case "publication":
289
+ return `${node.statement} ${nameOf(tokens, node.name).at(-1) ?? ""}`;
290
+ // A class or family is named by its index method too, and a configuration and a dictionary are different namespaces.
291
+ case "operatorClass":
292
+ case "operatorFamily":
293
+ return `${node.statement} ${qkey(qualify(nameOf(tokens, node.name)))} USING ${node.method}`;
294
+ case "textSearchConfiguration":
295
+ case "textSearchDictionary":
296
+ return `${node.statement} ${qkey(qualify(nameOf(tokens, node.name)))}`;
282
297
  case "cast":
283
298
  return `cast ${castKey(tokens, node.source, node.target)}`;
284
299
  case "grant":
285
300
  case "comment":
286
301
  case "rowSecurity":
302
+ case "textSearchMapping":
287
303
  return undefined;
288
304
  default:
289
305
  return `rel ${qkey(qualify(nameOf(tokens, node.name)))}`;
@@ -293,6 +309,7 @@ function ownerKey(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify:
293
309
  /** The key of the object a COMMENT ON or ALTER TABLE ... ROW LEVEL SECURITY finishes. */
294
310
  function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify: Qualify): string | undefined {
295
311
  if (node.statement === "rowSecurity") return `rel ${qkey(qualify(nameOf(tokens, node.table)))}`;
312
+ if (node.statement === "textSearchMapping") return `textSearchConfiguration ${qkey(qualify(nameOf(tokens, node.name)))}`;
296
313
  if (node.statement !== "comment") return undefined;
297
314
  const target = nameOf(tokens, node.target);
298
315
  switch (node.objectType) {
@@ -311,6 +328,20 @@ function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qua
311
328
  return node.on ? `${node.objectType.toLowerCase()} ${target.at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.on)))}` : undefined;
312
329
  case "EVENT TRIGGER":
313
330
  return `eventTrigger ${target.at(-1) ?? ""}`;
331
+ case "FOREIGN DATA WRAPPER":
332
+ return `foreignDataWrapper ${target.at(-1) ?? ""}`;
333
+ case "SERVER":
334
+ return `foreignServer ${target.at(-1) ?? ""}`;
335
+ case "PUBLICATION":
336
+ return `publication ${target.at(-1) ?? ""}`;
337
+ case "OPERATOR CLASS":
338
+ return `operatorClass ${qkey(qualify(target))} USING ${node.using ?? ""}`;
339
+ case "OPERATOR FAMILY":
340
+ return `operatorFamily ${qkey(qualify(target))} USING ${node.using ?? ""}`;
341
+ case "TEXT SEARCH CONFIGURATION":
342
+ return `textSearchConfiguration ${qkey(qualify(target))}`;
343
+ case "TEXT SEARCH DICTIONARY":
344
+ return `textSearchDictionary ${qkey(qualify(target))}`;
314
345
  case "CAST":
315
346
  return node.cast ? `cast ${castKey(tokens, node.cast.source, node.cast.target)}` : undefined;
316
347
  case "CONSTRAINT":
@@ -2177,6 +2177,9 @@ export const AGGREGATE_COMBINATORS: readonly string[] = ["ArgMax","ArgMin","Arra
2177
2177
  /** The table functions (`system.table_functions`): `numbers`, `remote`, `s3`. */
2178
2178
  export const TABLE_FUNCTIONS: readonly string[] = ["SQLStandardValues","arrowFlight","arrowflight","azureBlobStorage","azureBlobStorageCluster","bigquery","cluster","clusterAllReplicas","cosn","deltaLake","deltaLakeAzure","deltaLakeAzureCluster","deltaLakeCluster","deltaLakeLocal","deltaLakeS3","deltaLakeS3Cluster","dictionary","eval","executable","file","fileCluster","filesystem","format","fuzzJSON","fuzzQuery","gcs","generateRandom","generateSeries","generate_series","hdfs","hdfsCluster","hive","hudi","hudiCluster","iceberg","icebergAzure","icebergAzureCluster","icebergCluster","icebergHDFS","icebergHDFSCluster","icebergLocal","icebergLocalCluster","icebergS3","icebergS3Cluster","input","jdbc","loop","merge","mergeTreeAnalyzeIndexes","mergeTreeAnalyzeIndexesUUID","mergeTreeCodecBlockCounts","mergeTreeIndex","mergeTreeProjection","mergeTreeTextIndex","mongodb","mysql","null","numbers","numbers_mt","odbc","oss","paimon","paimonAzure","paimonAzureCluster","paimonCluster","paimonHDFS","paimonHDFSCluster","paimonLocal","paimonS3","paimonS3Cluster","postgresql","primes","prometheusQuery","prometheusQueryRange","redis","remote","remoteSecure","s3","s3Cluster","sqlite","timeSeriesData","timeSeriesMetrics","timeSeriesSamples","timeSeriesSelector","timeSeriesTags","url","urlCluster","values","view","viewExplain","viewIfPermitted","ytsaurus","zeros","zeros_mt"];
2179
2179
 
2180
+ /** The input and output formats (`system.formats`): `Parquet`, `JSONEachRow`. */
2181
+ export const FORMATS: readonly string[] = ["Arrow","ArrowStream","Avro","AvroConfluent","BSONEachRow","Buffers","CSV","CSVWithNames","CSVWithNamesAndTypes","CapnProto","CustomSeparated","CustomSeparatedIgnoreSpaces","CustomSeparatedIgnoreSpacesWithNames","CustomSeparatedIgnoreSpacesWithNamesAndTypes","CustomSeparatedWithNames","CustomSeparatedWithNamesAndTypes","DWARF","Form","GeoJSON","Hash","HiveText","JSON","JSONAsObject","JSONAsString","JSONColumns","JSONColumnsWithMetadata","JSONCompact","JSONCompactColumns","JSONCompactEachRow","JSONCompactEachRowWithNames","JSONCompactEachRowWithNamesAndTypes","JSONCompactEachRowWithProgress","JSONCompactStrings","JSONCompactStringsEachRow","JSONCompactStringsEachRowWithNames","JSONCompactStringsEachRowWithNamesAndTypes","JSONCompactStringsEachRowWithProgress","JSONEachRow","JSONEachRowWithProgress","JSONL","JSONLines","JSONObjectEachRow","JSONStrings","JSONStringsEachRow","JSONStringsEachRowWithProgress","LineAsString","LineAsStringWithNames","LineAsStringWithNamesAndTypes","MD","Markdown","MsgPack","MySQLDump","MySQLWire","NDJSON","Native","Npy","Null","ODBCDriver2","ORC","One","PNG","Parquet","ParquetMetadata","PostgreSQLWire","Pretty","PrettyCompact","PrettyCompactMonoBlock","PrettyCompactNoEscapes","PrettyCompactNoEscapesMonoBlock","PrettyJSONEachRow","PrettyJSONLines","PrettyMonoBlock","PrettyNDJSON","PrettyNoEscapes","PrettyNoEscapesMonoBlock","PrettySpace","PrettySpaceMonoBlock","PrettySpaceNoEscapes","PrettySpaceNoEscapesMonoBlock","Prometheus","Protobuf","ProtobufList","ProtobufSingle","Puffin","PuffinMetadata","Raw","RawBLOB","RawWithNames","RawWithNamesAndTypes","Regexp","RowBinary","RowBinaryWithDefaults","RowBinaryWithNames","RowBinaryWithNamesAndTypes","RowBinaryWithNamesAndTypesAndDefaults","SQLInsert","TSKV","TSV","TSVRaw","TSVRawWithNames","TSVRawWithNamesAndTypes","TSVWithNames","TSVWithNamesAndTypes","TabSeparated","TabSeparatedRaw","TabSeparatedRawWithNames","TabSeparatedRawWithNamesAndTypes","TabSeparatedWithNames","TabSeparatedWithNamesAndTypes","Template","TemplateIgnoreSpaces","Values","Vertical","XML"];
2182
+
2180
2183
  export const SKIP_INDEX_TYPES: Readonly<Record<SkipIndexType, SkipIndexSpec>> = {
2181
2184
  bloom_filter: {"syntax":"INDEX name expr TYPE bloom_filter([false_positive_rate]) GRANULARITY g","args":[{"name":"false_positive_rate","position":0,"optional":true,"repeated":false,"named":false,"kind":"number","range":[0,1],"note":"Exclusive of 0 and 1; 0.025 when omitted."}],"summary":"A Bloom filter over the values of the index expression, for speeding up equality and IN conditions on columns, including Array and Map elements."},
2182
2185
  hypothesis: {"syntax":"INDEX name expr TYPE hypothesis GRANULARITY g","args":[],"summary":"Deprecated and no longer supported. It is retained only so that tables which still reference it can be attached."},
@@ -129,6 +129,56 @@
129
129
  "kind": "resource",
130
130
  "lexicon": "sql"
131
131
  },
132
+ "PostgresOperator": {
133
+ "resourceType": "Postgres::Operator",
134
+ "kind": "resource",
135
+ "lexicon": "sql"
136
+ },
137
+ "PostgresOperatorClass": {
138
+ "resourceType": "Postgres::OperatorClass",
139
+ "kind": "resource",
140
+ "lexicon": "sql"
141
+ },
142
+ "PostgresOperatorFamily": {
143
+ "resourceType": "Postgres::OperatorFamily",
144
+ "kind": "resource",
145
+ "lexicon": "sql"
146
+ },
147
+ "PostgresForeignTable": {
148
+ "resourceType": "Postgres::ForeignTable",
149
+ "kind": "resource",
150
+ "lexicon": "sql"
151
+ },
152
+ "PostgresTextSearchConfiguration": {
153
+ "resourceType": "Postgres::TextSearchConfiguration",
154
+ "kind": "resource",
155
+ "lexicon": "sql"
156
+ },
157
+ "PostgresTextSearchDictionary": {
158
+ "resourceType": "Postgres::TextSearchDictionary",
159
+ "kind": "resource",
160
+ "lexicon": "sql"
161
+ },
162
+ "PostgresStatistics": {
163
+ "resourceType": "Postgres::Statistics",
164
+ "kind": "resource",
165
+ "lexicon": "sql"
166
+ },
167
+ "PostgresForeignDataWrapper": {
168
+ "resourceType": "Postgres::ForeignDataWrapper",
169
+ "kind": "resource",
170
+ "lexicon": "sql"
171
+ },
172
+ "PostgresForeignServer": {
173
+ "resourceType": "Postgres::ForeignServer",
174
+ "kind": "resource",
175
+ "lexicon": "sql"
176
+ },
177
+ "PostgresPublication": {
178
+ "resourceType": "Postgres::Publication",
179
+ "kind": "resource",
180
+ "lexicon": "sql"
181
+ },
132
182
  "PostgresExtension": {
133
183
  "resourceType": "Postgres::Extension",
134
184
  "kind": "resource",
@@ -0,0 +1,92 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { resolveParserOptions } from "@intentius/chant/import/parser-options";
3
+ import { guessDialect, looksLikeTSql, sourceOption } from "./import-parser";
4
+ import { sqlPlugin } from "./plugin";
5
+
6
+ const TSQL = `SET ANSI_NULLS ON
7
+ GO
8
+ CREATE TABLE [dbo].[Orders] (
9
+ [OrderID] INT IDENTITY (1, 1) NOT NULL,
10
+ [Note] NVARCHAR (200) NULL,
11
+ CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderID] ASC)
12
+ );
13
+ GO`;
14
+
15
+ const parse = (text: string, entries: string[] = []) => {
16
+ const resolved = resolveParserOptions(sqlPlugin, entries);
17
+ if ("error" in resolved) throw new Error(resolved.error);
18
+ return sqlPlugin.templateParser!(resolved.options).parse(text);
19
+ };
20
+ const types = (ir: { resources: Array<{ type: string }> }) => [...new Set(ir.resources.map((r) => r.type))];
21
+
22
+ describe("a T-SQL file is not read as Postgres", () => {
23
+ test("the dialect guess alone would have said Postgres, which is why the markers exist", () => {
24
+ expect(guessDialect(TSQL)).toBe("postgres");
25
+ expect(looksLikeTSql(TSQL)).toBe(true);
26
+ });
27
+
28
+ test("with no option, a T-SQL file is read as SQL Server and gives ClickHouse objects and diagnostics", () => {
29
+ const ir = parse(TSQL);
30
+ expect(types(ir)).toEqual(["ClickHouse::Table"]);
31
+ expect(ir.diagnostics?.length).toBeGreaterThan(0);
32
+ expect(ir.metadata).toMatchObject({ source: "sqlserver" });
33
+ });
34
+
35
+ test.each([
36
+ ["a GO line", "CREATE TABLE a (x int)\nGO\n"],
37
+ ["CLUSTERED", "CREATE CLUSTERED INDEX ix ON a (x)"],
38
+ ["COLUMNSTORE", "CREATE CLUSTERED COLUMNSTORE INDEX cci ON a"],
39
+ ["a bracketed name", "CREATE TABLE [a] (x int)"],
40
+ ["nvarchar", "CREATE TABLE a (x nvarchar(5))"],
41
+ ["IDENTITY(1,1)", "CREATE TABLE a (x int IDENTITY(1,1))"],
42
+ ["an SSMS preamble", "SET QUOTED_IDENTIFIER ON\nCREATE TABLE a (x int)"],
43
+ ["sp_addextendedproperty", "EXEC sp_addextendedproperty @name = N'a'"],
44
+ ])("%s marks a file as T-SQL", (_, text) => {
45
+ expect(looksLikeTSql(text)).toBe(true);
46
+ });
47
+
48
+ test.each([
49
+ ["Postgres with arrays and a quoted name", 'CREATE TABLE "a" (x int[], y text[3], z int GENERATED ALWAYS AS IDENTITY, PRIMARY KEY (x))'],
50
+ ["Postgres with a subscript", "CREATE FUNCTION f(a int[]) RETURNS int AS $$ SELECT a[1] $$ LANGUAGE sql"],
51
+ ["ClickHouse", "CREATE TABLE a (x UInt8) ENGINE = MergeTree ORDER BY x"],
52
+ ["ClickHouse naming a clustered column", "CREATE TABLE clustered (x UInt8) ENGINE = Memory"],
53
+ ["a plain index", "CREATE INDEX ix ON a (x)"],
54
+ ])("%s is not", (_, text) => {
55
+ expect(looksLikeTSql(text)).toBe(false);
56
+ });
57
+
58
+ test("Postgres and ClickHouse files import as before", () => {
59
+ expect(types(parse("CREATE TABLE a (x int PRIMARY KEY, y text)"))).toEqual(["Postgres::Table"]);
60
+ expect(types(parse("CREATE TABLE a (x UInt8) ENGINE = MergeTree ORDER BY x"))).toEqual(["ClickHouse::Table"]);
61
+ expect(parse("CREATE TABLE a (x int PRIMARY KEY)").diagnostics).toBeUndefined();
62
+ });
63
+
64
+ test("a dialect named by the author is kept: dialect=postgres on T-SQL fails as Postgres, it is not rerouted", () => {
65
+ expect(() => parse(TSQL, ["dialect=postgres"])).toThrow(/Postgres DDL/);
66
+ });
67
+ });
68
+
69
+ describe("--parser-option from=sqlserver", () => {
70
+ test("is declared with the other options", () => {
71
+ expect(sqlPlugin.parserOptions!().map((o) => o.name)).toEqual(["dialect", "from", "skipUndeclarable"]);
72
+ expect(resolveParserOptions(sqlPlugin, ["from=sqlserver"])).toEqual({ options: { from: "sqlserver" } });
73
+ });
74
+
75
+ test("forces the T-SQL source on a file without markers", () => {
76
+ const ir = parse("CREATE TABLE a (x int NOT NULL)", ["from=sqlserver"]);
77
+ expect(types(ir)).toEqual(["ClickHouse::Table"]);
78
+ expect(ir.diagnostics!.map((d) => d.ruleId)).toEqual(["SQLRV001", "SQLRV002"]);
79
+ });
80
+
81
+ test("a source other than sqlserver is refused, naming what is accepted", () => {
82
+ expect(() => sourceOption({ from: "mysql" })).toThrow("expected sqlserver, got mysql");
83
+ });
84
+
85
+ test("cannot be combined with dialect", () => {
86
+ expect(() => sourceOption({ from: "sqlserver", dialect: "clickhouse" })).toThrow(/cannot be combined with dialect/);
87
+ });
88
+
89
+ test("a lexing error is reported as an import error naming the position", () => {
90
+ expect(() => parse("CREATE TABLE [a (x int)", ["from=sqlserver"])).toThrow(/unterminated \[identifier\] \(line 1, column 14\)/);
91
+ });
92
+ });
@@ -1,6 +1,16 @@
1
1
  /**
2
2
  * `chant import schema.sql`: a file of DDL into the import IR, in either
3
- * dialect. `--parser-option dialect=postgres` (or `clickhouse`) names it;
3
+ * dialect, or a SQL Server (T-SQL) schema into ClickHouse declarations.
4
+ *
5
+ * `--parser-option from=sqlserver` (#4398) reads the file as T-SQL and
6
+ * translates it to ClickHouse (`./sqlserver/translate.ts`). SQL Server is an
7
+ * import source, not a dialect: nothing is declared in it, and a build never
8
+ * holds one. A file with T-SQL's own markers (`[bracketed]` names, a `GO`
9
+ * line, `CLUSTERED` or `COLUMNSTORE`, `nvarchar`, `IDENTITY(1,1)`) is read
10
+ * the same way without the option, so it is not taken for Postgres.
11
+ * `from=sqlserver` with `dialect=...` is refused: the target is ClickHouse.
12
+ *
13
+ * `--parser-option dialect=postgres` (or `clickhouse`) names the dialect;
4
14
  * without one it is read off the statements: an `ENGINE =` clause, a
5
15
  * `CREATE DATABASE`, a `CREATE DICTIONARY` or a lambda `CREATE FUNCTION f AS
6
16
  * (x) -> ...` is ClickHouse, a statement
@@ -22,6 +32,9 @@ import { readPostgres } from "./files/postgres";
22
32
  import { SqlFileError } from "./files/common";
23
33
  import { objectsToIR as chObjectsToIR } from "./clickhouse/import/ir";
24
34
  import { objectsToIR as pgObjectsToIR } from "./postgres/import/ir";
35
+ import { namedCollectionPrerequisites } from "./clickhouse/named-collections";
36
+ import { translateSqlServer } from "./sqlserver/translate";
37
+ import { TsqlLexError } from "./sqlserver/lexer";
25
38
 
26
39
  const CLICKHOUSE_ONLY = [
27
40
  /\bENGINE\s*=/i,
@@ -37,6 +50,31 @@ const POSTGRES_ONLY = [
37
50
  /^\s*CREATE\s+TABLE\b/im,
38
51
  ];
39
52
 
53
+ /**
54
+ * Markers only T-SQL has. Each is rare enough in Postgres and ClickHouse DDL
55
+ * that one is enough: a bracketed name after CREATE TABLE or ON, a `GO`
56
+ * batch line, `CLUSTERED`/`COLUMNSTORE`, T-SQL type names, `IDENTITY(n, n)`,
57
+ * the `SET ANSI_NULLS` preamble of an SSMS script.
58
+ */
59
+ const TSQL_MARKERS = [
60
+ /^\s*GO\s*$/im,
61
+ /\bCREATE\s+(?:UNIQUE\s+)?(?:NONCLUSTERED\s+|CLUSTERED\s+)(?:COLUMNSTORE\s+)?INDEX\b/i,
62
+ /\bCREATE\s+(?:NONCLUSTERED\s+|CLUSTERED\s+)?COLUMNSTORE\s+INDEX\b/i,
63
+ /\bPRIMARY\s+KEY\s+(?:NON)?CLUSTERED\b/i,
64
+ /\b(?:CREATE\s+TABLE|ALTER\s+TABLE|\bON|REFERENCES)\s+\[[^\]\n]+\]/i,
65
+ /\b(?:NVARCHAR|NCHAR|UNIQUEIDENTIFIER|DATETIME2|DATETIMEOFFSET|SMALLDATETIME|SMALLMONEY|ROWVERSION)\b/i,
66
+ /\bIDENTITY\s*\(\s*\d+\s*,\s*\d+\s*\)/i,
67
+ /^\s*SET\s+(?:ANSI_NULLS|QUOTED_IDENTIFIER|NOCOUNT|ANSI_PADDING)\b/im,
68
+ /\bEXEC(?:UTE)?\s+sp_addextendedproperty\b/i,
69
+ ];
70
+
71
+ /** True when the DDL has a marker only T-SQL has and no ClickHouse-only clause. */
72
+ export function looksLikeTSql(content: string): boolean {
73
+ const text = content.replace(/--[^\n]*/g, "");
74
+ if (CLICKHOUSE_ONLY.some((re) => re.test(text))) return false;
75
+ return TSQL_MARKERS.some((re) => re.test(text));
76
+ }
77
+
40
78
  /** The dialect a file of DDL is written in, read off its statements. */
41
79
  export function guessDialect(content: string): SqlDialect {
42
80
  const text = content.replace(/--[^\n]*/g, "");
@@ -53,20 +91,56 @@ export function dialectOption(options: ParserOptions | undefined): SqlDialect |
53
91
  return d as SqlDialect;
54
92
  }
55
93
 
94
+ /** Sources other than a dialect that `--parser-option from=` names. */
95
+ export const IMPORT_SOURCES = ["sqlserver"] as const;
96
+ export type ImportSource = (typeof IMPORT_SOURCES)[number];
97
+
98
+ /** The `from` parser option, checked. */
99
+ export function sourceOption(options: ParserOptions | undefined): ImportSource | undefined {
100
+ const f = options?.from;
101
+ if (f === undefined) return undefined;
102
+ if (typeof f !== "string" || !(IMPORT_SOURCES as readonly string[]).includes(f)) throw new Error(`--parser-option from: expected ${IMPORT_SOURCES.join(" or ")}, got ${String(f)}`);
103
+ if (options?.dialect !== undefined) throw new Error(`--parser-option from=${f} cannot be combined with dialect: a ${f} schema is translated to ClickHouse`);
104
+ return f as ImportSource;
105
+ }
106
+
56
107
  export class SqlFileParser implements TemplateParser {
57
108
  constructor(
58
109
  private readonly dialect?: SqlDialect,
59
- private readonly options: { skipUndeclarable?: boolean } = {},
110
+ private readonly options: { skipUndeclarable?: boolean; from?: ImportSource } = {},
60
111
  ) {}
61
112
 
62
113
  parse(content: string): TemplateIR {
114
+ if (this.options.from === "sqlserver" || (this.dialect === undefined && looksLikeTSql(content))) return this.parseSqlServer(content);
63
115
  const dialect = this.dialect ?? guessDialect(content);
64
116
  const problems: string[] = [];
65
117
  const skipped: string[] = [];
66
- const read = { origin: "the DDL", ...(this.options.skipUndeclarable ? { undeclarable: skipped } : {}) };
118
+ const prerequisites: Array<{ kind: string; name: string }> = [];
119
+ const read = { origin: "the DDL", prerequisites, ...(this.options.skipUndeclarable ? { undeclarable: skipped } : {}) };
67
120
  const ir = dialect === "postgres" ? pgObjectsToIR(readPostgres(content, read, problems)) : chObjectsToIR(readClickHouse(content, read, problems));
68
121
  if (problems.length > 0) throw new SqlFileError(`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} DDL`, problems);
69
- if (skipped.length === 0) return ir;
70
- return { ...ir, warnings: [...(ir.warnings ?? []), ...skipped.map((s) => `left out (skipUndeclarable): ${s}`)] };
122
+ // A named collection is the environment's (#4395): its CREATE, or a declaration naming one, is a prerequisite, its values never imported.
123
+ const objects = ir.resources.map((r) => r.properties as { database?: string; name: string; ddl: string });
124
+ const prereq =
125
+ dialect === "clickhouse"
126
+ ? namedCollectionPrerequisites(prerequisites, objects, { found: "the DDL creates it", unseen: "the DDL does not create it" })
127
+ : { prerequisites: [], warnings: [] };
128
+ const warnings = [...(ir.warnings ?? []), ...prereq.warnings, ...skipped.map((s) => `left out (skipUndeclarable): ${s}`)];
129
+ const out = prereq.prerequisites.length > 0 ? { ...ir, metadata: { ...ir.metadata, prerequisites: prereq.prerequisites } } : ir;
130
+ return warnings.length > 0 ? { ...out, warnings } : out;
131
+ }
132
+
133
+ /** A T-SQL schema as ClickHouse objects, with a finding for each judgment call (#4398). */
134
+ private parseSqlServer(content: string): TemplateIR {
135
+ let t;
136
+ try {
137
+ t = translateSqlServer(content, { skipUnreadable: this.options.skipUndeclarable === true });
138
+ } catch (e) {
139
+ if (e instanceof TsqlLexError) throw new SqlFileError("the SQL Server DDL", [e.message]);
140
+ throw e;
141
+ }
142
+ if (t.problems.length > 0) throw new SqlFileError("the SQL Server DDL", t.problems);
143
+ const ir = chObjectsToIR(t.objects);
144
+ return { ...ir, diagnostics: t.diagnostics, metadata: { ...ir.metadata, source: "sqlserver" } };
71
145
  }
72
146
  }
package/src/index.ts CHANGED
@@ -18,7 +18,7 @@ export { database, table, view, dictionary, user, role, policy, quota, settingsP
18
18
  // A tag folds as the function its file imports, so `table` imported from
19
19
  // `@intentius/chant-lexicon-sql/postgres` is Postgres's. Declarations import
20
20
  // them from the dialect subpath.
21
- export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, collation, cast } from "./postgres/entities";
21
+ export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, operator, operatorClass, operatorFamily, foreignTable, textSearchConfiguration, textSearchDictionary, statistics, foreignDataWrapper, foreignServer, publication, collation, cast } from "./postgres/entities";
22
22
 
23
23
  // A schema change's statements between two builds, rendered offline for a
24
24
  // migration file (#3644); also at `@intentius/chant-lexicon-sql/migration-statements`.
@@ -33,4 +33,5 @@ export const sqlPostSynthAuditEntries: Record<string, RuleMeta> = {
33
33
  SQLCH125: auditRule("SQLCH125", "merge-worthy", "guidance", "A codec parameter is outside what the codec takes", "Give the codec a parameter within its range or set, or drop the parameter for the default.", { category: "correctness" }),
34
34
  SQLCH126: auditRule("SQLCH126", "merge-worthy", "guidance", "A GRANT column list names a column the table does not declare", "Fix the column name, or declare the column on the table.", { category: "correctness" }),
35
35
  SQLCH127: auditRule("SQLCH127", "merge-worthy", "guidance", "An expression calls a function the pinned server does not have", "Fix the function name, use one from the pinned server's system.functions, or declare it with func.", { category: "correctness" }),
36
+ SQLCH128: auditRule("SQLCH128", "merge-worthy", "guidance", "An integration engine or table function argument holds a literal credential", "Create a named collection with the credentials on the server and name it in place of the arguments: ENGINE = S3(lake_creds, format = 'Parquet').", { category: "security" }),
36
37
  };
@@ -29,6 +29,7 @@ import { sqlch124 } from "./sqlch124";
29
29
  import { sqlch125 } from "./sqlch125";
30
30
  import { sqlch126 } from "./sqlch126";
31
31
  import { sqlch127 } from "./sqlch127";
32
+ import { sqlch128 } from "./sqlch128";
32
33
  import { sqlpg101 } from "./sqlpg101";
33
34
  import { sqlpg102 } from "./sqlpg102";
34
35
  import { sqlpg103 } from "./sqlpg103";
@@ -86,6 +87,7 @@ export const postSynthChecks: PostSynthCheck[] = [
86
87
  sqlch125,
87
88
  sqlch126,
88
89
  sqlch127,
90
+ sqlch128,
89
91
  sqlpg101,
90
92
  sqlpg102,
91
93
  sqlpg103,
@@ -4,7 +4,7 @@ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
4
4
  import type { SerializerResult } from "@intentius/chant/serializer";
5
5
  import { sqlSerializer } from "../../serializer";
6
6
  import { table as chTable } from "../../clickhouse/entities";
7
- import { domain, extension, func, grant, index, role, schema, sequence, table, type as pgType, view } from "../../postgres/entities";
7
+ import { domain, extension, func, grant, index, operatorClass, role, schema, sequence, table, type as pgType, view } from "../../postgres/entities";
8
8
  import { sqlAuditCatalog } from "../audit-catalog";
9
9
  import { postSynthChecks } from "./index";
10
10
 
@@ -444,6 +444,13 @@ describe("SQLPG125: access methods and operator classes", () => {
444
444
  clean("SQLPG125", { t, i, e: extension`CREATE EXTENSION pg_trgm` });
445
445
  });
446
446
  test("an operator class in an undeclared schema is left alone", () => clean("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} (email ext.my_ops)` }));
447
+ test("an operator class the project declares passes for its own method only (#4406)", () => {
448
+ const app = schema`CREATE SCHEMA app`;
449
+ const ops = operatorClass`CREATE OPERATOR CLASS ${app}.shop_ops FOR TYPE text USING btree AS OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 =, OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 bttextcmp(text, text)`;
450
+ clean("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} (email shop_ops)` });
451
+ clean("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} (email app.shop_ops)` });
452
+ flagged("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} USING hash (email shop_ops)` }, /shop_ops, which Postgres 18 does not have for hash/);
453
+ });
447
454
  });
448
455
 
449
456
  describe("SQLPG126: functions", () => {
@@ -474,6 +474,8 @@ export type OpClassResolution = { ok: true } | { ok: false; miss: Miss; otherMet
474
474
  /** An operator class for `method` at the target major. */
475
475
  export function resolveOpClass(p: PgProject, method: string, opclass: { schema?: string; name: string }): OpClassResolution {
476
476
  if (opclass.schema !== undefined && outsideProject(p, opclass.schema)) return { ok: true };
477
+ // A class the project declares (an `operatorClass` tag), for this method.
478
+ if (p.objects.some((o) => o.type === "Postgres::OperatorClass" && o.name === opclass.name && o.method === method && (opclass.schema === undefined || o.schema === opclass.schema))) return { ok: true };
477
479
  const classes = OPERATOR_CLASSES as Readonly<Record<string, Readonly<Record<string, Presence>>>>;
478
480
  if (presentAt(classes[method]?.[opclass.name], p.major)) return { ok: true };
479
481
  const ext = extensionFor(p, (e) => e.opclasses?.[method]?.includes(opclass.name) ?? false);
@@ -0,0 +1,107 @@
1
+ /**
2
+ * SQLCH128 (#4395): a literal credential in an integration engine's or table
3
+ * function's arguments. Each flagged case builds today and writes its secret
4
+ * into the build output and `clickhouse.sql`; each clean case keeps it out.
5
+ */
6
+ import { describe, expect, test } from "vitest";
7
+ import { makePostSynthCtx } from "@intentius/chant-test-utils";
8
+ import type { SerializerResult } from "@intentius/chant/serializer";
9
+ import { sqlSerializer } from "../../serializer";
10
+ import { database, dictionary, table, view } from "../../clickhouse/entities";
11
+ import { sqlAuditCatalog } from "../audit-catalog";
12
+ import { postSynthChecks } from "./index";
13
+
14
+ function run(entities: Record<string, unknown>) {
15
+ const check = postSynthChecks.find((c) => c.id === "SQLCH128")!;
16
+ const out = sqlSerializer.serialize(new Map(Object.entries(entities)) as never) as SerializerResult;
17
+ return check.check(makePostSynthCtx("sql", out.primary));
18
+ }
19
+
20
+ /** The findings' messages, in the build's apply order (by export name here). */
21
+ const messages = (entities: Record<string, unknown>) => {
22
+ const diags = run(entities);
23
+ for (const d of diags) expect(d).toMatchObject({ checkId: "SQLCH128", lexicon: "sql", severity: "error" });
24
+ return diags.map((d) => d.message);
25
+ };
26
+
27
+ test("SQLCH128 is registered with an audit entry", () => {
28
+ expect(postSynthChecks.map((c) => c.id)).toContain("SQLCH128");
29
+ expect(sqlAuditCatalog.SQLCH128).toBeDefined();
30
+ });
31
+
32
+ describe("an inline secret fails", () => {
33
+ test("S3: the secret access key", () => {
34
+ const t = table`CREATE TABLE lake (a Int32) ENGINE = S3('https://bucket.s3.amazonaws.com/x.parquet', 'AKIAEXAMPLE', 'wJalrXUtnFEMI', 'Parquet')`;
35
+ expect(messages({ t })).toEqual([
36
+ expect.stringMatching(
37
+ /^t \(lake\): ENGINE = S3 argument secret_access_key holds a literal credential, which is the environment's\. Create a named collection .*ENGINE = S3\(lake_creds, format = 'Parquet'\)/,
38
+ ),
39
+ ]);
40
+ });
41
+
42
+ test("Kafka: kafka_sasl_password, a setting", () => {
43
+ const t = table`CREATE TABLE k (a String) ENGINE = Kafka() SETTINGS kafka_broker_list = 'kafka:9092', kafka_topic_list = 'events', kafka_group_name = 'g', kafka_format = 'JSONEachRow', kafka_sasl_username = 'app', kafka_sasl_password = 'hunter2'`;
44
+ expect(messages({ t })).toEqual([expect.stringMatching(/^t \(k\): Kafka setting kafka_sasl_password holds a literal credential/)]);
45
+ });
46
+
47
+ test("PostgreSQL and MySQL: the password", () => {
48
+ const pg = table`CREATE TABLE pg (id Int32) ENGINE = PostgreSQL('db:5432', 'shop', 'orders', 'app', 'hunter2')`;
49
+ const my = table`CREATE TABLE my (id Int32) ENGINE = MySQL('db:3306', 'shop', 'orders', 'app', 'hunter2')`;
50
+ expect(messages({ pg, my })).toEqual([
51
+ expect.stringMatching(/^my \(my\): ENGINE = MySQL argument password holds a literal credential/),
52
+ expect.stringMatching(/^pg \(pg\): ENGINE = PostgreSQL argument password holds a literal credential/),
53
+ ]);
54
+ });
55
+
56
+ test("a database engine, a key = value override after a collection, a URL with a password", () => {
57
+ const db = database`CREATE DATABASE mirror ENGINE = MySQL('db:3306', 'shop', 'app', 'hunter2')`;
58
+ const s3 = table`CREATE TABLE s (a Int32) ENGINE = S3(lake_creds, secret_access_key = 'override', format = 'Parquet')`;
59
+ const u = table`CREATE TABLE u (a Int32) ENGINE = URL('https://app:hunter2@example.com/data.csv', 'CSV')`;
60
+ expect(messages({ db, s3, u })).toEqual([
61
+ expect.stringMatching(/^db \(mirror\): ENGINE = MySQL argument password holds a literal credential/),
62
+ expect.stringMatching(/^s3 \(s\): ENGINE = S3 argument secret_access_key holds a literal credential/),
63
+ expect.stringMatching(/^u \(u\): ENGINE = URL argument 1 holds a URL with a password in it/),
64
+ ]);
65
+ });
66
+
67
+ test("a table function, in a table over it and in a view's SELECT", () => {
68
+ const over = table`CREATE TABLE over AS s3('https://bucket/x.csv', 'AKIAEXAMPLE', 'wJalrXUtnFEMI', 'CSV')`;
69
+ const v = view`CREATE VIEW v AS SELECT * FROM mysql('db:3306', 'shop', 'orders', 'app', 'hunter2')`;
70
+ const r = view`CREATE VIEW r AS SELECT * FROM remote('h:9000', 'shop', 'orders', 'app', 'hunter2')`;
71
+ expect(messages({ over, v, r })).toEqual([
72
+ expect.stringMatching(/^over \(over\): s3\(\) argument secret_access_key holds a literal credential/),
73
+ expect.stringMatching(/^r \(r\): remote\(\) argument password holds a literal credential/),
74
+ expect.stringMatching(/^v \(v\): mysql\(\) argument password holds a literal credential/),
75
+ ]);
76
+ });
77
+
78
+ test("a dictionary source's PASSWORD", () => {
79
+ const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(MYSQL(HOST 'db' PORT 3306 USER 'app' PASSWORD 'hunter2' DB 'shop' TABLE 'names')) LAYOUT(HASHED()) LIFETIME(300)`;
80
+ expect(messages({ d })).toEqual([expect.stringMatching(/^d \(d\): SOURCE PASSWORD holds a literal credential/)]);
81
+ });
82
+
83
+ test("the server's '[HIDDEN]' mask, as an import of SHOW CREATE writes it, is named as such", () => {
84
+ const t = table`CREATE TABLE lake (a Int32) ENGINE = S3('https://bucket/x.parquet', 'AKIAEXAMPLE', '[HIDDEN]')`;
85
+ expect(messages({ t })).toEqual([expect.stringMatching(/secret_access_key holds '\[HIDDEN\]', the server's mask for a credential, in place of one/)]);
86
+ });
87
+ });
88
+
89
+ describe("no secret, no finding", () => {
90
+ test("a named collection in place of the arguments", () => {
91
+ const s3 = table`CREATE TABLE lake (a Int32) ENGINE = S3(lake_creds, url = 'https://bucket/x.parquet', format = 'Parquet')`;
92
+ const k = table`CREATE TABLE k (a String) ENGINE = Kafka(kafka_creds, kafka_format = 'JSONEachRow')`;
93
+ const pg = table`CREATE TABLE pg (id Int32) ENGINE = PostgreSQL(pg_creds, table = 'orders')`;
94
+ const v = view`CREATE VIEW v AS SELECT * FROM mysql(mysql_creds, table = 'orders')`;
95
+ const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(MYSQL(NAME mysql_creds)) LAYOUT(HASHED()) LIFETIME(300)`;
96
+ expect(messages({ s3, k, pg, v, d })).toEqual([]);
97
+ });
98
+
99
+ test("an S3 table with a format and a compression where the keys would be, NOSIGN, or an empty password", () => {
100
+ const fmt = table`CREATE TABLE f (a Int32) ENGINE = S3('https://bucket/x.csv.gz', 'CSV', 'gzip')`;
101
+ const nosign = table`CREATE TABLE n (a Int32) ENGINE = S3('https://bucket/x.csv', NOSIGN, 'CSV')`;
102
+ const az = table`CREATE TABLE az (a Int32) ENGINE = AzureBlobStorage('https://acct.blob.core.windows.net', 'c', 'p.csv', 'CSV', 'gzip')`;
103
+ const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'names' USER 'default' PASSWORD '')) LAYOUT(HASHED()) LIFETIME(300)`;
104
+ const mt = table`CREATE TABLE m (password String) ENGINE = MergeTree ORDER BY tuple()`;
105
+ expect(messages({ fmt, nosign, az, d, mt })).toEqual([]);
106
+ });
107
+ });