@intentius/chant-lexicon-sql 0.124.45 → 0.124.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/dist/change-set.d.ts +1 -1
  2. package/dist/change-set.d.ts.map +1 -1
  3. package/dist/change-set.js +2 -1
  4. package/dist/clickhouse/apply/statements.d.ts +9 -0
  5. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  6. package/dist/clickhouse/apply/statements.js +24 -0
  7. package/dist/clickhouse/import/live-export.d.ts +7 -3
  8. package/dist/clickhouse/import/live-export.d.ts.map +1 -1
  9. package/dist/clickhouse/import/live-export.js +21 -5
  10. package/dist/clickhouse/live/named-collections.d.ts +27 -0
  11. package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
  12. package/dist/clickhouse/live/named-collections.js +53 -0
  13. package/dist/clickhouse/live/undeclarable.d.ts +15 -3
  14. package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
  15. package/dist/clickhouse/live/undeclarable.js +18 -4
  16. package/dist/clickhouse/named-collections.d.ts +83 -0
  17. package/dist/clickhouse/named-collections.d.ts.map +1 -0
  18. package/dist/clickhouse/named-collections.js +203 -0
  19. package/dist/clickhouse/parser.d.ts.map +1 -1
  20. package/dist/clickhouse/parser.js +1 -1
  21. package/dist/clickhouse/plan/commands.d.ts +2 -1
  22. package/dist/clickhouse/plan/commands.d.ts.map +1 -1
  23. package/dist/clickhouse/plan/commands.js +17 -2
  24. package/dist/clickhouse/plan/diff.d.ts +15 -0
  25. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  26. package/dist/clickhouse/plan/diff.js +43 -1
  27. package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
  28. package/dist/clickhouse/plan/disruption.js +5 -0
  29. package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
  30. package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
  31. package/dist/clickhouse/plan/report.d.ts.map +1 -1
  32. package/dist/clickhouse/plan/report.js +6 -1
  33. package/dist/clickhouse/plan/rules.d.ts +5 -1
  34. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  35. package/dist/clickhouse/plan/rules.js +7 -3
  36. package/dist/clickhouse/plan/stream.d.ts +40 -0
  37. package/dist/clickhouse/plan/stream.d.ts.map +1 -0
  38. package/dist/clickhouse/plan/stream.js +107 -0
  39. package/dist/clickhouse/rebuild/observe.d.ts +3 -1
  40. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
  41. package/dist/clickhouse/rebuild/observe.js +14 -1
  42. package/dist/clickhouse/review-catalog.d.ts +213 -0
  43. package/dist/clickhouse/review-catalog.d.ts.map +1 -0
  44. package/dist/clickhouse/review-catalog.js +330 -0
  45. package/dist/codegen/classifier-page.d.ts.map +1 -1
  46. package/dist/codegen/classifier-page.js +21 -4
  47. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  48. package/dist/codegen/clickhouse-module.js +3 -0
  49. package/dist/codegen/coverage-page.d.ts.map +1 -1
  50. package/dist/codegen/coverage-page.js +5 -3
  51. package/dist/codegen/docs.js +1 -1
  52. package/dist/codegen/postgres-classifier-page.js +1 -1
  53. package/dist/codegen/registry.d.ts.map +1 -1
  54. package/dist/codegen/registry.js +5 -0
  55. package/dist/composites/catalog.d.ts.map +1 -1
  56. package/dist/composites/catalog.js +100 -0
  57. package/dist/composites/clickhouse.d.ts +2 -0
  58. package/dist/composites/clickhouse.d.ts.map +1 -1
  59. package/dist/composites/clickhouse.js +2 -0
  60. package/dist/composites/otel-tables.d.ts +100 -0
  61. package/dist/composites/otel-tables.d.ts.map +1 -0
  62. package/dist/composites/otel-tables.js +282 -0
  63. package/dist/composites/stream-ingest.d.ts +63 -0
  64. package/dist/composites/stream-ingest.d.ts.map +1 -0
  65. package/dist/composites/stream-ingest.js +39 -0
  66. package/dist/coverage/clickhouse.json +1 -1
  67. package/dist/coverage/postgres.json +7 -7
  68. package/dist/coverage.d.ts +16 -6
  69. package/dist/coverage.d.ts.map +1 -1
  70. package/dist/coverage.js +10 -5
  71. package/dist/drizzle-command.d.ts +17 -0
  72. package/dist/drizzle-command.d.ts.map +1 -0
  73. package/dist/drizzle-command.js +91 -0
  74. package/dist/drizzle.d.ts +70 -0
  75. package/dist/drizzle.d.ts.map +1 -0
  76. package/dist/drizzle.js +684 -0
  77. package/dist/files/clickhouse.d.ts.map +1 -1
  78. package/dist/files/clickhouse.js +8 -1
  79. package/dist/files/common.d.ts +13 -0
  80. package/dist/files/common.d.ts.map +1 -1
  81. package/dist/files/common.js +5 -0
  82. package/dist/files/postgres.js +12 -2
  83. package/dist/generated/clickhouse.d.ts +2 -0
  84. package/dist/generated/clickhouse.d.ts.map +1 -1
  85. package/dist/generated/clickhouse.js +2 -0
  86. package/dist/import-parser.d.ts +21 -1
  87. package/dist/import-parser.d.ts.map +1 -1
  88. package/dist/import-parser.js +79 -5
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/integrity.json +6 -5
  93. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  94. package/dist/lint/post-synth/audit-entries.js +1 -0
  95. package/dist/lint/post-synth/index.d.ts.map +1 -1
  96. package/dist/lint/post-synth/index.js +2 -0
  97. package/dist/lint/post-synth/sqlch128.d.ts +18 -0
  98. package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
  99. package/dist/lint/post-synth/sqlch128.js +222 -0
  100. package/dist/lint/rules/postgres-templates.js +2 -2
  101. package/dist/lsp/postgres-template.d.ts.map +1 -1
  102. package/dist/lsp/postgres-template.js +11 -0
  103. package/dist/manifest.json +1 -1
  104. package/dist/mcp/postgres.js +1 -1
  105. package/dist/meta.json +25 -0
  106. package/dist/migrate/from-postgres/detect.d.ts +4 -0
  107. package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
  108. package/dist/migrate/from-postgres/detect.js +7 -0
  109. package/dist/migrate/from-postgres/emit.d.ts +26 -0
  110. package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
  111. package/dist/migrate/from-postgres/emit.js +86 -0
  112. package/dist/migrate/from-postgres/index.d.ts +51 -0
  113. package/dist/migrate/from-postgres/index.d.ts.map +1 -0
  114. package/dist/migrate/from-postgres/index.js +115 -0
  115. package/dist/migrate/from-postgres/translate.d.ts +73 -0
  116. package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
  117. package/dist/migrate/from-postgres/translate.js +261 -0
  118. package/dist/migrate/from-postgres/types.d.ts +51 -0
  119. package/dist/migrate/from-postgres/types.d.ts.map +1 -0
  120. package/dist/migrate/from-postgres/types.js +211 -0
  121. package/dist/migration-replay.d.ts +73 -0
  122. package/dist/migration-replay.d.ts.map +1 -0
  123. package/dist/migration-replay.js +294 -0
  124. package/dist/okf/index.md +6 -0
  125. package/dist/okf/rules/SQLCH128.md +11 -0
  126. package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
  127. package/dist/okf/types/PostgresForeignServer.md +9 -0
  128. package/dist/okf/types/PostgresOperator.md +9 -0
  129. package/dist/okf/types/PostgresPublication.md +9 -0
  130. package/dist/okf/types/PostgresStatistics.md +9 -0
  131. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  132. package/dist/op/activities/postgres-migration.js +4 -0
  133. package/dist/op/activity-contracts.d.ts +20 -0
  134. package/dist/op/activity-contracts.d.ts.map +1 -1
  135. package/dist/op/activity-contracts.js +2 -0
  136. package/dist/plugin.d.ts.map +1 -1
  137. package/dist/plugin.js +36 -2
  138. package/dist/postgres/apply/apply.js +8 -1
  139. package/dist/postgres/apply/statements.d.ts.map +1 -1
  140. package/dist/postgres/apply/statements.js +87 -2
  141. package/dist/postgres/constraint-names.d.ts +30 -0
  142. package/dist/postgres/constraint-names.d.ts.map +1 -0
  143. package/dist/postgres/constraint-names.js +72 -0
  144. package/dist/postgres/entities.d.ts +110 -1
  145. package/dist/postgres/entities.d.ts.map +1 -1
  146. package/dist/postgres/entities.js +146 -0
  147. package/dist/postgres/entity-types.d.ts +5 -0
  148. package/dist/postgres/entity-types.d.ts.map +1 -1
  149. package/dist/postgres/entity-types.js +5 -0
  150. package/dist/postgres/import/generator.d.ts.map +1 -1
  151. package/dist/postgres/import/generator.js +50 -2
  152. package/dist/postgres/import/live-export.d.ts.map +1 -1
  153. package/dist/postgres/import/live-export.js +2 -0
  154. package/dist/postgres/live/catalog-kinds.d.ts +14 -0
  155. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
  156. package/dist/postgres/live/catalog-kinds.js +206 -0
  157. package/dist/postgres/live/catalog.d.ts +2 -0
  158. package/dist/postgres/live/catalog.d.ts.map +1 -1
  159. package/dist/postgres/live/catalog.js +3 -1
  160. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  161. package/dist/postgres/live/describe-resources.js +10 -2
  162. package/dist/postgres/migrate/backfill.d.ts +2 -0
  163. package/dist/postgres/migrate/backfill.d.ts.map +1 -1
  164. package/dist/postgres/migrate/backfill.js +7 -1
  165. package/dist/postgres/migrate/carry.d.ts +45 -1
  166. package/dist/postgres/migrate/carry.d.ts.map +1 -1
  167. package/dist/postgres/migrate/carry.js +72 -8
  168. package/dist/postgres/migrate/observe.d.ts +28 -4
  169. package/dist/postgres/migrate/observe.d.ts.map +1 -1
  170. package/dist/postgres/migrate/observe.js +41 -12
  171. package/dist/postgres/migrate/op.d.ts +26 -1
  172. package/dist/postgres/migrate/op.d.ts.map +1 -1
  173. package/dist/postgres/migrate/op.js +3 -1
  174. package/dist/postgres/migrate/steps.d.ts +35 -2
  175. package/dist/postgres/migrate/steps.d.ts.map +1 -1
  176. package/dist/postgres/migrate/steps.js +158 -12
  177. package/dist/postgres/parser.d.ts +72 -1
  178. package/dist/postgres/parser.d.ts.map +1 -1
  179. package/dist/postgres/parser.js +194 -2
  180. package/dist/postgres/plan/deep.d.ts.map +1 -1
  181. package/dist/postgres/plan/deep.js +29 -1
  182. package/dist/postgres/plan/diff.d.ts.map +1 -1
  183. package/dist/postgres/plan/diff.js +39 -1
  184. package/dist/postgres/plan/normalize.d.ts +17 -2
  185. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  186. package/dist/postgres/plan/normalize.js +109 -1
  187. package/dist/postgres/plan/rules.d.ts +1 -0
  188. package/dist/postgres/plan/rules.d.ts.map +1 -1
  189. package/dist/postgres/plan/rules.js +3 -2
  190. package/dist/postgres/plan/schema.js +1 -1
  191. package/dist/postgres/secret-options.d.ts +10 -0
  192. package/dist/postgres/secret-options.d.ts.map +1 -0
  193. package/dist/postgres/secret-options.js +9 -0
  194. package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
  195. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  196. package/dist/postgres/testing/roundtrip-schema.js +13 -1
  197. package/dist/postgres.d.ts +1 -1
  198. package/dist/postgres.d.ts.map +1 -1
  199. package/dist/postgres.js +1 -1
  200. package/dist/rules/audit-entries.ts +1 -0
  201. package/dist/rules/postgres-templates.ts +2 -2
  202. package/dist/rules/sqlch128.ts +230 -0
  203. package/dist/serializer.d.ts +5 -0
  204. package/dist/serializer.d.ts.map +1 -1
  205. package/dist/serializer.js +13 -2
  206. package/dist/sql-types-command.d.ts +13 -0
  207. package/dist/sql-types-command.d.ts.map +1 -1
  208. package/dist/sql-types-command.js +31 -16
  209. package/dist/sql-types.d.ts +22 -0
  210. package/dist/sql-types.d.ts.map +1 -1
  211. package/dist/sql-types.js +10 -6
  212. package/dist/sqlserver/lexer.d.ts +37 -0
  213. package/dist/sqlserver/lexer.d.ts.map +1 -0
  214. package/dist/sqlserver/lexer.js +254 -0
  215. package/dist/sqlserver/parse.d.ts +185 -0
  216. package/dist/sqlserver/parse.d.ts.map +1 -0
  217. package/dist/sqlserver/parse.js +767 -0
  218. package/dist/sqlserver/translate.d.ts +34 -0
  219. package/dist/sqlserver/translate.d.ts.map +1 -0
  220. package/dist/sqlserver/translate.js +495 -0
  221. package/dist/sqlserver/types.d.ts +44 -0
  222. package/dist/sqlserver/types.d.ts.map +1 -0
  223. package/dist/sqlserver/types.js +133 -0
  224. package/dist/sqlserver/validate.d.ts +31 -0
  225. package/dist/sqlserver/validate.d.ts.map +1 -0
  226. package/dist/sqlserver/validate.js +67 -0
  227. package/dist/validate.d.ts.map +1 -1
  228. package/dist/validate.js +5 -0
  229. package/package.json +2 -2
  230. package/src/change-set.ts +2 -1
  231. package/src/clickhouse/apply/statements.ts +25 -0
  232. package/src/clickhouse/import/live-export.ts +20 -5
  233. package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
  234. package/src/clickhouse/live/named-collections.ts +61 -0
  235. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
  236. package/src/clickhouse/live/undeclarable.ts +20 -4
  237. package/src/clickhouse/named-collections.test.ts +187 -0
  238. package/src/clickhouse/named-collections.ts +224 -0
  239. package/src/clickhouse/parser.ts +2 -1
  240. package/src/clickhouse/plan/commands.ts +17 -2
  241. package/src/clickhouse/plan/diff.ts +57 -1
  242. package/src/clickhouse/plan/disruption.ts +5 -0
  243. package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
  244. package/src/clickhouse/plan/report.ts +6 -1
  245. package/src/clickhouse/plan/rules.ts +14 -4
  246. package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
  247. package/src/clickhouse/plan/stream.test.ts +237 -0
  248. package/src/clickhouse/plan/stream.ts +111 -0
  249. package/src/clickhouse/rebuild/observe.ts +15 -1
  250. package/src/clickhouse/review-catalog.test.ts +181 -0
  251. package/src/clickhouse/review-catalog.ts +489 -0
  252. package/src/codegen/classifier-page.ts +21 -4
  253. package/src/codegen/clickhouse-module.ts +3 -0
  254. package/src/codegen/coverage-page.ts +5 -3
  255. package/src/codegen/docs.ts +1 -1
  256. package/src/codegen/postgres-classifier-page.ts +1 -1
  257. package/src/codegen/registry.ts +5 -0
  258. package/src/composites/catalog.test.ts +6 -1
  259. package/src/composites/catalog.ts +100 -0
  260. package/src/composites/clickhouse.ts +2 -0
  261. package/src/composites/composites.test.ts +1 -1
  262. package/src/composites/otel-tables.test.ts +118 -0
  263. package/src/composites/otel-tables.ts +339 -0
  264. package/src/composites/stream-ingest.test.ts +114 -0
  265. package/src/composites/stream-ingest.ts +82 -0
  266. package/src/core/undeclarable.test.ts +6 -5
  267. package/src/coverage/clickhouse.json +1 -1
  268. package/src/coverage/postgres.json +7 -7
  269. package/src/coverage.test.ts +27 -2
  270. package/src/coverage.ts +18 -7
  271. package/src/drizzle-command.ts +94 -0
  272. package/src/drizzle-kit.e2e.test.ts +89 -0
  273. package/src/drizzle-types.e2e.test.ts +67 -0
  274. package/src/drizzle.test.ts +358 -0
  275. package/src/drizzle.ts +737 -0
  276. package/src/files/clickhouse.ts +8 -1
  277. package/src/files/common.ts +14 -1
  278. package/src/files/postgres.ts +12 -2
  279. package/src/generated/clickhouse.ts +3 -0
  280. package/src/generated/lexicon-sql.json +25 -0
  281. package/src/import-parser.sqlserver.test.ts +92 -0
  282. package/src/import-parser.ts +79 -5
  283. package/src/index.ts +1 -1
  284. package/src/lint/post-synth/audit-entries.ts +1 -0
  285. package/src/lint/post-synth/index.ts +2 -0
  286. package/src/lint/post-synth/sqlch128.test.ts +107 -0
  287. package/src/lint/post-synth/sqlch128.ts +230 -0
  288. package/src/lint/rules/postgres-templates.ts +2 -2
  289. package/src/lsp/postgres-template.ts +11 -0
  290. package/src/mcp/postgres.ts +1 -1
  291. package/src/migrate/from-postgres/cli.test.ts +165 -0
  292. package/src/migrate/from-postgres/detect.ts +7 -0
  293. package/src/migrate/from-postgres/emit.ts +96 -0
  294. package/src/migrate/from-postgres/example.test.ts +205 -0
  295. package/src/migrate/from-postgres/index.ts +122 -0
  296. package/src/migrate/from-postgres/translate.test.ts +242 -0
  297. package/src/migrate/from-postgres/translate.ts +359 -0
  298. package/src/migrate/from-postgres/types.test.ts +116 -0
  299. package/src/migrate/from-postgres/types.ts +236 -0
  300. package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
  301. package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
  302. package/src/migration-replay.e2e.test.ts +155 -0
  303. package/src/migration-replay.test.ts +173 -0
  304. package/src/migration-replay.ts +289 -0
  305. package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
  306. package/src/op/activities/postgres-migration.ts +4 -0
  307. package/src/op/activity-contracts.ts +2 -0
  308. package/src/plugin.ts +37 -2
  309. package/src/postgres/apply/apply.ts +9 -1
  310. package/src/postgres/apply/statements.ts +74 -1
  311. package/src/postgres/constraint-names.ts +71 -0
  312. package/src/postgres/database-kinds.test.ts +384 -0
  313. package/src/postgres/entities.ts +266 -1
  314. package/src/postgres/entity-types.ts +5 -0
  315. package/src/postgres/import/generator.ts +42 -2
  316. package/src/postgres/import/live-export.ts +1 -0
  317. package/src/postgres/live/catalog-kinds.ts +223 -0
  318. package/src/postgres/live/catalog.ts +6 -1
  319. package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
  320. package/src/postgres/live/describe-resources.ts +11 -2
  321. package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
  322. package/src/postgres/migrate/backfill.ts +13 -1
  323. package/src/postgres/migrate/carry.ts +103 -8
  324. package/src/postgres/migrate/followups.e2e.test.ts +218 -0
  325. package/src/postgres/migrate/followups.test.ts +199 -0
  326. package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
  327. package/src/postgres/migrate/observe.ts +53 -14
  328. package/src/postgres/migrate/op.ts +28 -1
  329. package/src/postgres/migrate/partition.e2e.test.ts +129 -0
  330. package/src/postgres/migrate/partition.test.ts +206 -0
  331. package/src/postgres/migrate/steps.ts +157 -12
  332. package/src/postgres/parser.ts +234 -2
  333. package/src/postgres/plan/deep.ts +28 -1
  334. package/src/postgres/plan/diff.ts +36 -1
  335. package/src/postgres/plan/normalize.ts +122 -1
  336. package/src/postgres/plan/rules.ts +10 -3
  337. package/src/postgres/plan/schema.ts +1 -1
  338. package/src/postgres/secret-options.ts +10 -0
  339. package/src/postgres/testing/roundtrip-schema.ts +13 -1
  340. package/src/postgres.ts +16 -0
  341. package/src/serializer.ts +13 -2
  342. package/src/skills/chant-sql-postgres-migration.md +3 -3
  343. package/src/skills/chant-sql-postgres.md +6 -1
  344. package/src/skills/chant-sql.md +2 -1
  345. package/src/sql-types-command.ts +38 -16
  346. package/src/sql-types.ts +11 -6
  347. package/src/sqlserver/fixture.test.ts +186 -0
  348. package/src/sqlserver/import-cli.test.ts +61 -0
  349. package/src/sqlserver/lexer.ts +260 -0
  350. package/src/sqlserver/parse.ts +865 -0
  351. package/src/sqlserver/translate.test.ts +396 -0
  352. package/src/sqlserver/translate.ts +558 -0
  353. package/src/sqlserver/types.test.ts +102 -0
  354. package/src/sqlserver/types.ts +154 -0
  355. package/src/sqlserver/validate.e2e.test.ts +32 -0
  356. package/src/sqlserver/validate.test.ts +49 -0
  357. package/src/sqlserver/validate.ts +76 -0
  358. package/src/validate.ts +5 -0
  359. package/src/workspace-kinds.test.ts +11 -0
  360. package/workspace-kinds.json +100 -2
@@ -0,0 +1,865 @@
1
+ /**
2
+ * T-SQL DDL into a syntax tree: `CREATE TABLE`, `CREATE [UNIQUE] [CLUSTERED |
3
+ * NONCLUSTERED] INDEX`, `CREATE [CLUSTERED | NONCLUSTERED] COLUMNSTORE
4
+ * INDEX ... [ORDER (...)]`, `CREATE VIEW`, `CREATE SCHEMA`, `ALTER TABLE ...
5
+ * ADD CONSTRAINT`, and `sp_addextendedproperty` descriptions.
6
+ *
7
+ * Grammar sources (Microsoft Learn, Transact-SQL reference):
8
+ * - CREATE TABLE: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql
9
+ * - CREATE INDEX: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-index-transact-sql
10
+ * - CREATE COLUMNSTORE INDEX (the `ORDER (column [, ...n])` clause, which the
11
+ * SQL Server grammar places before or after `WITH`):
12
+ * https://learn.microsoft.com/en-us/sql/t-sql/statements/create-columnstore-index-transact-sql
13
+ *
14
+ * Every statement that is not one of these comes back as `refused` (a kind of
15
+ * object this source cannot read, named: `trigger [dbo].[trg_audit]`) or
16
+ * `ignored` (a session setting or a `DROP`, which declares nothing). Nothing
17
+ * is dropped without being returned.
18
+ */
19
+
20
+ import { batches, tokenize, upper, sourceOf, type Token } from "./lexer";
21
+
22
+ export interface Loc {
23
+ line: number;
24
+ column: number;
25
+ }
26
+
27
+ /** A multi-part name, unquoted: `[Sales].[Orders]` is `["Sales", "Orders"]`. */
28
+ export interface QName {
29
+ parts: string[];
30
+ loc: Loc;
31
+ }
32
+
33
+ export interface KeyColumn {
34
+ name: string;
35
+ descending: boolean;
36
+ }
37
+
38
+ export type ConstraintKind = "primary-key" | "unique" | "foreign-key" | "check" | "default";
39
+
40
+ export interface ConstraintAst {
41
+ kind: ConstraintKind;
42
+ name?: string;
43
+ /** `CLUSTERED` or `NONCLUSTERED` when written. */
44
+ clustered?: boolean;
45
+ columns: KeyColumn[];
46
+ references?: { table: QName; columns: string[] };
47
+ /** A check's or a default's expression, as written (outer parentheses kept). */
48
+ expression?: string;
49
+ expressionTokens?: Token[];
50
+ /** The column a column-level constraint was written on; the default's column. */
51
+ onColumn?: string;
52
+ /** `WITH (...)` or `ON ...` text after the key, which the translation names but does not carry. */
53
+ options: string[];
54
+ loc: Loc;
55
+ }
56
+
57
+ export interface ColumnAst {
58
+ name: string;
59
+ loc: Loc;
60
+ /** Lower-cased type name; `sysname` and alias types appear as written. */
61
+ type?: string;
62
+ /** Qualified type (`[dbo].[Flag]`) when the name has a schema. */
63
+ typeSchema?: string;
64
+ /** `max` is `"max"`. */
65
+ typeArgs: Array<number | "max">;
66
+ nullable?: boolean;
67
+ identity?: { seed: string; increment: string };
68
+ /** `AS (expr) [PERSISTED]` and no type. */
69
+ computed?: { expression: string; persisted: boolean };
70
+ /** `GENERATED ALWAYS AS ROW START | END | TRANSACTION_ID_* ...` as written. */
71
+ generated?: string;
72
+ collate?: string;
73
+ rowguid: boolean;
74
+ sparse: boolean;
75
+ /** Other column options carried as text and named in the review: `MASKED WITH`, `ENCRYPTED WITH`, `FILESTREAM`, `NOT FOR REPLICATION`. */
76
+ extras: string[];
77
+ }
78
+
79
+ export interface TableAst {
80
+ kind: "table";
81
+ name: QName;
82
+ columns: ColumnAst[];
83
+ constraints: ConstraintAst[];
84
+ /** `INDEX name [UNIQUE] [CLUSTERED|NONCLUSTERED] (cols)` inside the table. */
85
+ indexes: IndexAst[];
86
+ /** `PERIOD FOR SYSTEM_TIME (a, b)`. */
87
+ period?: { start: string; end: string };
88
+ /** Text of each clause after the column list: `WITH (...)`, `ON [PRIMARY]`, `TEXTIMAGE_ON ...`. */
89
+ options: string[];
90
+ /** `AS FileTable` / `AS NODE` / `AS EDGE`. */
91
+ asKind?: string;
92
+ loc: Loc;
93
+ }
94
+
95
+ export interface IndexAst {
96
+ kind: "index";
97
+ name: string;
98
+ /** Absent for an index written inside `CREATE TABLE`. */
99
+ table?: QName;
100
+ unique: boolean;
101
+ clustered: boolean;
102
+ columnstore: boolean;
103
+ columns: KeyColumn[];
104
+ include: string[];
105
+ /** The columnstore `ORDER (...)` columns. */
106
+ order: string[];
107
+ where?: string;
108
+ options: string[];
109
+ loc: Loc;
110
+ }
111
+
112
+ export interface ViewAst {
113
+ kind: "view";
114
+ name: QName;
115
+ columns: string[];
116
+ /** `WITH SCHEMABINDING, ...` */
117
+ options: string[];
118
+ /** The SELECT, as written, without the trailing `;`. */
119
+ body: string;
120
+ bodyTokens: Token[];
121
+ loc: Loc;
122
+ }
123
+
124
+ export interface SchemaAst {
125
+ kind: "schema";
126
+ name: string;
127
+ loc: Loc;
128
+ }
129
+
130
+ export interface AlterConstraintAst {
131
+ kind: "alter-constraint";
132
+ table: QName;
133
+ constraint: ConstraintAst;
134
+ loc: Loc;
135
+ }
136
+
137
+ export interface ExtendedPropertyAst {
138
+ kind: "extended-property";
139
+ /** `description` or the property's name. */
140
+ property: string;
141
+ value: string;
142
+ schema?: string;
143
+ table?: string;
144
+ column?: string;
145
+ /** `INDEX`, `CONSTRAINT`, `VIEW`, ... when level 2 is not a column. */
146
+ level2?: { type: string; name: string };
147
+ level1Type?: string;
148
+ loc: Loc;
149
+ }
150
+
151
+ export interface RefusedAst {
152
+ kind: "refused";
153
+ /** `trigger`, `procedure`, `function`, ... */
154
+ objectKind: string;
155
+ name: string;
156
+ reason: string;
157
+ loc: Loc;
158
+ }
159
+
160
+ export interface IgnoredAst {
161
+ kind: "ignored";
162
+ /** What the statement was, e.g. `SET ANSI_NULLS ON`. */
163
+ text: string;
164
+ reason: string;
165
+ loc: Loc;
166
+ }
167
+
168
+ export type StatementAst = TableAst | IndexAst | ViewAst | SchemaAst | AlterConstraintAst | ExtendedPropertyAst | RefusedAst | IgnoredAst;
169
+
170
+ export class TsqlParseError extends Error {
171
+ constructor(
172
+ message: string,
173
+ readonly loc: Loc,
174
+ ) {
175
+ super(message);
176
+ this.name = "TsqlParseError";
177
+ }
178
+ }
179
+
180
+ const loc = (t: Token): Loc => ({ line: t.line, column: t.column });
181
+
182
+ /** A statement that began and could not be read: its first words, and why. */
183
+ export interface UnreadStatement {
184
+ text: string;
185
+ reason: string;
186
+ loc: Loc;
187
+ }
188
+
189
+ export interface ParseResult {
190
+ statements: StatementAst[];
191
+ /** Statements that begin like one this source reads and are malformed or use syntax it does not read. */
192
+ unread: UnreadStatement[];
193
+ }
194
+
195
+ // --- statement boundaries --------------------------------------------------
196
+
197
+ const START_WORDS = new Set("CREATE ALTER DROP EXEC EXECUTE SET USE INSERT UPDATE DELETE DECLARE PRINT IF GRANT DENY REVOKE SELECT BEGIN TRUNCATE MERGE RETURN WHILE".split(" "));
198
+ /** Statements that own the rest of their batch: a body can hold any statement word. */
199
+ const BATCH_BODY = new Set(["VIEW", "PROC", "PROCEDURE", "FUNCTION", "TRIGGER"]);
200
+
201
+ /** Split one batch into statements: at `;`, and where a new statement word starts at depth 0. */
202
+ function statementsOf(batch: readonly Token[]): Token[][] {
203
+ const out: Token[][] = [];
204
+ let cur: Token[] = [];
205
+ let depth = 0;
206
+ let ownsBatch = false;
207
+ const flush = () => {
208
+ if (cur.length > 0) out.push(cur);
209
+ cur = [];
210
+ depth = 0;
211
+ ownsBatch = false;
212
+ };
213
+ for (let k = 0; k < batch.length; k++) {
214
+ const t = batch[k]!;
215
+ if (t.kind === "punct" && t.value === ";" && depth === 0 && !ownsBatch) {
216
+ flush();
217
+ continue;
218
+ }
219
+ if (t.kind === "punct" && t.value === "(") depth++;
220
+ if (t.kind === "punct" && t.value === ")") depth = Math.max(0, depth - 1);
221
+ const w = upper(t);
222
+ const prev = upper(cur[cur.length - 1]);
223
+ if (cur.length > 0 && depth === 0 && !ownsBatch && START_WORDS.has(w)) {
224
+ const guarded = prev === "ON" || prev === "OR" || prev === "DELETE" || prev === "UPDATE" || (w === "SET" && batch[k + 1]?.value === "(") || (w === "IF" && upper(cur[0]) === "DROP");
225
+ // `DROP INDEX IF EXISTS`, `ON DELETE SET NULL` and `ALTER TABLE ... SET (...)` keep going.
226
+ if (!guarded) flush();
227
+ }
228
+ cur.push(t);
229
+ if (cur.length === 2 || cur.length === 3 || cur.length === 4) {
230
+ // `CREATE [OR ALTER] VIEW`: the body runs to the end of the batch.
231
+ const words = cur.map(upper);
232
+ if (words[0] === "CREATE" || words[0] === "ALTER") {
233
+ const kind = words[1] === "OR" ? words[3] : words[1];
234
+ if (BATCH_BODY.has(kind ?? "") && (words[1] !== "OR" || cur.length === 4)) ownsBatch = true;
235
+ }
236
+ }
237
+ }
238
+ flush();
239
+ return out;
240
+ }
241
+
242
+ // --- cursor ------------------------------------------------------------------
243
+
244
+ class Cursor {
245
+ i = 0;
246
+ constructor(readonly tokens: readonly Token[]) {}
247
+ get done(): boolean {
248
+ return this.i >= this.tokens.length;
249
+ }
250
+ peek(offset = 0): Token | undefined {
251
+ return this.tokens[this.i + offset];
252
+ }
253
+ next(): Token {
254
+ const t = this.tokens[this.i++];
255
+ if (!t) throw new TsqlParseError("the statement ends early", this.tokens.length > 0 ? loc(this.tokens[this.tokens.length - 1]!) : { line: 1, column: 1 });
256
+ return t;
257
+ }
258
+ isWord(...words: string[]): boolean {
259
+ return words.includes(upper(this.peek()));
260
+ }
261
+ /** Consume the next token when it is one of `words`. */
262
+ eat(...words: string[]): Token | undefined {
263
+ return this.isWord(...words) ? this.next() : undefined;
264
+ }
265
+ eatPunct(p: string): boolean {
266
+ const t = this.peek();
267
+ if (t && t.kind === "punct" && t.value === p) {
268
+ this.i++;
269
+ return true;
270
+ }
271
+ return false;
272
+ }
273
+ expectWord(...words: string[]): Token {
274
+ const t = this.peek();
275
+ if (!this.isWord(...words)) throw new TsqlParseError(`expected ${words.join(" or ")}, found ${t ? `"${t.text}"` : "the end of the statement"}`, t ? loc(t) : { line: 1, column: 1 });
276
+ return this.next();
277
+ }
278
+ expectPunct(p: string): void {
279
+ const t = this.peek();
280
+ if (!this.eatPunct(p)) throw new TsqlParseError(`expected "${p}", found ${t ? `"${t.text}"` : "the end of the statement"}`, t ? loc(t) : { line: 1, column: 1 });
281
+ }
282
+ /** Tokens inside a balanced `( ... )`, which the cursor is at. */
283
+ group(): Token[] {
284
+ this.expectPunct("(");
285
+ const start = this.i;
286
+ let depth = 1;
287
+ while (!this.done) {
288
+ const t = this.next();
289
+ if (t.kind === "punct" && t.value === "(") depth++;
290
+ if (t.kind === "punct" && t.value === ")" && --depth === 0) return this.tokens.slice(start, this.i - 1) as Token[];
291
+ }
292
+ throw new TsqlParseError('a "(" is never closed', loc(this.tokens[start - 1]!));
293
+ }
294
+ }
295
+
296
+ /** Split tokens at depth-0 commas. */
297
+ function splitCommas(tokens: readonly Token[]): Token[][] {
298
+ const out: Token[][] = [];
299
+ let cur: Token[] = [];
300
+ let depth = 0;
301
+ for (const t of tokens) {
302
+ if (t.kind === "punct" && t.value === "(") depth++;
303
+ if (t.kind === "punct" && t.value === ")") depth--;
304
+ if (depth === 0 && t.kind === "punct" && t.value === ",") {
305
+ out.push(cur);
306
+ cur = [];
307
+ } else cur.push(t);
308
+ }
309
+ if (cur.length > 0) out.push(cur);
310
+ return out;
311
+ }
312
+
313
+ function qname(c: Cursor): QName {
314
+ const first = c.peek();
315
+ if (!first || (first.kind !== "word" && first.kind !== "qident")) throw new TsqlParseError(`expected a name, found ${first ? `"${first.text}"` : "the end of the statement"}`, first ? loc(first) : { line: 1, column: 1 });
316
+ const parts: string[] = [];
317
+ for (;;) {
318
+ const t = c.next();
319
+ parts.push(t.value);
320
+ const dot = c.peek();
321
+ if (dot && dot.kind === "punct" && dot.value === ".") {
322
+ c.i++;
323
+ // `db..table` has an empty schema part.
324
+ const after = c.peek();
325
+ if (after && after.kind === "punct" && after.value === ".") {
326
+ c.i++;
327
+ parts.push("dbo");
328
+ }
329
+ continue;
330
+ }
331
+ break;
332
+ }
333
+ return { parts, loc: loc(first) };
334
+ }
335
+
336
+ function keyColumns(tokens: readonly Token[]): KeyColumn[] {
337
+ return splitCommas(tokens).map((part) => {
338
+ const c = new Cursor(part);
339
+ const name = c.next();
340
+ let descending = false;
341
+ if (c.eat("DESC")) descending = true;
342
+ else c.eat("ASC");
343
+ return { name: name.value, descending };
344
+ });
345
+ }
346
+
347
+ const nameList = (tokens: readonly Token[]): string[] => splitCommas(tokens).map((p) => p[0]!.value);
348
+
349
+ // --- constraints ---------------------------------------------------------------
350
+
351
+ /** Text of a `WITH (...)` / `ON x` tail on a key, so the translation can name it. */
352
+ function keyOptions(c: Cursor, text: string): string[] {
353
+ const out: string[] = [];
354
+ for (;;) {
355
+ if (c.isWord("WITH") && c.peek(1)?.value === "(") {
356
+ const start = c.i;
357
+ c.next();
358
+ c.group();
359
+ out.push(sourceOf(text, c.tokens.slice(start, c.i)));
360
+ } else if (c.isWord("ON") && c.peek(1) && !["DELETE", "UPDATE"].includes(upper(c.peek(1)))) {
361
+ const start = c.i;
362
+ c.next();
363
+ qname(c);
364
+ if (c.peek()?.value === "(") c.group();
365
+ out.push(sourceOf(text, c.tokens.slice(start, c.i)));
366
+ } else break;
367
+ }
368
+ return out;
369
+ }
370
+
371
+ /**
372
+ * One constraint, with the cursor at `PRIMARY`, `UNIQUE`, `FOREIGN`, `CHECK`,
373
+ * `DEFAULT` or `REFERENCES` (a column-level foreign key). `onColumn` is the
374
+ * column a column-level constraint was written on.
375
+ */
376
+ function constraint(c: Cursor, text: string, name: string | undefined, onColumn: string | undefined, startTok: Token): ConstraintAst {
377
+ const base = { name, onColumn, options: [] as string[], loc: loc(startTok) };
378
+ if (c.eat("PRIMARY")) {
379
+ c.expectWord("KEY");
380
+ const clustered = c.eat("CLUSTERED", "NONCLUSTERED");
381
+ const columns = c.peek()?.value === "(" ? keyColumns(c.group()) : onColumn ? [{ name: onColumn, descending: false }] : [];
382
+ c.eat("HASH");
383
+ const options = keyOptions(c, text);
384
+ return { ...base, kind: "primary-key", clustered: clustered ? upper(clustered) === "CLUSTERED" : undefined, columns, options };
385
+ }
386
+ if (c.eat("UNIQUE")) {
387
+ const clustered = c.eat("CLUSTERED", "NONCLUSTERED");
388
+ const columns = c.peek()?.value === "(" ? keyColumns(c.group()) : onColumn ? [{ name: onColumn, descending: false }] : [];
389
+ const options = keyOptions(c, text);
390
+ return { ...base, kind: "unique", clustered: clustered ? upper(clustered) === "CLUSTERED" : undefined, columns, options };
391
+ }
392
+ if (c.eat("FOREIGN")) {
393
+ c.expectWord("KEY");
394
+ const columns = keyColumns(c.group());
395
+ c.expectWord("REFERENCES");
396
+ return { ...referenceTail(c, text, base), columns };
397
+ }
398
+ if (c.isWord("REFERENCES")) {
399
+ c.next();
400
+ return { ...referenceTail(c, text, base), columns: onColumn ? [{ name: onColumn, descending: false }] : [] };
401
+ }
402
+ if (c.eat("CHECK")) {
403
+ const notForReplication = c.isWord("NOT") && c.peek(1)?.value.toUpperCase() === "FOR";
404
+ if (notForReplication) {
405
+ c.next();
406
+ c.next();
407
+ c.next();
408
+ }
409
+ const startIdx = c.i;
410
+ const inner = c.group();
411
+ return { ...base, kind: "check", columns: onColumn ? [{ name: onColumn, descending: false }] : [], expression: sourceOf(text, c.tokens.slice(startIdx, c.i)), expressionTokens: inner };
412
+ }
413
+ if (c.eat("DEFAULT")) {
414
+ const { tokens, source } = defaultExpression(c, text);
415
+ return { ...base, kind: "default", columns: onColumn ? [{ name: onColumn, descending: false }] : [], expression: source, expressionTokens: tokens };
416
+ }
417
+ const t = c.peek();
418
+ throw new TsqlParseError(`a constraint this source does not read: "${t?.text ?? ""}"`, t ? loc(t) : base.loc);
419
+ }
420
+
421
+ function referenceTail(c: Cursor, text: string, base: { name: string | undefined; onColumn: string | undefined; options: string[]; loc: Loc }): Omit<ConstraintAst, "columns"> {
422
+ const table = qname(c);
423
+ const columns = c.peek()?.value === "(" ? nameList(c.group()) : [];
424
+ // ON DELETE | ON UPDATE actions, NOT FOR REPLICATION
425
+ const actions: string[] = [];
426
+ for (;;) {
427
+ if (c.isWord("ON") && ["DELETE", "UPDATE"].includes(c.peek(1)?.value.toUpperCase() ?? "")) {
428
+ const start = c.i;
429
+ c.next();
430
+ c.next();
431
+ if (c.eat("NO")) c.expectWord("ACTION");
432
+ else if (c.eat("SET")) c.expectWord("NULL", "DEFAULT");
433
+ else c.expectWord("CASCADE");
434
+ actions.push(sourceOf(text, c.tokens.slice(start, c.i)));
435
+ } else if (c.isWord("NOT") && c.peek(1)?.value.toUpperCase() === "FOR") {
436
+ c.next();
437
+ c.next();
438
+ c.next();
439
+ } else break;
440
+ }
441
+ return { ...base, kind: "foreign-key", references: { table, columns }, options: actions };
442
+ }
443
+
444
+ /** A default's expression: a parenthesised group, or a signed literal / function call. */
445
+ function defaultExpression(c: Cursor, text: string): { tokens: Token[]; source: string } {
446
+ const start = c.i;
447
+ if (c.peek()?.value === "(") {
448
+ const inner = c.group();
449
+ return { tokens: inner, source: sourceOf(text, c.tokens.slice(start, c.i)) };
450
+ }
451
+ const sign = c.peek();
452
+ if (sign && sign.kind === "punct" && (sign.value === "-" || sign.value === "+")) c.next();
453
+ const t = c.next();
454
+ if (t.kind === "word" && c.peek()?.value === "(") c.group();
455
+ const slice = c.tokens.slice(start, c.i);
456
+ return { tokens: slice as Token[], source: sourceOf(text, slice) };
457
+ }
458
+
459
+ // --- CREATE TABLE ----------------------------------------------------------------
460
+
461
+ const TABLE_LEVEL_START = new Set(["CONSTRAINT", "PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "INDEX", "PERIOD", "CONNECTION"]);
462
+
463
+ function columnDef(tokens: readonly Token[], text: string, constraints: ConstraintAst[]): ColumnAst {
464
+ const c = new Cursor(tokens);
465
+ const nameTok = c.next();
466
+ const col: ColumnAst = { name: nameTok.value, loc: loc(nameTok), typeArgs: [], rowguid: false, sparse: false, extras: [] };
467
+ if (c.isWord("AS")) {
468
+ c.next();
469
+ const startIdx = c.i;
470
+ // `AS (expr)` or `AS expr` up to PERSISTED / NOT NULL / a constraint word
471
+ let expr: Token[];
472
+ if (c.peek()?.value === "(") {
473
+ c.group();
474
+ expr = c.tokens.slice(startIdx, c.i) as Token[];
475
+ } else {
476
+ while (!c.done && !c.isWord("PERSISTED", "NOT", "NULL", "CONSTRAINT", "PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "REFERENCES")) c.next();
477
+ expr = c.tokens.slice(startIdx, c.i) as Token[];
478
+ }
479
+ col.computed = { expression: sourceOf(text, expr), persisted: false };
480
+ } else {
481
+ // The type: `[schema].]name [(args)]`
482
+ const first = c.next();
483
+ let typeTok = first;
484
+ if (c.peek()?.value === "." && c.peek()?.kind === "punct") {
485
+ c.next();
486
+ col.typeSchema = first.value;
487
+ typeTok = c.next();
488
+ }
489
+ col.type = typeTok.value.toLowerCase();
490
+ if (c.peek()?.value === "(" && c.peek()?.kind === "punct") {
491
+ for (const arg of splitCommas(c.group())) {
492
+ const t = arg[0];
493
+ if (t && t.kind === "number") col.typeArgs.push(Number(t.value));
494
+ else if (t && upper(t) === "MAX") col.typeArgs.push("max");
495
+ else throw new TsqlParseError(`a type argument this source does not read: "${arg.map((x) => x.text).join(" ")}"`, t ? loc(t) : col.loc);
496
+ }
497
+ }
498
+ }
499
+ let constraintName: string | undefined;
500
+ while (!c.done) {
501
+ const t = c.peek()!;
502
+ const w = upper(t);
503
+ if (w === "CONSTRAINT") {
504
+ c.next();
505
+ constraintName = c.next().value;
506
+ continue;
507
+ }
508
+ if (w === "COLLATE") {
509
+ c.next();
510
+ col.collate = c.next().value;
511
+ } else if (w === "NOT" && upper(c.peek(1)) === "NULL") {
512
+ c.next();
513
+ c.next();
514
+ col.nullable = false;
515
+ } else if (w === "NULL") {
516
+ c.next();
517
+ col.nullable = true;
518
+ } else if (w === "NOT" && upper(c.peek(1)) === "FOR") {
519
+ c.next();
520
+ c.next();
521
+ c.next();
522
+ col.extras.push("NOT FOR REPLICATION");
523
+ } else if (w === "IDENTITY") {
524
+ c.next();
525
+ if (c.peek()?.value === "(") {
526
+ const args = splitCommas(c.group());
527
+ col.identity = { seed: args[0]?.map((x) => x.text).join("") ?? "1", increment: args[1]?.map((x) => x.text).join("") ?? "1" };
528
+ } else col.identity = { seed: "1", increment: "1" };
529
+ } else if (w === "GENERATED") {
530
+ const start = c.i;
531
+ c.next();
532
+ c.expectWord("ALWAYS");
533
+ c.expectWord("AS");
534
+ while (!c.done && !c.isWord("NOT", "NULL", "HIDDEN", "CONSTRAINT")) c.next();
535
+ col.generated = sourceOf(text, c.tokens.slice(start, c.i));
536
+ if (c.eat("HIDDEN")) col.generated += " HIDDEN";
537
+ } else if (w === "PERSISTED" && col.computed) {
538
+ c.next();
539
+ col.computed.persisted = true;
540
+ } else if (w === "ROWGUIDCOL") {
541
+ c.next();
542
+ col.rowguid = true;
543
+ } else if (w === "SPARSE") {
544
+ c.next();
545
+ col.sparse = true;
546
+ } else if (w === "FILESTREAM") {
547
+ c.next();
548
+ col.extras.push("FILESTREAM");
549
+ } else if (w === "MASKED" || w === "ENCRYPTED") {
550
+ const start = c.i;
551
+ c.next();
552
+ c.expectWord("WITH");
553
+ c.group();
554
+ col.extras.push(sourceOf(text, c.tokens.slice(start, c.i)));
555
+ } else if (w === "DEFAULT" || w === "PRIMARY" || w === "UNIQUE" || w === "CHECK" || w === "REFERENCES" || w === "FOREIGN") {
556
+ constraints.push(constraint(c, text, constraintName, col.name, t));
557
+ constraintName = undefined;
558
+ } else {
559
+ throw new TsqlParseError(`a column option this source does not read: "${t.text}" on column ${col.name}`, loc(t));
560
+ }
561
+ }
562
+ return col;
563
+ }
564
+
565
+ function tableBody(c: Cursor, text: string, nameTok: Token, name: QName): TableAst {
566
+ const table: TableAst = { kind: "table", name, columns: [], constraints: [], indexes: [], options: [], loc: loc(nameTok) };
567
+ if (c.isWord("AS")) {
568
+ // AS FileTable | AS NODE | AS EDGE
569
+ c.next();
570
+ table.asKind = c.next().value.toUpperCase();
571
+ }
572
+ const items = splitCommas(c.group());
573
+ for (const item of items) {
574
+ const ic = new Cursor(item);
575
+ const w = upper(ic.peek());
576
+ // A column may be named like a table-level keyword when bracketed; only a bare word starts one.
577
+ if (ic.peek()?.kind === "word" && TABLE_LEVEL_START.has(w)) {
578
+ if (w === "CONSTRAINT") {
579
+ const startTok = ic.next();
580
+ const cname = ic.next().value;
581
+ table.constraints.push(constraint(ic, text, cname, undefined, startTok));
582
+ } else if (w === "INDEX") {
583
+ ic.next();
584
+ const nameTok2 = ic.next();
585
+ const idx: IndexAst = { kind: "index", name: nameTok2.value, unique: false, clustered: false, columnstore: false, columns: [], include: [], order: [], options: [], loc: loc(nameTok2) };
586
+ if (ic.eat("UNIQUE")) idx.unique = true;
587
+ const cl = ic.eat("CLUSTERED", "NONCLUSTERED");
588
+ if (cl) idx.clustered = upper(cl) === "CLUSTERED";
589
+ if (ic.eat("COLUMNSTORE")) idx.columnstore = true;
590
+ if (ic.peek()?.value === "(") idx.columns = keyColumns(ic.group());
591
+ if (ic.eat("INCLUDE")) idx.include = nameList(ic.group());
592
+ if (ic.eat("WHERE")) {
593
+ const start = ic.i;
594
+ while (!ic.done && !ic.isWord("WITH", "ON")) ic.next();
595
+ idx.where = sourceOf(text, ic.tokens.slice(start, ic.i));
596
+ }
597
+ idx.options = keyOptions(ic, text);
598
+ table.indexes.push(idx);
599
+ } else if (w === "PERIOD") {
600
+ ic.next();
601
+ ic.expectWord("FOR");
602
+ ic.expectWord("SYSTEM_TIME");
603
+ const cols = nameList(ic.group());
604
+ table.period = { start: cols[0] ?? "", end: cols[1] ?? "" };
605
+ } else if (w === "CONNECTION") {
606
+ throw new TsqlParseError("an edge-table CONNECTION constraint, which this source does not read", loc(ic.peek()!));
607
+ } else {
608
+ table.constraints.push(constraint(ic, text, undefined, undefined, ic.peek()!));
609
+ }
610
+ if (!ic.done) throw new TsqlParseError(`text this source does not read after a table constraint: "${ic.peek()!.text}"`, loc(ic.peek()!));
611
+ } else {
612
+ table.columns.push(columnDef(item, text, table.constraints));
613
+ }
614
+ }
615
+ // `WITH (...)`, `ON [PRIMARY]`, `TEXTIMAGE_ON x`, `FILESTREAM_ON x`, `LEDGER = ON`
616
+ while (!c.done) {
617
+ const start = c.i;
618
+ const t = c.next();
619
+ const w = upper(t);
620
+ if (w === "WITH" && c.peek()?.value === "(") c.group();
621
+ else if (w === "ON") {
622
+ qname(c);
623
+ if (c.peek()?.value === "(") c.group();
624
+ } else if (w === "TEXTIMAGE_ON" || w === "FILESTREAM_ON") qname(c);
625
+ else throw new TsqlParseError(`a table clause this source does not read: "${t.text}"`, loc(t));
626
+ table.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
627
+ }
628
+ return table;
629
+ }
630
+
631
+ // --- CREATE INDEX ----------------------------------------------------------------
632
+
633
+ function indexStatement(c: Cursor, text: string, startTok: Token): IndexAst {
634
+ let unique = false;
635
+ let clustered = false;
636
+ let columnstore = false;
637
+ if (c.eat("UNIQUE")) unique = true;
638
+ const cl = c.eat("CLUSTERED", "NONCLUSTERED");
639
+ if (cl) clustered = upper(cl) === "CLUSTERED";
640
+ if (c.eat("COLUMNSTORE")) columnstore = true;
641
+ c.expectWord("INDEX");
642
+ const nameTok = c.next();
643
+ c.expectWord("ON");
644
+ const table = qname(c);
645
+ const idx: IndexAst = { kind: "index", name: nameTok.value, table, unique, clustered, columnstore, columns: [], include: [], order: [], options: [], loc: loc(startTok) };
646
+ if (c.peek()?.value === "(") idx.columns = keyColumns(c.group());
647
+ // Clauses may come in more than one order: ORDER before or after WITH.
648
+ while (!c.done) {
649
+ if (c.eat("INCLUDE")) idx.include = nameList(c.group());
650
+ else if (c.eat("ORDER")) idx.order = nameList(c.group());
651
+ else if (c.eat("WHERE")) {
652
+ const start = c.i;
653
+ while (!c.done && !c.isWord("WITH", "ON", "ORDER")) c.next();
654
+ idx.where = sourceOf(text, c.tokens.slice(start, c.i));
655
+ } else if (c.isWord("WITH") && c.peek(1)?.value === "(") {
656
+ const start = c.i;
657
+ c.next();
658
+ c.group();
659
+ idx.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
660
+ } else if (c.isWord("ON")) {
661
+ const start = c.i;
662
+ c.next();
663
+ qname(c);
664
+ if (c.peek()?.value === "(") c.group();
665
+ idx.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
666
+ } else {
667
+ const t = c.peek()!;
668
+ throw new TsqlParseError(`an index clause this source does not read: "${t.text}"`, loc(t));
669
+ }
670
+ }
671
+ return idx;
672
+ }
673
+
674
+ // --- the rest ------------------------------------------------------------------------
675
+
676
+ const REFUSED_KINDS: Record<string, { kind: string; reason: string }> = {
677
+ TRIGGER: { kind: "trigger", reason: "a trigger runs T-SQL on writes; ClickHouse has no trigger, and its closest match is a materialized view" },
678
+ PROC: { kind: "procedure", reason: "a stored procedure is T-SQL code; ClickHouse has no stored procedures" },
679
+ PROCEDURE: { kind: "procedure", reason: "a stored procedure is T-SQL code; ClickHouse has no stored procedures" },
680
+ FUNCTION: { kind: "function", reason: "a T-SQL function is code this source does not translate; a ClickHouse user-defined function is a lambda expression" },
681
+ SEQUENCE: { kind: "sequence", reason: "ClickHouse has no sequence object" },
682
+ TYPE: { kind: "type", reason: "a user-defined type has no ClickHouse counterpart" },
683
+ SYNONYM: { kind: "synonym", reason: "ClickHouse has no synonym" },
684
+ ROLE: { kind: "role", reason: "access control is not read by this source" },
685
+ USER: { kind: "user", reason: "access control is not read by this source" },
686
+ LOGIN: { kind: "login", reason: "access control is not read by this source" },
687
+ DATABASE: { kind: "database", reason: "a database is created outside the schema file" },
688
+ STATISTICS: { kind: "statistics", reason: "ClickHouse keeps no user-created statistics" },
689
+ PARTITION: { kind: "partition function or scheme", reason: "ClickHouse partitions with PARTITION BY on the table" },
690
+ XML: { kind: "xml schema collection", reason: "ClickHouse has no XML schema collection" },
691
+ ASSEMBLY: { kind: "assembly", reason: "a CLR assembly is not portable" },
692
+ AGGREGATE: { kind: "aggregate", reason: "a CLR aggregate is not portable" },
693
+ RULE: { kind: "rule", reason: "ClickHouse has no rule object" },
694
+ DEFAULT: { kind: "default object", reason: "ClickHouse has no default object" },
695
+ EVENT: { kind: "event session or notification", reason: "not read by this source" },
696
+ FULLTEXT: { kind: "full-text object", reason: "ClickHouse has no full-text catalog" },
697
+ SEARCH: { kind: "search property list", reason: "not read by this source" },
698
+ MASTER: { kind: "master key", reason: "key management is not read by this source" },
699
+ CERTIFICATE: { kind: "certificate", reason: "key management is not read by this source" },
700
+ EXTERNAL: { kind: "external object", reason: "not read by this source" },
701
+ MATERIALIZED: { kind: "materialized view", reason: "not read by this source" },
702
+ SECURITY: { kind: "security policy", reason: "row-level security is not read by this source" },
703
+ QUEUE: { kind: "service broker object", reason: "ClickHouse has no service broker" },
704
+ };
705
+
706
+ function describeStatement(tokens: readonly Token[], text: string): string {
707
+ const shown = tokens.slice(0, 8);
708
+ return sourceOf(text, shown).replace(/\s+/g, " ");
709
+ }
710
+
711
+ function oneStatement(tokens: readonly Token[], text: string, out: ParseResult): void {
712
+ const c = new Cursor(tokens);
713
+ const first = c.peek()!;
714
+ const start = loc(first);
715
+ const w = upper(first);
716
+ try {
717
+ if (w === "CREATE" || w === "ALTER") {
718
+ c.next();
719
+ if (w === "CREATE" && c.isWord("OR")) {
720
+ c.next();
721
+ c.expectWord("ALTER");
722
+ }
723
+ const kind = upper(c.peek());
724
+ if (w === "CREATE" && kind === "TABLE") {
725
+ c.next();
726
+ const nameTok = c.peek()!;
727
+ const name = qname(c);
728
+ out.statements.push(tableBody(c, text, nameTok, name));
729
+ return;
730
+ }
731
+ if (w === "CREATE" && (kind === "INDEX" || kind === "UNIQUE" || kind === "CLUSTERED" || kind === "NONCLUSTERED" || kind === "COLUMNSTORE")) {
732
+ out.statements.push(indexStatement(c, text, first));
733
+ return;
734
+ }
735
+ if (w === "CREATE" && kind === "VIEW") {
736
+ c.next();
737
+ const name = qname(c);
738
+ const columns = c.peek()?.value === "(" ? nameList(c.group()) : [];
739
+ const options: string[] = [];
740
+ if (c.eat("WITH")) {
741
+ for (const part of splitCommas(tokens.slice(c.i))) options.push(sourceOf(text, part));
742
+ while (!c.isWord("AS")) c.next();
743
+ }
744
+ c.expectWord("AS");
745
+ let body = tokens.slice(c.i);
746
+ while (body.length > 0 && body[body.length - 1]!.kind === "punct" && body[body.length - 1]!.value === ";") body = body.slice(0, -1);
747
+ out.statements.push({ kind: "view", name, columns, options, body: sourceOf(text, body), bodyTokens: [...body], loc: name.loc });
748
+ return;
749
+ }
750
+ if (w === "CREATE" && kind === "SCHEMA") {
751
+ c.next();
752
+ out.statements.push({ kind: "schema", name: c.next().value, loc: start });
753
+ return;
754
+ }
755
+ if (w === "CREATE" || (w === "ALTER" && kind !== "TABLE")) {
756
+ const refused = REFUSED_KINDS[kind];
757
+ const objectKind = refused?.kind ?? kind.toLowerCase();
758
+ c.next();
759
+ // `PARTITION FUNCTION f`, `XML SCHEMA COLLECTION c`, `EXTERNAL TABLE t`: skip the second word.
760
+ if (["PARTITION", "XML", "EXTERNAL", "EVENT", "FULLTEXT", "SEARCH", "MASTER", "SECURITY", "MATERIALIZED"].includes(kind)) {
761
+ const second = c.peek();
762
+ if (second && second.kind === "word") c.next();
763
+ }
764
+ const nameTok = c.peek();
765
+ let name = "";
766
+ if (nameTok && (nameTok.kind === "word" || nameTok.kind === "qident")) name = qname(c).parts.join(".");
767
+ out.statements.push({
768
+ kind: "refused",
769
+ objectKind: w === "ALTER" ? `alter ${objectKind}` : objectKind,
770
+ name,
771
+ reason: refused?.reason ?? "a kind of object this source does not read",
772
+ loc: start,
773
+ });
774
+ return;
775
+ }
776
+ // ALTER TABLE t [WITH [NO]CHECK] ADD [CONSTRAINT n] ...
777
+ c.expectWord("TABLE");
778
+ const table = qname(c);
779
+ if (c.eat("WITH")) c.expectWord("CHECK", "NOCHECK");
780
+ if (c.isWord("ADD")) {
781
+ c.next();
782
+ let cname: string | undefined;
783
+ const ctok = c.peek()!;
784
+ if (c.eat("CONSTRAINT")) cname = c.next().value;
785
+ if (c.isWord("PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "DEFAULT")) {
786
+ const k = constraint(c, text, cname, undefined, ctok);
787
+ // `ADD DEFAULT (x) FOR col`
788
+ if (k.kind === "default" && c.eat("FOR")) k.columns = [{ name: c.next().value, descending: false }];
789
+ if (!c.done) throw new TsqlParseError(`text this source does not read after the constraint: "${c.peek()!.text}"`, loc(c.peek()!));
790
+ out.statements.push({ kind: "alter-constraint", table, constraint: k, loc: start });
791
+ return;
792
+ }
793
+ }
794
+ out.unread.push({ text: describeStatement(tokens, text), reason: "an ALTER TABLE this source does not read; only ADD CONSTRAINT is read", loc: start });
795
+ return;
796
+ }
797
+ if (w === "EXEC" || w === "EXECUTE") {
798
+ c.next();
799
+ const proc = c.peek();
800
+ if (proc && /^sp_addextendedproperty$/i.test(proc.value)) {
801
+ const ep = extendedProperty(c, start);
802
+ if (ep) {
803
+ out.statements.push(ep);
804
+ return;
805
+ }
806
+ }
807
+ out.statements.push({ kind: "refused", objectKind: "statement", name: describeStatement(tokens, text), reason: "executing a procedure is code, not a declaration", loc: start });
808
+ return;
809
+ }
810
+ if (w === "SET" || w === "USE" || w === "PRINT" || w === "DROP" || w === "DECLARE" || w === "RETURN" || w === "TRUNCATE") {
811
+ const reason =
812
+ w === "DROP"
813
+ ? "a DROP is not a declaration; the schema describes the end state"
814
+ : w === "SET" || w === "USE"
815
+ ? "a session or connection setting, not a declaration"
816
+ : "not a declaration";
817
+ out.statements.push({ kind: "ignored", text: describeStatement(tokens, text), reason, loc: start });
818
+ return;
819
+ }
820
+ out.statements.push({ kind: "refused", objectKind: "statement", name: describeStatement(tokens, text), reason: "this source reads CREATE TABLE, CREATE INDEX, CREATE VIEW, CREATE SCHEMA and ALTER TABLE ... ADD CONSTRAINT", loc: start });
821
+ } catch (e) {
822
+ if (e instanceof TsqlParseError) {
823
+ out.unread.push({ text: describeStatement(tokens, text), reason: e.message, loc: e.loc });
824
+ return;
825
+ }
826
+ throw e;
827
+ }
828
+ }
829
+
830
+ /** `EXECUTE sp_addextendedproperty @name = N'...', @value = ..., @level0type = ..., ...`; undefined when it is not the shape SSDT writes. */
831
+ function extendedProperty(c: Cursor, at: Loc): ExtendedPropertyAst | undefined {
832
+ c.next();
833
+ const args = new Map<string, string>();
834
+ const rest = c.tokens.slice(c.i);
835
+ for (const part of splitCommas(rest)) {
836
+ const t = part[0];
837
+ if (!t || t.kind !== "variable" || part[1]?.value !== "=" || !part[2]) return undefined;
838
+ const v = part[2];
839
+ args.set(t.value.toLowerCase(), v.kind === "string" || v.kind === "qident" ? v.value : v.text);
840
+ }
841
+ const name = args.get("@name");
842
+ const value = args.get("@value");
843
+ if (name === undefined || value === undefined) return undefined;
844
+ const l2t = args.get("@level2type");
845
+ const l2n = args.get("@level2name");
846
+ return {
847
+ kind: "extended-property",
848
+ property: name,
849
+ value,
850
+ schema: args.get("@level0name"),
851
+ table: args.get("@level1name"),
852
+ level1Type: args.get("@level1type"),
853
+ ...(l2t?.toUpperCase() === "COLUMN" ? { column: l2n } : l2t && l2n ? { level2: { type: l2t, name: l2n } } : {}),
854
+ loc: at,
855
+ };
856
+ }
857
+
858
+ /** Parse a file of T-SQL. Lexing errors throw `TsqlLexError`. */
859
+ export function parseTsql(text: string): ParseResult {
860
+ const out: ParseResult = { statements: [], unread: [] };
861
+ for (const batch of batches(tokenize(text))) {
862
+ for (const stmt of statementsOf(batch)) oneStatement(stmt, text, out);
863
+ }
864
+ return out;
865
+ }