@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,558 @@
1
+ /**
2
+ * T-SQL DDL into ClickHouse `CREATE` statements, with a needs-review finding
3
+ * for every judgment call (#4398).
4
+ *
5
+ * What is mechanical (the type map in `./types.ts`, names, column order) makes
6
+ * no finding. Everything else is decided by the shared catalog
7
+ * (`../clickhouse/review-catalog.ts`): the sort key, the engine, `Nullable`
8
+ * against a default, `LowCardinality` candidates and the constraints
9
+ * ClickHouse does not enforce. This file adds the rules only T-SQL has:
10
+ * identity, sequence and computed columns, default expressions, storage
11
+ * options, indexes that are not carried, and a view's query text.
12
+ *
13
+ * A schema other than `dbo` becomes a ClickHouse database of that name.
14
+ * The `dbo` schema is the default and is dropped from names.
15
+ *
16
+ * Nothing is left out silently: a statement this source does not read is a
17
+ * `refused` entry naming it, and an object it reads but does not carry (an
18
+ * index, a `DROP`, an extended property that is not a description) is an
19
+ * `info` or `warning` finding.
20
+ */
21
+
22
+ import { CLICKHOUSE_ENTITY_TYPES } from "../clickhouse/entities";
23
+ import type { ImportedObject } from "../clickhouse/import/ir";
24
+ import {
25
+ constraintReview,
26
+ engineReview,
27
+ findingText,
28
+ lowCardinalityReview,
29
+ nullableReview,
30
+ sortKeyReview,
31
+ type ConstraintFact,
32
+ type ReviewFinding,
33
+ type ReviewRuleId,
34
+ type SortKeyCandidate,
35
+ } from "../clickhouse/review-catalog";
36
+ import type { ImportDiagnostic } from "@intentius/chant/import/parser";
37
+ import { parseTsql, type AlterConstraintAst, type ColumnAst, type ConstraintAst, type IndexAst, type Loc, type QName, type TableAst, type ViewAst } from "./parse";
38
+ import { mapType } from "./types";
39
+ import { upper, type Token } from "./lexer";
40
+
41
+ export interface TranslateOptions {
42
+ /** Where the refusals go: a statement this source cannot read is listed as a warning instead of failing the import. */
43
+ skipUnreadable?: boolean;
44
+ }
45
+
46
+ export interface Translation {
47
+ objects: ImportedObject[];
48
+ diagnostics: ImportDiagnostic[];
49
+ /** Statements refused or unread, as lines naming them. Empty when the file imports. */
50
+ problems: string[];
51
+ }
52
+
53
+ const DEFAULT_SCHEMA = "dbo";
54
+
55
+ const ident = (name: string): string => (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `\`${name.replace(/\\/g, "\\\\").replace(/`/g, "\\`")}\``);
56
+ const chString = (s: string): string => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
57
+ const oneLine = (s: string): string => s.replace(/\s+/g, " ").trim();
58
+
59
+ /** A table's identity: schema and name, compared without regard to case as SQL Server's default collation does. */
60
+ const keyOf = (q: QName): string => {
61
+ const n = q.parts[q.parts.length - 1]!;
62
+ const s = q.parts.length >= 2 ? q.parts[q.parts.length - 2]! : DEFAULT_SCHEMA;
63
+ return `${s}.${n}`.toLowerCase();
64
+ };
65
+ const schemaOf = (q: QName): string => (q.parts.length >= 2 ? q.parts[q.parts.length - 2]! : DEFAULT_SCHEMA);
66
+ const nameOf = (q: QName): string => q.parts[q.parts.length - 1]!;
67
+ const display = (q: QName): string => (schemaOf(q).toLowerCase() === DEFAULT_SCHEMA ? nameOf(q) : `${schemaOf(q)}.${nameOf(q)}`);
68
+ const databaseOf = (q: QName): string | undefined => (schemaOf(q).toLowerCase() === DEFAULT_SCHEMA ? undefined : schemaOf(q));
69
+ const qualified = (q: QName): string => {
70
+ const db = databaseOf(q);
71
+ return db ? `${ident(db)}.${ident(nameOf(q))}` : ident(nameOf(q));
72
+ };
73
+
74
+ class Report {
75
+ readonly diagnostics: ImportDiagnostic[] = [];
76
+ finding(f: ReviewFinding): void {
77
+ this.diagnostics.push({
78
+ ruleId: f.ruleId,
79
+ severity: f.severity,
80
+ message: findingText(f),
81
+ ...(f.at ? { line: f.at.line, column: f.at.column } : {}),
82
+ entity: f.object,
83
+ });
84
+ }
85
+ note(ruleId: ReviewRuleId, severity: "warning" | "info", object: string, message: string, evidence: string[], at: Loc, column?: string, suggestion?: string): void {
86
+ this.finding({ ruleId, severity, object, ...(column ? { column } : {}), message, evidence, ...(suggestion ? { suggestion } : {}), at });
87
+ }
88
+ }
89
+
90
+ // --- defaults ------------------------------------------------------------------------
91
+
92
+ interface TranslatedDefault {
93
+ kind: "none" | "literal" | "function" | "sequence" | "untranslated";
94
+ expr?: string;
95
+ source: string;
96
+ }
97
+
98
+ /** Strip redundant outer parentheses: SQL Server stores `DEFAULT ((0))`. */
99
+ function unwrap(tokens: readonly Token[]): Token[] {
100
+ let t = [...tokens];
101
+ while (t.length >= 2 && t[0]!.kind === "punct" && t[0]!.value === "(" && t[t.length - 1]!.kind === "punct" && t[t.length - 1]!.value === ")") {
102
+ let depth = 0;
103
+ let wraps = true;
104
+ for (let i = 0; i < t.length; i++) {
105
+ if (t[i]!.value === "(" && t[i]!.kind === "punct") depth++;
106
+ if (t[i]!.value === ")" && t[i]!.kind === "punct") depth--;
107
+ if (depth === 0 && i < t.length - 1) {
108
+ wraps = false;
109
+ break;
110
+ }
111
+ }
112
+ if (!wraps) break;
113
+ t = t.slice(1, -1);
114
+ }
115
+ return t;
116
+ }
117
+
118
+ const NOW_FUNCTIONS = new Set(["sysdatetime", "sysdatetimeoffset", "sysutcdatetime", "getdate", "getutcdate", "current_timestamp"]);
119
+
120
+ function translateDefault(c: ConstraintAst | undefined, chType: string): TranslatedDefault {
121
+ if (!c || !c.expressionTokens) return { kind: "none", source: "" };
122
+ const source = oneLine(c.expression ?? "");
123
+ const t = unwrap(c.expressionTokens);
124
+ const words = t.map((x) => x.value.toUpperCase());
125
+ if (t.length === 1 && t[0]!.kind === "number") return { kind: "literal", expr: t[0]!.value, source };
126
+ if (t.length === 2 && t[0]!.kind === "punct" && (t[0]!.value === "-" || t[0]!.value === "+") && t[1]!.kind === "number") return { kind: "literal", expr: `${t[0]!.value === "-" ? "-" : ""}${t[1]!.value}`, source };
127
+ if (t.length === 1 && t[0]!.kind === "string") return { kind: "literal", expr: chString(t[0]!.value), source };
128
+ if (t.length === 1 && upper(t[0]) === "NULL") return { kind: "none", source };
129
+ if (words[0] === "NEXT" && words[1] === "VALUE" && words[2] === "FOR") return { kind: "sequence", source };
130
+ const call = t.length >= 3 && t[0]!.kind === "word" && t[1]!.value === "(" && t[t.length - 1]!.value === ")" ? t[0]!.value.toLowerCase() : undefined;
131
+ const noArgs = t.length === 3 || (t.length === 1 && t[0]!.kind === "word");
132
+ const fn = t.length === 1 && t[0]!.kind === "word" ? t[0]!.value.toLowerCase() : call;
133
+ if (fn && noArgs) {
134
+ if (NOW_FUNCTIONS.has(fn)) {
135
+ if (chType.startsWith("Date32")) return { kind: "function", expr: "today()", source };
136
+ const p = /^DateTime64\((\d)/.exec(chType)?.[1] ?? "3";
137
+ return { kind: "function", expr: `now64(${p})`, source };
138
+ }
139
+ if (fn === "newid" || fn === "newsequentialid") return { kind: "function", expr: "generateUUIDv4()", source };
140
+ }
141
+ return { kind: "untranslated", source };
142
+ }
143
+
144
+ // --- tables ----------------------------------------------------------------------------
145
+
146
+ interface TableState {
147
+ ast: TableAst;
148
+ display: string;
149
+ constraints: ConstraintAst[];
150
+ indexes: IndexAst[];
151
+ /** Description text from `sp_addextendedproperty`. */
152
+ comment?: string;
153
+ columnComments: Map<string, string>;
154
+ }
155
+
156
+ function columnLookup(ast: TableAst): Map<string, ColumnAst> {
157
+ return new Map(ast.columns.map((c) => [c.name.toLowerCase(), c]));
158
+ }
159
+
160
+ /** The column's declared casing, so a key written in another case still names it. */
161
+ function declared(lookup: Map<string, ColumnAst>, name: string): string {
162
+ return lookup.get(name.toLowerCase())?.name ?? name;
163
+ }
164
+
165
+ function keyText(c: ConstraintAst): string {
166
+ return `${c.kind === "primary-key" ? "PRIMARY KEY" : "UNIQUE"}${c.clustered === undefined ? "" : c.clustered ? " CLUSTERED" : " NONCLUSTERED"}${c.name ? ` ${c.name}` : ""}`;
167
+ }
168
+
169
+ function translateTable(state: TableState, report: Report, problems: string[]): ImportedObject | undefined {
170
+ const { ast } = state;
171
+ const object = state.display;
172
+ const lookup = columnLookup(ast);
173
+ const at = ast.loc;
174
+ const columnDefaults = new Map<string, ConstraintAst>();
175
+ const tableConstraints: ConstraintAst[] = [];
176
+ for (const c of state.constraints) {
177
+ if (c.kind === "default") {
178
+ const col = c.columns[0]?.name ?? c.onColumn;
179
+ if (col) columnDefaults.set(col.toLowerCase(), c);
180
+ } else tableConstraints.push(c);
181
+ }
182
+ const primary = tableConstraints.find((c) => c.kind === "primary-key");
183
+ const pkColumns = new Set((primary?.columns ?? []).map((k) => k.name.toLowerCase()));
184
+
185
+ const lines: string[] = [];
186
+ const columnTypes: Record<string, string> = {};
187
+ const header: string[] = [];
188
+ const maxLengths = new Map<string, number>();
189
+ let failed = false;
190
+
191
+ for (const col of ast.columns) {
192
+ if (col.computed) {
193
+ report.note(
194
+ "SQLRV006",
195
+ "warning",
196
+ object,
197
+ `Computed column ${col.name} is not carried.`,
198
+ [`AS ${oneLine(col.computed.expression)}${col.computed.persisted ? " PERSISTED" : ""}`, "the expression is T-SQL and is not translated"],
199
+ col.loc,
200
+ col.name,
201
+ "Add the column as MATERIALIZED or ALIAS with the expression rewritten in ClickHouse SQL, or compute it in the query.",
202
+ );
203
+ header.push(`-- computed column ${ident(col.name)} AS ${oneLine(col.computed.expression)} is not carried`);
204
+ continue;
205
+ }
206
+ const mapped = mapType(col.type ?? "", col.typeArgs);
207
+ if (!mapped.ok) {
208
+ problems.push(`${object}.${col.name} (line ${String(col.loc.line)}): ${mapped.reason}`);
209
+ failed = true;
210
+ continue;
211
+ }
212
+ if (mapped.note) report.note("SQLRV007", "warning", object, mapped.note.message, mapped.note.evidence, col.loc, col.name);
213
+ if (mapped.maxLength !== undefined) maxLengths.set(col.name, mapped.maxLength);
214
+
215
+ const def = translateDefault(columnDefaults.get(col.name.toLowerCase()), mapped.type);
216
+ if (def.kind === "sequence") {
217
+ report.note("SQLRV006", "warning", object, `The default of ${col.name} draws from a sequence, which ClickHouse does not have.`, [`DEFAULT ${def.source}`], col.loc, col.name, "Generate the value in the load, or use generateSerialID('name') in a DEFAULT where one counter per server is enough.");
218
+ } else if (def.kind === "untranslated") {
219
+ report.note("SQLRV008", "warning", object, `The default of ${col.name} is T-SQL and is not carried.`, [`DEFAULT ${def.source}`], col.loc, col.name, "Write the default in ClickHouse SQL.");
220
+ } else if (def.kind === "function") {
221
+ report.note("SQLRV008", "warning", object, `The default of ${col.name} is written as ${def.expr}.`, [`DEFAULT ${def.source}`, "the source function is evaluated in the server's local time or UTC by its name; the ClickHouse function follows the server time zone"], col.loc, col.name);
222
+ }
223
+ if (col.identity) {
224
+ report.note(
225
+ "SQLRV006",
226
+ "warning",
227
+ object,
228
+ `Identity column ${col.name} is a plain column here.`,
229
+ [`IDENTITY(${col.identity.seed}, ${col.identity.increment})`, "ClickHouse has no auto-increment column"],
230
+ col.loc,
231
+ col.name,
232
+ "Load the values from the source, or generate them with generateSerialID or a UUID.",
233
+ );
234
+ }
235
+ if (col.generated) {
236
+ report.note("SQLRV006", "warning", object, `Column ${col.name} is filled by the system in the source.`, [oneLine(col.generated), "ClickHouse does not maintain system-versioning columns"], col.loc, col.name, "Load the values, or give the column a DEFAULT now64().");
237
+ }
238
+ if (col.collate) {
239
+ report.note("SQLRV007", "warning", object, `Collation ${col.collate} on ${col.name} is not carried.`, ["ClickHouse compares String values as bytes; SQL Server collations are usually case-insensitive"], col.loc, col.name);
240
+ }
241
+ for (const extra of [...col.extras, ...(col.sparse ? ["SPARSE"] : []), ...(col.rowguid ? ["ROWGUIDCOL"] : [])]) {
242
+ report.note("SQLRV009", "warning", object, `Column option ${extra} on ${col.name} is not carried.`, [extra], col.loc, col.name);
243
+ }
244
+
245
+ // Nullability: a primary key column and an identity column are NOT NULL; the default for others is NULL.
246
+ const sourceNullable = col.nullable ?? (!pkColumns.has(col.name.toLowerCase()) && !col.identity);
247
+ const forcedNotNull = pkColumns.has(col.name.toLowerCase()) || !!col.identity;
248
+ const nullable = forcedNotNull ? false : sourceNullable;
249
+ const defaultText = def.expr ?? (def.kind === "untranslated" ? def.source : undefined);
250
+ const n = nullableReview({ object, column: col.name, type: mapped.type, sourceNullable: nullable, ...(defaultText !== undefined ? { defaultExpr: defaultText } : {}), at: col.loc });
251
+ if (n.finding) report.finding(n.finding);
252
+ columnTypes[col.name] = n.type;
253
+ const lc = lowCardinalityReview({ object, column: col.name, type: n.type, ...(mapped.maxLength !== undefined ? { maxLength: mapped.maxLength } : {}), at: col.loc });
254
+ if (lc) report.finding(lc);
255
+
256
+ const comment = state.columnComments.get(col.name.toLowerCase());
257
+ lines.push(` ${ident(col.name)} ${n.type}${def.expr !== undefined ? ` DEFAULT ${def.expr}` : ""}${comment ? ` COMMENT ${chString(comment)}` : ""}`);
258
+ }
259
+ if (failed) return undefined;
260
+ if (lines.length === 0) {
261
+ problems.push(`${object} (line ${String(at.line)}): the table has no column that can be carried`);
262
+ return undefined;
263
+ }
264
+
265
+ // Indexes: the clustered rowstore index, the clustered columnstore index, the rest.
266
+ const cci = state.indexes.find((i) => i.columnstore && i.clustered);
267
+ const explicitClustered = state.indexes.find((i) => !i.columnstore && i.clustered);
268
+ const clusteredConstraint = tableConstraints.find((c) => (c.kind === "primary-key" || c.kind === "unique") && c.clustered === true);
269
+ // A primary key is clustered unless the table has another clustered structure or says NONCLUSTERED.
270
+ const impliedClusteredPk = primary && primary.clustered === undefined && !cci && !explicitClustered && !clusteredConstraint ? primary : undefined;
271
+
272
+ const candidates: SortKeyCandidate[] = [];
273
+ if (cci && cci.order.length > 0) {
274
+ candidates.push({ kind: "explicit-order", name: cci.name, columns: cci.order.map((c) => declared(lookup, c)), source: `CREATE CLUSTERED COLUMNSTORE INDEX ${cci.name} ORDER` });
275
+ }
276
+ if (explicitClustered) {
277
+ candidates.push({
278
+ kind: "clustering-key",
279
+ name: explicitClustered.name,
280
+ columns: explicitClustered.columns.map((k) => declared(lookup, k.name)),
281
+ descending: explicitClustered.columns.filter((k) => k.descending).map((k) => declared(lookup, k.name)),
282
+ source: `CREATE ${explicitClustered.unique ? "UNIQUE " : ""}CLUSTERED INDEX ${explicitClustered.name}`,
283
+ });
284
+ }
285
+ const clusteringConstraint = clusteredConstraint ?? impliedClusteredPk;
286
+ if (clusteringConstraint) {
287
+ candidates.push({
288
+ kind: "clustering-key",
289
+ name: clusteringConstraint.name,
290
+ columns: clusteringConstraint.columns.map((k) => declared(lookup, k.name)),
291
+ descending: clusteringConstraint.columns.filter((k) => k.descending).map((k) => declared(lookup, k.name)),
292
+ source: `${keyText(clusteringConstraint)}${clusteringConstraint === impliedClusteredPk ? " (clustered because the table has no other clustered index)" : ""}`,
293
+ });
294
+ }
295
+ if (primary && primary !== clusteringConstraint) {
296
+ candidates.push({ kind: "primary-key", name: primary.name, columns: primary.columns.map((k) => declared(lookup, k.name)), descending: primary.columns.filter((k) => k.descending).map((k) => declared(lookup, k.name)), source: keyText(primary) });
297
+ }
298
+ const firstUnique = tableConstraints.find((c) => c.kind === "unique" && c !== clusteringConstraint) ?? undefined;
299
+ const uniqueIndex = state.indexes.find((i) => i.unique && !i.columnstore && !i.clustered);
300
+ if (firstUnique) candidates.push({ kind: "unique-key", name: firstUnique.name, columns: firstUnique.columns.map((k) => declared(lookup, k.name)), source: keyText(firstUnique) });
301
+ else if (uniqueIndex) candidates.push({ kind: "unique-key", name: uniqueIndex.name, columns: uniqueIndex.columns.map((k) => declared(lookup, k.name)), source: `CREATE UNIQUE INDEX ${uniqueIndex.name}` });
302
+
303
+ // A key that names a column this source did not carry (a computed column) cannot be used.
304
+ const usable = candidates.filter((c) => c.columns.every((n) => n in columnTypes));
305
+ const sortKey = sortKeyReview({ object, candidates: usable, columnTypes, at });
306
+ const unusable = candidates.filter((c) => !usable.includes(c));
307
+ const extraEvidence = [
308
+ ...unusable.map((c) => `passed over: ${c.source} (${c.columns.join(", ")}) names a column that is not carried`),
309
+ ...(cci && cci.order.length === 0 ? [`CREATE CLUSTERED COLUMNSTORE INDEX ${cci.name} has no ORDER clause, so it names no key`] : []),
310
+ ];
311
+ if (extraEvidence.length > 0) sortKey.finding = { ...sortKey.finding, evidence: [...sortKey.finding.evidence, ...extraEvidence] };
312
+ report.finding(sortKey.finding);
313
+
314
+ const uniqueFacts = primary ? primary.columns.map((k) => declared(lookup, k.name)) : undefined;
315
+ const engine = engineReview({
316
+ object,
317
+ mode: "append",
318
+ facts: [
319
+ ...(cci ? [`${cci.name} is a clustered columnstore index, which stores the table by column as MergeTree does`] : []),
320
+ ...(!cci ? ["the table is a rowstore table, read here as an append-only analytical table"] : []),
321
+ ],
322
+ ...(uniqueFacts ? { uniqueKey: uniqueFacts } : {}),
323
+ at,
324
+ });
325
+ report.finding(engine.finding);
326
+
327
+ // Constraints ClickHouse does not enforce.
328
+ const facts: ConstraintFact[] = [];
329
+ for (const c of tableConstraints) {
330
+ if (c.kind === "foreign-key") {
331
+ facts.push({ kind: "foreign-key", ...(c.name ? { name: c.name } : {}), columns: c.columns.map((k) => declared(lookup, k.name)), ...(c.references ? { references: { table: c.references.table.parts.join("."), columns: c.references.columns } } : {}), at: c.loc });
332
+ } else if (c.kind === "check") {
333
+ facts.push({ kind: "check", ...(c.name ? { name: c.name } : {}), ...(c.expression ? { expression: c.expression } : {}), at: c.loc });
334
+ } else if (c.kind === "unique") {
335
+ facts.push({ kind: "unique", ...(c.name ? { name: c.name } : {}), columns: c.columns.map((k) => declared(lookup, k.name)), at: c.loc });
336
+ }
337
+ }
338
+ for (const i of state.indexes) {
339
+ if (i.unique && !i.columnstore) facts.push({ kind: "unique", name: i.name, columns: i.columns.map((k) => declared(lookup, k.name)), at: i.loc });
340
+ }
341
+ const constraintOut = constraintReview({ object, constraints: facts });
342
+ for (const f of constraintOut.findings) report.finding(f);
343
+
344
+ // Storage options and indexes that nothing carries.
345
+ const dropped: string[] = [];
346
+ for (const o of ast.options) dropped.push(oneLine(o));
347
+ if (ast.period) dropped.push(`PERIOD FOR SYSTEM_TIME (${ast.period.start}, ${ast.period.end})`);
348
+ for (const c of tableConstraints) for (const o of c.kind === "foreign-key" ? [] : c.options) dropped.push(`${keyText(c)} ${oneLine(o)}`);
349
+ for (const i of state.indexes) for (const o of i.options) dropped.push(`${i.name} ${oneLine(o)}`);
350
+ if (dropped.length > 0) {
351
+ report.note(
352
+ "SQLRV009",
353
+ "warning",
354
+ object,
355
+ "Storage options of the source table are not carried.",
356
+ dropped,
357
+ at,
358
+ undefined,
359
+ "Map partitioning to PARTITION BY and compression to column CODECs after the first load, once you know how the table is queried.",
360
+ );
361
+ }
362
+ for (const i of state.indexes) {
363
+ if (i.columnstore && i.clustered) continue;
364
+ if (i.clustered) continue;
365
+ const what = i.columnstore ? "nonclustered columnstore index" : i.unique ? "unique index" : "nonclustered index";
366
+ report.note(
367
+ "SQLRV009",
368
+ "warning",
369
+ object,
370
+ `${i.name}, a ${what} on (${i.columns.map((k) => declared(lookup, k.name)).join(", ")}), is not carried.`,
371
+ [`CREATE ${i.unique ? "UNIQUE " : ""}${i.columnstore ? "COLUMNSTORE " : ""}INDEX ${i.name}${i.where ? ` WHERE ${oneLine(i.where)}` : ""}${i.include.length ? ` INCLUDE (${i.include.join(", ")})` : ""}`],
372
+ i.loc,
373
+ undefined,
374
+ i.columnstore ? "A MergeTree table is already stored by column." : "ClickHouse has no secondary index of this kind. A skip index (minmax, set, bloom_filter) on the column can prune granules.",
375
+ );
376
+ }
377
+
378
+ const engineText = engine.engine;
379
+ const extra = engine.extraColumns.map((c) => ` ${c.name} ${c.type}`);
380
+ const settings = Object.entries(sortKey.settings).map(([k, v]) => `${k} = ${v}`);
381
+ const ddl = [
382
+ ...constraintOut.comments,
383
+ ...header,
384
+ `CREATE TABLE ${qualified(ast.name)}`,
385
+ "(",
386
+ [...lines, ...extra].join(",\n"),
387
+ ")",
388
+ `ENGINE = ${engineText}`,
389
+ `ORDER BY ${sortKey.orderBy.length === 0 ? "tuple()" : sortKey.orderBy.length === 1 ? ident(sortKey.orderBy[0]!) : `(${sortKey.orderBy.map(ident).join(", ")})`}`,
390
+ ...(settings.length > 0 ? [`SETTINGS ${settings.join(", ")}`] : []),
391
+ ...(state.comment ? [`COMMENT ${chString(state.comment)}`] : []),
392
+ ].join("\n");
393
+ return { type: CLICKHOUSE_ENTITY_TYPES.table, ...(databaseOf(ast.name) ? { database: databaseOf(ast.name)! } : {}), name: nameOf(ast.name), ddl };
394
+ }
395
+
396
+ // --- views -----------------------------------------------------------------------------
397
+
398
+ const TSQL_ONLY = new Set(
399
+ "TOP ISNULL GETDATE GETUTCDATE SYSDATETIME SYSUTCDATETIME CONVERT LEN CHARINDEX DATEADD DATEDIFF DATEPART DATENAME NEWID STUFF STRING_AGG APPLY NOLOCK FORMAT OFFSET FETCH PIVOT UNPIVOT ROWLOCK READPAST UPDLOCK TABLESAMPLE OPENJSON OPENXML CROSS OUTER".split(" "),
400
+ );
401
+
402
+ function translateView(v: ViewAst, report: Report): ImportedObject {
403
+ const object = display(v.name);
404
+ const toks = v.bodyTokens;
405
+ const base = toks[0]?.start ?? 0;
406
+ let body = "";
407
+ let prevEnd = base;
408
+ let dropDot = false;
409
+ const found = new Set<string>();
410
+ toks.forEach((t, i) => {
411
+ const gap = v.body.slice(prevEnd - base, t.start - base);
412
+ prevEnd = t.end;
413
+ if (dropDot) {
414
+ // The dot after a dropped `dbo`.
415
+ dropDot = false;
416
+ return;
417
+ }
418
+ let text = t.text;
419
+ const next = toks[i + 1];
420
+ if (t.kind === "string") text = chString(t.value);
421
+ else if (t.kind === "word" || t.kind === "qident") {
422
+ if (t.kind === "qident") text = ident(t.value);
423
+ if (t.kind === "word" && TSQL_ONLY.has(t.value.toUpperCase())) found.add(t.value.toUpperCase());
424
+ // `dbo.Orders`: the default schema is dropped from names.
425
+ if (t.value.toLowerCase() === DEFAULT_SCHEMA && next && next.kind === "punct" && next.value === "." && next.start === t.end) {
426
+ dropDot = true;
427
+ body += gap;
428
+ return;
429
+ }
430
+ }
431
+ body += gap + text;
432
+ });
433
+ const evidence = ["the body is copied as written, with identifiers requoted and the dbo schema dropped"];
434
+ evidence.push(found.size > 0 ? `T-SQL constructs found by name: ${[...found].sort().join(", ")}` : "no T-SQL-only construct was found by name, but the server checks every function and type when the view is created");
435
+ if (v.columns.length > 0) evidence.push(`the column list (${v.columns.join(", ")}) is not carried; alias the columns in the SELECT`);
436
+ if (v.options.length > 0) evidence.push(`view options not carried: ${v.options.map(oneLine).join(", ")}`);
437
+ report.note("SQLRV010", "warning", object, "The view's query is not translated.", evidence, v.loc, undefined, "Rewrite the query in ClickHouse SQL. ClickHouse names are case-sensitive, and a T-SQL function, TOP or an unqualified join key may need a different form.");
438
+ const ddl = `CREATE VIEW ${qualified(v.name)} AS\n${body.trim()}`;
439
+ return { type: CLICKHOUSE_ENTITY_TYPES.view, ...(databaseOf(v.name) ? { database: databaseOf(v.name)! } : {}), name: nameOf(v.name), ddl };
440
+ }
441
+
442
+ // --- the file --------------------------------------------------------------------------
443
+
444
+ export function translateSqlServer(text: string, options: TranslateOptions = {}): Translation {
445
+ const report = new Report();
446
+ const problems: string[] = [];
447
+ const parsed = parseTsql(text);
448
+
449
+ const tables = new Map<string, TableState>();
450
+ const order: Array<TableState | ViewAst> = [];
451
+ const schemas = new Map<string, { name: string; loc: Loc }>();
452
+ const views: ViewAst[] = [];
453
+ const pendingIndexes: IndexAst[] = [];
454
+ const pendingConstraints: AlterConstraintAst[] = [];
455
+ const pendingProperties: Array<(typeof parsed.statements)[number] & { kind: "extended-property" }> = [];
456
+
457
+ for (const s of parsed.statements) {
458
+ switch (s.kind) {
459
+ case "table": {
460
+ const st: TableState = { ast: s, display: display(s.name), constraints: [...s.constraints], indexes: [...s.indexes], columnComments: new Map() };
461
+ tables.set(keyOf(s.name), st);
462
+ order.push(st);
463
+ break;
464
+ }
465
+ case "view":
466
+ views.push(s);
467
+ order.push(s);
468
+ break;
469
+ case "index":
470
+ pendingIndexes.push(s);
471
+ break;
472
+ case "alter-constraint":
473
+ pendingConstraints.push(s);
474
+ break;
475
+ case "schema":
476
+ schemas.set(s.name.toLowerCase(), { name: s.name, loc: s.loc });
477
+ break;
478
+ case "extended-property":
479
+ pendingProperties.push(s);
480
+ break;
481
+ case "refused":
482
+ problems.push(`${s.objectKind}${s.name ? ` ${s.name}` : ""} (line ${String(s.loc.line)}): ${s.reason}`);
483
+ break;
484
+ case "ignored":
485
+ report.note("SQLRV011", "info", s.text, `Not carried: ${s.reason}.`, [s.text], s.loc);
486
+ break;
487
+ }
488
+ }
489
+ for (const u of parsed.unread) problems.push(`${u.text} (line ${String(u.loc.line)}): ${u.reason}`);
490
+
491
+ for (const ix of pendingIndexes) {
492
+ const target = ix.table ? tables.get(keyOf(ix.table)) : undefined;
493
+ if (!target) {
494
+ const isView = ix.table && views.some((v) => keyOf(v.name) === keyOf(ix.table!));
495
+ problems.push(`index ${ix.name} (line ${String(ix.loc.line)}): ${isView ? "an index on a view; ClickHouse has no indexed views" : `the table ${ix.table ? ix.table.parts.join(".") : ""} is not created in this file`}`);
496
+ continue;
497
+ }
498
+ target.indexes.push(ix);
499
+ }
500
+ for (const a of pendingConstraints) {
501
+ const target = tables.get(keyOf(a.table));
502
+ if (!target) {
503
+ problems.push(`ALTER TABLE ${a.table.parts.join(".")} ADD CONSTRAINT${a.constraint.name ? ` ${a.constraint.name}` : ""} (line ${String(a.loc.line)}): the table is not created in this file`);
504
+ continue;
505
+ }
506
+ target.constraints.push(a.constraint);
507
+ }
508
+ for (const p of pendingProperties) {
509
+ const target = p.schema && p.table ? tables.get(`${p.schema}.${p.table}`.toLowerCase()) : undefined;
510
+ const isDescription = p.property.toLowerCase() === "description" || p.property.toLowerCase() === "ms_description";
511
+ if (target && isDescription && !p.level2 && p.level1Type?.toUpperCase() === "TABLE") {
512
+ if (p.column) {
513
+ if (target.ast.columns.some((c) => c.name.toLowerCase() === p.column!.toLowerCase())) target.columnComments.set(p.column.toLowerCase(), p.value);
514
+ else report.note("SQLRV011", "info", target.display, `The description of column ${p.column} names no column of the table.`, [p.value], p.loc);
515
+ } else target.comment = p.value;
516
+ } else {
517
+ report.note("SQLRV011", "info", p.table ? `${p.schema ?? ""}.${p.table}`.replace(/^\./, "") : "extended property", `Extended property ${p.property} is not carried.`, [`${p.property} = ${p.value}${p.level2 ? ` on ${p.level2.type} ${p.level2.name}` : ""}`, "only a Description on a table or column becomes a ClickHouse COMMENT"], p.loc);
518
+ }
519
+ }
520
+
521
+ // Databases: one per schema other than dbo, in the order the file first names them.
522
+ const databases = new Map<string, { name: string; explicit: boolean; loc: Loc }>();
523
+ for (const [lower, sc] of schemas) databases.set(lower, { name: sc.name, explicit: true, loc: sc.loc });
524
+ for (const e of order) {
525
+ const q = "ast" in e ? e.ast.name : e.name;
526
+ const db = databaseOf(q);
527
+ if (db && !databases.has(db.toLowerCase())) databases.set(db.toLowerCase(), { name: db, explicit: false, loc: q.loc });
528
+ }
529
+ const objects: ImportedObject[] = [];
530
+ for (const [lower, d] of databases) {
531
+ if (lower === DEFAULT_SCHEMA) continue;
532
+ if (!d.explicit) report.note("SQLRV011", "info", d.name, `Schema ${d.name} becomes the database ${d.name}.`, ["the file creates no schema of that name, but a table or view lives in it"], d.loc);
533
+ objects.push({ type: CLICKHOUSE_ENTITY_TYPES.database, name: d.name, ddl: `CREATE DATABASE ${ident(d.name)}` });
534
+ }
535
+
536
+ for (const e of order) {
537
+ if ("ast" in e) {
538
+ const o = translateTable(e, report, problems);
539
+ if (o) objects.push(o);
540
+ } else {
541
+ objects.push(translateView(e, report));
542
+ }
543
+ }
544
+
545
+ // Refusals: a failure, or listed when the caller skips what it cannot read.
546
+ const diagnostics = [...report.diagnostics];
547
+ if (options.skipUnreadable) {
548
+ for (const p of problems.splice(0)) diagnostics.push({ ruleId: "SQLRV011", severity: "warning", message: `Left out (skipUndeclarable): ${p}` });
549
+ }
550
+ return { objects, diagnostics: sortDiagnostics(diagnostics), problems };
551
+ }
552
+
553
+ function sortDiagnostics(d: ImportDiagnostic[]): ImportDiagnostic[] {
554
+ return d
555
+ .map((x, i) => [x, i] as const)
556
+ .sort(([a, i], [b, j]) => (a.line ?? Infinity) - (b.line ?? Infinity) || (a.column ?? 0) - (b.column ?? 0) || i - j)
557
+ .map(([x]) => x);
558
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { TYPE_FAMILIES } from "../generated/clickhouse";
3
+ import { numericParam, readType, typeTokens } from "../clickhouse/type-syntax";
4
+ import { mapType } from "./types";
5
+
6
+ /** [T-SQL name, arguments, ClickHouse type]: the maps that need no review. */
7
+ const MECHANICAL: Array<[string, Array<number | "max">, string]> = [
8
+ ["nvarchar", [50], "String"],
9
+ ["nvarchar", ["max"], "String"],
10
+ ["varchar", [8], "String"],
11
+ ["char", [2], "String"],
12
+ ["nchar", [2], "String"],
13
+ ["text", [], "String"],
14
+ ["ntext", [], "String"],
15
+ ["datetime2", [3], "DateTime64(3)"],
16
+ ["datetime2", [], "DateTime64(7)"],
17
+ ["bit", [], "Bool"],
18
+ ["uniqueidentifier", [], "UUID"],
19
+ ["decimal", [12, 4], "Decimal(12, 4)"],
20
+ ["numeric", [10, 2], "Decimal(10, 2)"],
21
+ ["decimal", [], "Decimal(18, 0)"],
22
+ ["money", [], "Decimal(19, 4)"],
23
+ ["smallmoney", [], "Decimal(10, 4)"],
24
+ ["int", [], "Int32"],
25
+ ["bigint", [], "Int64"],
26
+ ["smallint", [], "Int16"],
27
+ ["tinyint", [], "UInt8"],
28
+ ["real", [], "Float32"],
29
+ ["float", [], "Float64"],
30
+ ["float", [24], "Float32"],
31
+ ["float", [25], "Float64"],
32
+ ["date", [], "Date32"],
33
+ ["time", [3], "Time64(3)"],
34
+ ["datetime", [], "DateTime64(3)"],
35
+ ["smalldatetime", [], "DateTime64(0)"],
36
+ ["varbinary", [16], "String"],
37
+ ["image", [], "String"],
38
+ ["json", [], "JSON"],
39
+ ["sysname", [], "String"],
40
+ ];
41
+
42
+ describe("mapType", () => {
43
+ test.each(MECHANICAL)("%s(%j) is %s with no note", (name, args, expected) => {
44
+ const m = mapType(name, args);
45
+ expect(m).toMatchObject({ ok: true, type: expected });
46
+ expect(m.ok && m.note).toBeFalsy();
47
+ });
48
+
49
+ test("a character type keeps its declared length for the review rules", () => {
50
+ expect(mapType("nvarchar", [20])).toMatchObject({ maxLength: 20 });
51
+ expect(mapType("nvarchar", ["max"])).not.toHaveProperty("maxLength");
52
+ });
53
+
54
+ test.each([
55
+ ["datetimeoffset", [7], "DateTime64(7, 'UTC')", "offset"],
56
+ ["xml", [], "String", "XML"],
57
+ ["rowversion", [], "String", "counter"],
58
+ ["timestamp", [], "String", "counter"],
59
+ ["hierarchyid", [], "String", "hierarchy"],
60
+ ["geography", [], "String", "spatial"],
61
+ ["geometry", [], "String", "spatial"],
62
+ ["vector", [3], "Array(Float32)", "3 dimensions"],
63
+ ] as const)("%s is a judgment, with a note", (name, args, expected, evidence) => {
64
+ const m = mapType(name, [...args]);
65
+ expect(m).toMatchObject({ ok: true, type: expected });
66
+ if (!m.ok) throw new Error("unreachable");
67
+ expect(m.note?.message.length).toBeGreaterThan(0);
68
+ expect(JSON.stringify(m.note).toLowerCase()).toContain(evidence.toLowerCase());
69
+ });
70
+
71
+ test.each(["sql_variant", "cursor", "table", "mytype"])("%s is refused with a reason", (name) => {
72
+ const m = mapType(name, []);
73
+ expect(m.ok).toBe(false);
74
+ if (m.ok) throw new Error("unreachable");
75
+ expect(m.reason).toContain(name);
76
+ });
77
+ });
78
+
79
+ describe("every target type is in the pinned ClickHouse catalog", () => {
80
+ const all = [...MECHANICAL.map(([n, a]) => mapType(n, a)), mapType("datetimeoffset", [7]), mapType("vector", [3]), mapType("xml", [])];
81
+
82
+ test("each type's family exists in TYPE_FAMILIES, and its precision is in range", () => {
83
+ for (const m of all) {
84
+ if (!m.ok) throw new Error("unreachable");
85
+ const t = readType(typeTokens(m.type)!)!;
86
+ const family = t.words.map((w) => w.text).join(" ");
87
+ expect(family in TYPE_FAMILIES, `${m.type}: ${family}`).toBe(true);
88
+ const params = (t.params ?? []).map(numericParam);
89
+ if (family === "DateTime64" || family === "Time64") expect(params[0]).toBeGreaterThanOrEqual(0), expect(params[0]).toBeLessThanOrEqual(9);
90
+ if (family === "Decimal") {
91
+ expect(params[0]).toBeGreaterThanOrEqual(1);
92
+ expect(params[0]).toBeLessThanOrEqual(76);
93
+ expect(params[1]).toBeLessThanOrEqual(params[0]!);
94
+ }
95
+ }
96
+ });
97
+
98
+ test("the largest SQL Server scale and precision fit: datetime2(7), decimal(38, s)", () => {
99
+ expect(mapType("datetime2", [7])).toMatchObject({ type: "DateTime64(7)" });
100
+ expect(mapType("decimal", [38, 38])).toMatchObject({ type: "Decimal(38, 38)" });
101
+ });
102
+ });