@intentius/chant-lexicon-sql 0.124.31 → 0.124.33

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 (322) hide show
  1. package/dist/change-set.js +250 -0
  2. package/dist/clickhouse/access.js +722 -0
  3. package/dist/clickhouse/apply/apply.js +243 -0
  4. package/dist/clickhouse/apply/mutations.js +66 -0
  5. package/dist/clickhouse/apply/statements.js +545 -0
  6. package/dist/clickhouse/catalog-types.js +7 -0
  7. package/dist/clickhouse/container.js +152 -0
  8. package/dist/clickhouse/engine-syntax.js +159 -0
  9. package/dist/clickhouse/entities.js +593 -0
  10. package/dist/clickhouse/expressions.js +107 -0
  11. package/dist/clickhouse/http.js +55 -0
  12. package/dist/clickhouse/import/generator.js +192 -0
  13. package/dist/clickhouse/import/inlined.js +207 -0
  14. package/dist/clickhouse/import/ir.js +160 -0
  15. package/dist/clickhouse/import/live-export.js +131 -0
  16. package/dist/clickhouse/import/parser.js +17 -0
  17. package/dist/clickhouse/live/bind.js +116 -0
  18. package/dist/clickhouse/live/catalog.js +223 -0
  19. package/dist/clickhouse/live/describe-resources.js +91 -0
  20. package/dist/clickhouse/live/undeclarable.js +59 -0
  21. package/dist/clickhouse/overlays/codecs.js +57 -0
  22. package/dist/clickhouse/overlays/engines.js +101 -0
  23. package/dist/clickhouse/overlays/kinds.js +1 -0
  24. package/dist/clickhouse/overlays/projections.js +26 -0
  25. package/dist/clickhouse/overlays/skip-indexes.js +40 -0
  26. package/dist/clickhouse/overlays/ttl.js +34 -0
  27. package/dist/clickhouse/overlays/types.js +59 -0
  28. package/dist/clickhouse/ownership.js +83 -0
  29. package/dist/clickhouse/parser.js +674 -0
  30. package/dist/clickhouse/plan/commands.js +159 -0
  31. package/dist/clickhouse/plan/deep.js +345 -0
  32. package/dist/clickhouse/plan/diff.js +312 -0
  33. package/dist/clickhouse/plan/disruption.js +73 -0
  34. package/dist/clickhouse/plan/normalize.js +545 -0
  35. package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
  36. package/dist/clickhouse/plan/report.js +26 -0
  37. package/dist/clickhouse/plan/rules.js +72 -0
  38. package/dist/clickhouse/plan/schema.js +67 -0
  39. package/dist/clickhouse/plan/server-format.js +35 -0
  40. package/dist/clickhouse/rebuild/backfill.js +265 -0
  41. package/dist/clickhouse/rebuild/engines.js +146 -0
  42. package/dist/clickhouse/rebuild/observe.js +260 -0
  43. package/dist/clickhouse/rebuild/op.js +152 -0
  44. package/dist/clickhouse/rebuild/partitions.js +65 -0
  45. package/dist/clickhouse/rebuild/receipts.js +120 -0
  46. package/dist/clickhouse/rebuild/replicas.js +121 -0
  47. package/dist/clickhouse/rebuild/shards.js +76 -0
  48. package/dist/clickhouse/rebuild/steps.js +383 -0
  49. package/dist/clickhouse/rebuild/verify.js +279 -0
  50. package/dist/clickhouse/row-types.js +151 -0
  51. package/dist/clickhouse/testing/fake-server.js +47 -0
  52. package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
  53. package/dist/clickhouse/testing/writable-server.js +123 -0
  54. package/dist/clickhouse/tokens.js +23 -0
  55. package/dist/clickhouse/topology.js +463 -0
  56. package/dist/clickhouse/type-syntax.js +73 -0
  57. package/dist/clickhouse.js +25 -0
  58. package/dist/codegen/classifier-page.js +93 -0
  59. package/dist/codegen/clickhouse-module.js +249 -0
  60. package/dist/codegen/coverage-page.js +70 -0
  61. package/dist/codegen/docs-cli.js +3 -0
  62. package/dist/codegen/docs.js +222 -0
  63. package/dist/codegen/generate-cli.js +13 -0
  64. package/dist/codegen/generate.js +97 -0
  65. package/dist/codegen/package.js +28 -0
  66. package/dist/codegen/postgres-classifier-page.js +95 -0
  67. package/dist/codegen/postgres-module.js +418 -0
  68. package/dist/codegen/registry.js +52 -0
  69. package/dist/composites/audit-log-table.js +35 -0
  70. package/dist/composites/catalog.js +524 -0
  71. package/dist/composites/cdc-mirror.js +32 -0
  72. package/dist/composites/clickhouse.js +6 -0
  73. package/dist/composites/events-table.js +26 -0
  74. package/dist/composites/index.js +8 -0
  75. package/dist/composites/join-table.js +25 -0
  76. package/dist/composites/postgres.js +6 -0
  77. package/dist/composites/refreshed-view.js +24 -0
  78. package/dist/composites/replacing-table.js +28 -0
  79. package/dist/composites/rollup-view.js +29 -0
  80. package/dist/composites/sharded-table.js +27 -0
  81. package/dist/composites/soft-delete-table.js +34 -0
  82. package/dist/composites/tenant-table.js +26 -0
  83. package/dist/composites/typed.js +8 -0
  84. package/dist/config.js +206 -0
  85. package/dist/core/apply.js +93 -0
  86. package/dist/core/classifier.js +115 -0
  87. package/dist/core/container.js +69 -0
  88. package/dist/core/cursor.js +261 -0
  89. package/dist/core/diff.js +48 -0
  90. package/dist/core/entity.js +58 -0
  91. package/dist/core/find-templates.js +118 -0
  92. package/dist/core/foreign-tables.js +27 -0
  93. package/dist/core/handoff.js +32 -0
  94. package/dist/core/index.js +64 -0
  95. package/dist/core/interpolation.js +191 -0
  96. package/dist/core/normalize.js +43 -0
  97. package/dist/core/ownership.js +119 -0
  98. package/dist/core/receipts.js +22 -0
  99. package/dist/core/references.js +61 -0
  100. package/dist/core/statements.js +42 -0
  101. package/dist/core/template.js +79 -0
  102. package/dist/core/testing/http-stub.js +21 -0
  103. package/dist/core/tokens.js +202 -0
  104. package/dist/core/typed.js +40 -0
  105. package/dist/core/undeclarable.js +46 -0
  106. package/dist/coverage.js +161 -0
  107. package/dist/detect.js +32 -0
  108. package/dist/dialects.js +18 -0
  109. package/dist/files/build-root.js +72 -0
  110. package/dist/files/clickhouse.js +138 -0
  111. package/dist/files/common.js +58 -0
  112. package/dist/files/postgres.js +389 -0
  113. package/dist/generated/clickhouse-types.js +5 -0
  114. package/dist/generated/clickhouse.js +4361 -0
  115. package/dist/generated/postgres-types.js +227 -0
  116. package/dist/generated/postgres.js +3667 -0
  117. package/dist/import-generator.js +14 -0
  118. package/dist/import-parser.js +71 -0
  119. package/dist/index.js +24 -0
  120. package/dist/init-templates.js +290 -0
  121. package/dist/integrity.json +2 -2
  122. package/dist/lint/audit-catalog.js +31 -0
  123. package/dist/lint/audit-lineage.js +22 -0
  124. package/dist/lint/post-synth/audit-entries.js +34 -0
  125. package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
  126. package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
  127. package/dist/lint/post-synth/index.js +112 -0
  128. package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
  129. package/dist/lint/post-synth/postgres-helpers.js +65 -0
  130. package/dist/lint/post-synth/postgres-names.js +507 -0
  131. package/dist/lint/post-synth/sql-helpers.js +23 -0
  132. package/dist/lint/post-synth/sql101.js +59 -0
  133. package/dist/lint/post-synth/sql102.js +34 -0
  134. package/dist/lint/post-synth/sqlch101.js +34 -0
  135. package/dist/lint/post-synth/sqlch102.js +23 -0
  136. package/dist/lint/post-synth/sqlch103.js +45 -0
  137. package/dist/lint/post-synth/sqlch104.js +41 -0
  138. package/dist/lint/post-synth/sqlch105.js +36 -0
  139. package/dist/lint/post-synth/sqlch106.js +27 -0
  140. package/dist/lint/post-synth/sqlch107.js +39 -0
  141. package/dist/lint/post-synth/sqlch108.js +28 -0
  142. package/dist/lint/post-synth/sqlch109.js +21 -0
  143. package/dist/lint/post-synth/sqlch110.js +31 -0
  144. package/dist/lint/post-synth/sqlch111.js +41 -0
  145. package/dist/lint/post-synth/sqlch112.js +28 -0
  146. package/dist/lint/post-synth/sqlch113.js +19 -0
  147. package/dist/lint/post-synth/sqlch114.js +26 -0
  148. package/dist/lint/post-synth/sqlch115.js +30 -0
  149. package/dist/lint/post-synth/sqlch116.js +22 -0
  150. package/dist/lint/post-synth/sqlch117.js +26 -0
  151. package/dist/lint/post-synth/sqlch118.js +27 -0
  152. package/dist/lint/post-synth/sqlch119.js +32 -0
  153. package/dist/lint/post-synth/sqlch120.js +20 -0
  154. package/dist/lint/post-synth/sqlch121.js +11 -0
  155. package/dist/lint/post-synth/sqlch122.js +11 -0
  156. package/dist/lint/post-synth/sqlch123.js +10 -0
  157. package/dist/lint/post-synth/sqlch124.js +21 -0
  158. package/dist/lint/post-synth/sqlch125.js +55 -0
  159. package/dist/lint/post-synth/sqlch126.js +62 -0
  160. package/dist/lint/post-synth/sqlch127.js +132 -0
  161. package/dist/lint/post-synth/sqlpg101.js +25 -0
  162. package/dist/lint/post-synth/sqlpg102.js +35 -0
  163. package/dist/lint/post-synth/sqlpg103.js +23 -0
  164. package/dist/lint/post-synth/sqlpg104.js +23 -0
  165. package/dist/lint/post-synth/sqlpg105.js +22 -0
  166. package/dist/lint/post-synth/sqlpg106.js +22 -0
  167. package/dist/lint/post-synth/sqlpg107.js +22 -0
  168. package/dist/lint/post-synth/sqlpg108.js +28 -0
  169. package/dist/lint/post-synth/sqlpg109.js +39 -0
  170. package/dist/lint/post-synth/sqlpg110.js +38 -0
  171. package/dist/lint/post-synth/sqlpg111.js +23 -0
  172. package/dist/lint/post-synth/sqlpg112.js +25 -0
  173. package/dist/lint/post-synth/sqlpg113.js +29 -0
  174. package/dist/lint/post-synth/sqlpg114.js +39 -0
  175. package/dist/lint/post-synth/sqlpg115.js +48 -0
  176. package/dist/lint/post-synth/sqlpg116.js +26 -0
  177. package/dist/lint/post-synth/sqlpg117.js +24 -0
  178. package/dist/lint/post-synth/sqlpg118.js +22 -0
  179. package/dist/lint/post-synth/sqlpg119.js +41 -0
  180. package/dist/lint/post-synth/sqlpg120.js +83 -0
  181. package/dist/lint/post-synth/sqlpg121.js +33 -0
  182. package/dist/lint/post-synth/sqlpg122.js +27 -0
  183. package/dist/lint/post-synth/sqlpg123.js +69 -0
  184. package/dist/lint/post-synth/sqlpg124.js +50 -0
  185. package/dist/lint/post-synth/sqlpg125.js +41 -0
  186. package/dist/lint/post-synth/sqlpg126.js +38 -0
  187. package/dist/lint/rules/index.js +12 -0
  188. package/dist/lint/rules/postgres-templates.js +27 -0
  189. package/dist/lint/rules/sqlch001.js +79 -0
  190. package/dist/lint/rules/sqlch002.js +124 -0
  191. package/dist/lint/rules/sqlch003.js +69 -0
  192. package/dist/lint/rules/sqlch004.js +83 -0
  193. package/dist/lint/rules/sqlpg001.js +68 -0
  194. package/dist/lint/rules/sqlpg002.js +69 -0
  195. package/dist/lint/rules/sqlpg003.js +58 -0
  196. package/dist/lint/rules/sqlpg004.js +77 -0
  197. package/dist/lint/rules/sqlpg005.js +86 -0
  198. package/dist/lint/rules/templates.js +25 -0
  199. package/dist/lint/rules/typed-names.js +132 -0
  200. package/dist/live-dialect.js +32 -0
  201. package/dist/lsp/catalog.js +44 -0
  202. package/dist/lsp/completions.js +136 -0
  203. package/dist/lsp/hover.js +115 -0
  204. package/dist/lsp/postgres-catalog.js +176 -0
  205. package/dist/lsp/postgres-template.js +171 -0
  206. package/dist/lsp/postgres.js +266 -0
  207. package/dist/lsp/refs.js +60 -0
  208. package/dist/lsp/registry.d.ts.map +1 -1
  209. package/dist/lsp/registry.js +19 -0
  210. package/dist/lsp/template.js +143 -0
  211. package/dist/manifest.json +1 -1
  212. package/dist/mcp/index.d.ts.map +1 -1
  213. package/dist/mcp/index.js +293 -0
  214. package/dist/mcp/postgres.js +117 -0
  215. package/dist/migration-statements.js +243 -0
  216. package/dist/op/activities/clickhouse-apply.js +83 -0
  217. package/dist/op/activities/clickhouse-emulator.js +53 -0
  218. package/dist/op/activities/clickhouse-rebuild.js +119 -0
  219. package/dist/op/activities/index.js +26 -0
  220. package/dist/op/activities/postgres-apply.js +126 -0
  221. package/dist/op/activities/postgres-emulator.js +70 -0
  222. package/dist/op/activities/postgres-migration.js +156 -0
  223. package/dist/op/activities/sql-exec.js +57 -0
  224. package/dist/op/activity-contracts.js +103 -0
  225. package/dist/package-cli.js +14 -0
  226. package/dist/plugin.js +259 -0
  227. package/dist/postgres/access/acl.js +344 -0
  228. package/dist/postgres/access/changes.js +30 -0
  229. package/dist/postgres/access/import.js +61 -0
  230. package/dist/postgres/access/live.js +96 -0
  231. package/dist/postgres/apply/apply.js +557 -0
  232. package/dist/postgres/apply/statements.js +882 -0
  233. package/dist/postgres/catalog-types.js +18 -0
  234. package/dist/postgres/container.js +58 -0
  235. package/dist/postgres/entities.js +1390 -0
  236. package/dist/postgres/entity-types.js +28 -0
  237. package/dist/postgres/import/generator.js +283 -0
  238. package/dist/postgres/import/ir.js +53 -0
  239. package/dist/postgres/import/live-export.js +83 -0
  240. package/dist/postgres/keywords.js +32 -0
  241. package/dist/postgres/live/bind.js +130 -0
  242. package/dist/postgres/live/catalog-kinds.js +301 -0
  243. package/dist/postgres/live/catalog.js +644 -0
  244. package/dist/postgres/live/client.js +151 -0
  245. package/dist/postgres/live/describe-resources.js +135 -0
  246. package/dist/postgres/live/undeclarable.js +111 -0
  247. package/dist/postgres/migrate/backfill.js +170 -0
  248. package/dist/postgres/migrate/batches.js +122 -0
  249. package/dist/postgres/migrate/carry.js +518 -0
  250. package/dist/postgres/migrate/handoff.js +59 -0
  251. package/dist/postgres/migrate/names.js +61 -0
  252. package/dist/postgres/migrate/observe.js +360 -0
  253. package/dist/postgres/migrate/op.js +178 -0
  254. package/dist/postgres/migrate/receipts.js +117 -0
  255. package/dist/postgres/migrate/replication.js +106 -0
  256. package/dist/postgres/migrate/steps.js +514 -0
  257. package/dist/postgres/overlays/columns.js +31 -0
  258. package/dist/postgres/overlays/constraints.js +52 -0
  259. package/dist/postgres/overlays/extension-objects.js +88 -0
  260. package/dist/postgres/overlays/indexes.js +25 -0
  261. package/dist/postgres/overlays/kinds.js +2 -0
  262. package/dist/postgres/overlays/partitioning.js +23 -0
  263. package/dist/postgres/overlays/relations.js +25 -0
  264. package/dist/postgres/overlays/storage.js +65 -0
  265. package/dist/postgres/overlays/type-ddl.js +36 -0
  266. package/dist/postgres/overlays/types.js +74 -0
  267. package/dist/postgres/parser.js +1824 -0
  268. package/dist/postgres/plan/commands.js +137 -0
  269. package/dist/postgres/plan/deep.js +324 -0
  270. package/dist/postgres/plan/diff.js +499 -0
  271. package/dist/postgres/plan/disruption.js +64 -0
  272. package/dist/postgres/plan/normalize.js +889 -0
  273. package/dist/postgres/plan/report.js +21 -0
  274. package/dist/postgres/plan/rules.js +115 -0
  275. package/dist/postgres/plan/schema.js +105 -0
  276. package/dist/postgres/plan/server-normalize.js +166 -0
  277. package/dist/postgres/providers/aurora.js +44 -0
  278. package/dist/postgres/providers/azure.js +53 -0
  279. package/dist/postgres/providers/cloud-sql.js +41 -0
  280. package/dist/postgres/providers/index.js +73 -0
  281. package/dist/postgres/providers/neon.js +44 -0
  282. package/dist/postgres/providers/rds.js +47 -0
  283. package/dist/postgres/providers/supabase.js +45 -0
  284. package/dist/postgres/providers/types.js +21 -0
  285. package/dist/postgres/row-types.js +107 -0
  286. package/dist/postgres/testing/fake-client.js +34 -0
  287. package/dist/postgres/testing/migration.js +48 -0
  288. package/dist/postgres/testing/roundtrip-schema.js +64 -0
  289. package/dist/postgres/testing/server.js +47 -0
  290. package/dist/postgres/testing/writable-server.js +160 -0
  291. package/dist/postgres/tokens.js +41 -0
  292. package/dist/postgres.js +15 -0
  293. package/dist/read/clickhouse.js +82 -0
  294. package/dist/read/objects.js +44 -0
  295. package/dist/read/postgres.js +104 -0
  296. package/dist/read.js +52 -0
  297. package/dist/receipts.js +207 -0
  298. package/dist/serializer.js +172 -0
  299. package/dist/skill-defs.js +93 -0
  300. package/dist/spec/catalog.js +191 -0
  301. package/dist/spec/fetch.d.ts.map +1 -1
  302. package/dist/spec/fetch.js +90 -0
  303. package/dist/spec/pin.js +42 -0
  304. package/dist/spec/postgres-catalog.js +171 -0
  305. package/dist/spec/postgres-fetch.js +91 -0
  306. package/dist/spec/postgres-pin.js +82 -0
  307. package/dist/sql-files.js +206 -0
  308. package/dist/sql-types-command.js +97 -0
  309. package/dist/sql-types.js +323 -0
  310. package/dist/token-source.js +156 -0
  311. package/dist/validate-cli.js +5 -0
  312. package/dist/validate.js +151 -0
  313. package/dist/workspace-outputs.js +191 -0
  314. package/package.json +7 -7
  315. package/src/codegen/package.ts +2 -2
  316. package/src/lint/rules/typescript-7.test.ts +2 -1
  317. package/src/lsp/catalog.ts +2 -2
  318. package/src/lsp/postgres-catalog.ts +2 -2
  319. package/src/lsp/registry.ts +2 -1
  320. package/src/mcp/index.ts +4 -2
  321. package/src/spec/fetch.ts +2 -2
  322. package/src/spec/postgres-fetch.ts +2 -2
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The import generator for every dialect: an IR whose resources are Postgres
3
+ * types goes to the Postgres generator, any other to ClickHouse's. A build
4
+ * holds one dialect, and so does an import.
5
+ */
6
+ import { ClickHouseGenerator } from "./clickhouse/import/generator.js";
7
+ import { PostgresGenerator } from "./postgres/import/generator.js";
8
+ export const sqlTemplateGenerator = {
9
+ ownsLayout: true,
10
+ generate(ir) {
11
+ const postgres = ir.resources.some((r) => r.type.startsWith("Postgres::"));
12
+ return (postgres ? new PostgresGenerator() : new ClickHouseGenerator()).generate(ir);
13
+ },
14
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `chant import schema.sql`: a file of DDL into the import IR, in either
3
+ * dialect. `--parser-option dialect=postgres` (or `clickhouse`) names it;
4
+ * without one it is read off the statements: an `ENGINE =` clause, a
5
+ * `CREATE DATABASE`, a `CREATE DICTIONARY` or a lambda `CREATE FUNCTION f AS
6
+ * (x) -> ...` is ClickHouse, a statement
7
+ * only Postgres has (`CREATE SCHEMA`, `CREATE INDEX`, `COMMENT ON`, `ALTER
8
+ * TABLE`, `GRANT`, ...) or a `CREATE TABLE` with no engine is Postgres, and
9
+ * anything else is ClickHouse.
10
+ *
11
+ * A statement that declares nothing, or does not parse, is an error naming
12
+ * it (`./sql-files.ts`); none is left out. So is a `CREATE` of a kind chant
13
+ * cannot declare (#3911, `./coverage.ts`): `--parser-option
14
+ * skipUndeclarable` imports the rest and lists those as warnings. A clause the parser does not know
15
+ * (#3872) is kept in the template as written.
16
+ */
17
+ import { SQL_DIALECTS } from "./dialects.js";
18
+ import { readClickHouse } from "./files/clickhouse.js";
19
+ import { readPostgres } from "./files/postgres.js";
20
+ import { SqlFileError } from "./files/common.js";
21
+ import { objectsToIR as chObjectsToIR } from "./clickhouse/import/ir.js";
22
+ import { objectsToIR as pgObjectsToIR } from "./postgres/import/ir.js";
23
+ const CLICKHOUSE_ONLY = [
24
+ /\bENGINE\s*=/i,
25
+ /^\s*CREATE\s+(?:DATABASE|(?:OR\s+REPLACE\s+)?DICTIONARY)\b/im,
26
+ // A SQL function: CREATE FUNCTION f AS (x) -> ...
27
+ /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+[^\s(]+(?:\s+ON\s+CLUSTER\s+\S+)?\s+AS\s*\(/im,
28
+ // A row policy (#3711): Postgres says CREATE POLICY.
29
+ /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?ROW\s+POLICY\b/im,
30
+ ];
31
+ const POSTGRES_ONLY = [
32
+ /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:SCHEMA|EXTENSION|TYPE|DOMAIN|SEQUENCE|(?:UNIQUE\s+)?INDEX|(?:CONSTRAINT\s+)?TRIGGER|POLICY|ROLE|FUNCTION|PROCEDURE|(?:UNLOGGED|TEMP|TEMPORARY)\s+TABLE)\b/im,
33
+ /^\s*(?:COMMENT\s+ON|ALTER\s+TABLE|ALTER\s+DEFAULT\s+PRIVILEGES|GRANT|REVOKE|BEGIN)\b/im,
34
+ /^\s*CREATE\s+TABLE\b/im,
35
+ ];
36
+ /** The dialect a file of DDL is written in, read off its statements. */
37
+ export function guessDialect(content) {
38
+ const text = content.replace(/--[^\n]*/g, "");
39
+ if (CLICKHOUSE_ONLY.some((re) => re.test(text)))
40
+ return "clickhouse";
41
+ if (POSTGRES_ONLY.some((re) => re.test(text)))
42
+ return "postgres";
43
+ return "clickhouse";
44
+ }
45
+ /** The `dialect` parser option, checked. */
46
+ export function dialectOption(options) {
47
+ const d = options?.dialect;
48
+ if (d === undefined)
49
+ return undefined;
50
+ if (typeof d !== "string" || !SQL_DIALECTS.includes(d))
51
+ throw new Error(`--parser-option dialect: expected ${SQL_DIALECTS.join(" or ")}, got ${String(d)}`);
52
+ return d;
53
+ }
54
+ export class SqlFileParser {
55
+ constructor(dialect, options = {}) {
56
+ this.dialect = dialect;
57
+ this.options = options;
58
+ }
59
+ parse(content) {
60
+ const dialect = this.dialect ?? guessDialect(content);
61
+ const problems = [];
62
+ const skipped = [];
63
+ const read = { origin: "the DDL", ...(this.options.skipUndeclarable ? { undeclarable: skipped } : {}) };
64
+ const ir = dialect === "postgres" ? pgObjectsToIR(readPostgres(content, read, problems)) : chObjectsToIR(readClickHouse(content, read, problems));
65
+ if (problems.length > 0)
66
+ throw new SqlFileError(`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} DDL`, problems);
67
+ if (skipped.length === 0)
68
+ return ir;
69
+ return { ...ir, warnings: [...(ir.warnings ?? []), ...skipped.map((s) => `left out (skipUndeclarable): ${s}`)] };
70
+ }
71
+ }
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ // The sql lexicon. Each database dialect is a subpath:
2
+ // @intentius/chant-lexicon-sql/clickhouse
3
+ // @intentius/chant-lexicon-sql/postgres
4
+ // The root exports the plugin, the serializer and the dialect list.
5
+ export { sqlPlugin } from "./plugin.js";
6
+ export { sqlSerializer } from "./serializer.js";
7
+ export { SQL_DIALECTS } from "./dialects.js";
8
+ export { sqlConfigSchema } from "./config.js";
9
+ // The ClickHouse tags, also exported at the root because the plugin registers
10
+ // them as intrinsics (check-lexicon resolves a registered intrinsic against the
11
+ // root's exports). Declarations import them from the dialect subpath,
12
+ // `@intentius/chant-lexicon-sql/clickhouse`.
13
+ export { database, table, view, dictionary, user, role, policy, quota, settingsProfile, grant, literal } from "./clickhouse/entities.js";
14
+ // The Postgres tags whose names ClickHouse does not export, for the same reason.
15
+ // A tag folds as the function its file imports, so `table` imported from
16
+ // `@intentius/chant-lexicon-sql/postgres` is Postgres's. Declarations import
17
+ // them from the dialect subpath.
18
+ export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, collation, cast } from "./postgres/entities.js";
19
+ // A schema change's statements between two builds, rendered offline for a
20
+ // migration file (#3644); also at `@intentius/chant-lexicon-sql/migration-statements`.
21
+ export { diffStatements, renderStatements, } from "./migration-statements.js";
22
+ // Plain `.sql` files as a schema source (#3646): DDL read into the declarations
23
+ // the tagged templates make; also at `@intentius/chant-lexicon-sql/sql-files`.
24
+ export { readSqlFile, sqlFileDeclarations, sqlFileEntities, sqlFileOf, SqlFileError, } from "./sql-files.js";
@@ -0,0 +1,290 @@
1
+ const DATABASE = (name, comment) => `import { database } from "@intentius/chant-lexicon-sql/clickhouse";
2
+
3
+ export const ${name} = database\`
4
+ CREATE DATABASE ${name}
5
+ ENGINE = Atomic
6
+ COMMENT '${comment}'\`;
7
+ `;
8
+ // ── default ────────────────────────────────────────────────────────────
9
+ const DEFAULT_TABLES = `import { table } from "@intentius/chant-lexicon-sql/clickhouse";
10
+ import { shop } from "./database";
11
+
12
+ // Orders land here. The sort key is the on-disk order of every part, and it
13
+ // cannot be changed in place later, so choose it for the queries you run.
14
+ export const orders = table\`
15
+ CREATE TABLE \${shop}.orders (
16
+ id UInt64,
17
+ customer_id UInt64,
18
+ status LowCardinality(String),
19
+ total Decimal(18, 2),
20
+ created_at DateTime
21
+ )
22
+ ENGINE = MergeTree
23
+ PARTITION BY toYYYYMM(created_at)
24
+ ORDER BY (customer_id, created_at, id)\`;
25
+ `;
26
+ const DEFAULT_VIEWS = `import { view } from "@intentius/chant-lexicon-sql/clickhouse";
27
+ import { shop } from "./database";
28
+ import { orders } from "./orders";
29
+
30
+ // Columns are referenced through .columns, which records the view's lineage.
31
+ export const openOrders = view\`
32
+ CREATE VIEW \${shop}.open_orders AS
33
+ SELECT
34
+ \${orders.columns.id} AS id,
35
+ \${orders.columns.customer_id} AS customer_id,
36
+ \${orders.columns.total} AS total
37
+ FROM \${orders}
38
+ WHERE \${orders.columns.status} = 'open'\`;
39
+ `;
40
+ export const DEFAULT_TEMPLATE = {
41
+ src: {
42
+ "database.ts": DATABASE("shop", "Orders and customers"),
43
+ "orders.ts": DEFAULT_TABLES,
44
+ "views.ts": DEFAULT_VIEWS,
45
+ },
46
+ };
47
+ // ── events ─────────────────────────────────────────────────────────────
48
+ const EVENTS_TABLE = `import { table } from "@intentius/chant-lexicon-sql/clickhouse";
49
+ import { analytics } from "./database";
50
+
51
+ // Raw events, kept 90 days. The TTL is a background rewrite when changed
52
+ // (chant sql plan reports it as SQLCH205).
53
+ export const events = table\`
54
+ CREATE TABLE \${analytics}.events (
55
+ user_id UUID,
56
+ kind LowCardinality(String),
57
+ ts DateTime CODEC(Delta, ZSTD(3)),
58
+ props Map(String, String)
59
+ )
60
+ ENGINE = MergeTree
61
+ PARTITION BY toYYYYMM(ts)
62
+ ORDER BY (kind, user_id, ts)
63
+ TTL ts + INTERVAL 90 DAY\`;
64
+ `;
65
+ const EVENTS_ROLLUP = `import { table, view } from "@intentius/chant-lexicon-sql/clickhouse";
66
+ import { analytics } from "./database";
67
+ import { events } from "./events";
68
+
69
+ // Hourly counts and distinct users per kind. The rollup outlives the raw
70
+ // events: it has no TTL.
71
+ export const hourly = table\`
72
+ CREATE TABLE \${analytics}.events_hourly (
73
+ hour DateTime,
74
+ kind LowCardinality(String),
75
+ n SimpleAggregateFunction(sum, UInt64),
76
+ users AggregateFunction(uniq, UUID)
77
+ )
78
+ ENGINE = AggregatingMergeTree
79
+ PARTITION BY toYYYYMM(hour)
80
+ ORDER BY (kind, hour)\`;
81
+
82
+ // The target of a materialized view is fixed when the view is created.
83
+ export const hourlyMv = view\`
84
+ CREATE MATERIALIZED VIEW \${analytics}.events_hourly_mv TO \${hourly} AS
85
+ SELECT
86
+ toStartOfHour(\${events.columns.ts}) AS hour,
87
+ \${events.columns.kind} AS kind,
88
+ count() AS n,
89
+ uniqState(\${events.columns.user_id}) AS users
90
+ FROM \${events}
91
+ GROUP BY hour, kind\`;
92
+ `;
93
+ export const EVENTS_TEMPLATE = {
94
+ src: {
95
+ "database.ts": DATABASE("analytics", "Product analytics"),
96
+ "events.ts": EVENTS_TABLE,
97
+ "rollup.ts": EVENTS_ROLLUP,
98
+ },
99
+ };
100
+ // ── cdc ────────────────────────────────────────────────────────────────
101
+ const CDC_TABLE = `import { table, view } from "@intentius/chant-lexicon-sql/clickhouse";
102
+ import { mirror } from "./database";
103
+
104
+ // A mirror of an upstream customers table. The CDC feed inserts a row per
105
+ // change with a rising _version, and a delete as a row with _deleted = 1.
106
+ // ReplacingMergeTree keeps the highest _version per sort key at merge time,
107
+ // and the engine arguments cannot be changed in place later.
108
+ export const customers = table\`
109
+ CREATE TABLE \${mirror}.customers (
110
+ id UInt64,
111
+ email String,
112
+ plan LowCardinality(String) DEFAULT 'free',
113
+ _version UInt64,
114
+ _deleted UInt8 DEFAULT 0
115
+ )
116
+ ENGINE = ReplacingMergeTree(_version, _deleted)
117
+ ORDER BY id\`;
118
+
119
+ // Merges are eventual, so read through FINAL (or this view) for the current rows.
120
+ export const customersCurrent = view\`
121
+ CREATE VIEW \${mirror}.customers_current AS
122
+ SELECT
123
+ \${customers.columns.id} AS id,
124
+ \${customers.columns.email} AS email,
125
+ \${customers.columns.plan} AS plan
126
+ FROM \${customers} FINAL\`;
127
+ `;
128
+ export const CDC_TEMPLATE = {
129
+ src: {
130
+ "database.ts": DATABASE("mirror", "Replicated from the upstream database"),
131
+ "customers.ts": CDC_TABLE,
132
+ },
133
+ };
134
+ // ── Postgres ───────────────────────────────────────────────────────────
135
+ //
136
+ // `--template postgres`, `postgres-tenant` and `postgres-events`. Each builds
137
+ // with the sql lexicon alone and lints clean.
138
+ const PG_SCHEMA = (name, comment) => `import { schema } from "@intentius/chant-lexicon-sql/postgres";
139
+
140
+ export const ${name} = schema\`
141
+ CREATE SCHEMA ${name};
142
+ COMMENT ON SCHEMA ${name} IS '${comment}'\`;
143
+ `;
144
+ const PG_USERS = `import { table } from "@intentius/chant-lexicon-sql/postgres";
145
+ import { app } from "./schema";
146
+
147
+ export const users = table\`
148
+ CREATE TABLE \${app}.users (
149
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
150
+ email text NOT NULL UNIQUE,
151
+ created_at timestamptz NOT NULL DEFAULT now()
152
+ );
153
+ COMMENT ON TABLE \${app}.users IS 'One row per account'\`;
154
+ `;
155
+ const PG_ORDERS = `import { index, table } from "@intentius/chant-lexicon-sql/postgres";
156
+ import { app } from "./schema";
157
+ import { users } from "./users";
158
+
159
+ // The foreign key is a reference: orders is created after users.
160
+ export const orders = table\`
161
+ CREATE TABLE \${app}.orders (
162
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
163
+ user_id bigint NOT NULL REFERENCES \${users} (\${users.columns.id}) ON DELETE CASCADE,
164
+ status text NOT NULL DEFAULT 'placed',
165
+ amount numeric(12, 2) NOT NULL CHECK (amount >= 0),
166
+ placed_at timestamptz NOT NULL DEFAULT now()
167
+ )\`;
168
+
169
+ // Postgres does not index a foreign key for you. An index needs a name: the
170
+ // name is its identity on the server.
171
+ export const ordersUser = index\`
172
+ CREATE INDEX orders_user_id_idx ON \${orders} (\${orders.columns.user_id}, \${orders.columns.placed_at} DESC)\`;
173
+ `;
174
+ const PG_VIEWS = `import { view } from "@intentius/chant-lexicon-sql/postgres";
175
+ import { app } from "./schema";
176
+ import { orders } from "./orders";
177
+ import { users } from "./users";
178
+
179
+ // Columns are referenced through .columns, which records the view's lineage.
180
+ export const orderTotals = view\`
181
+ CREATE VIEW \${app}.order_totals WITH (security_invoker = true) AS
182
+ SELECT u.\${users.columns.id} AS user_id,
183
+ count(o.\${orders.columns.id}) AS order_count,
184
+ coalesce(sum(o.\${orders.columns.amount}), 0) AS total
185
+ FROM \${users} u
186
+ LEFT JOIN \${orders} o ON o.\${orders.columns.user_id} = u.\${users.columns.id}
187
+ GROUP BY u.\${users.columns.id}\`;
188
+ `;
189
+ export const POSTGRES_TEMPLATE = {
190
+ src: {
191
+ "schema.ts": PG_SCHEMA("app", "Accounts and orders"),
192
+ "users.ts": PG_USERS,
193
+ "orders.ts": PG_ORDERS,
194
+ "views.ts": PG_VIEWS,
195
+ },
196
+ };
197
+ const TENANT_TABLES = `import { table } from "@intentius/chant-lexicon-sql/postgres";
198
+ import { app } from "./schema";
199
+
200
+ export const tenants = table\`
201
+ CREATE TABLE \${app}.tenants (
202
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
203
+ slug text NOT NULL UNIQUE
204
+ )\`;
205
+
206
+ // Every tenant-owned table carries tenant_id and leads its primary key with it,
207
+ // so a tenant's rows sit together and the key doubles as the tenant's index.
208
+ export const projects = table\`
209
+ CREATE TABLE \${app}.projects (
210
+ tenant_id bigint NOT NULL REFERENCES \${tenants} (\${tenants.columns.id}) ON DELETE CASCADE,
211
+ id bigint GENERATED ALWAYS AS IDENTITY,
212
+ name text NOT NULL,
213
+ created_at timestamptz NOT NULL DEFAULT now(),
214
+ PRIMARY KEY (tenant_id, id)
215
+ )\`;
216
+ `;
217
+ const TENANT_TASKS = `import { index, table } from "@intentius/chant-lexicon-sql/postgres";
218
+ import { app } from "./schema";
219
+ import { projects } from "./tables";
220
+
221
+ // The foreign key carries the tenant too, so a task cannot point at another
222
+ // tenant's project.
223
+ export const tasks = table\`
224
+ CREATE TABLE \${app}.tasks (
225
+ tenant_id bigint NOT NULL,
226
+ id bigint GENERATED ALWAYS AS IDENTITY,
227
+ project_id bigint NOT NULL,
228
+ title text NOT NULL,
229
+ done boolean NOT NULL DEFAULT false,
230
+ PRIMARY KEY (tenant_id, id),
231
+ FOREIGN KEY (tenant_id, project_id) REFERENCES \${projects} (\${projects.columns.tenant_id}, \${projects.columns.id}) ON DELETE CASCADE
232
+ )\`;
233
+
234
+ // Every index leads with the tenant key.
235
+ export const tasksProject = index\`
236
+ CREATE INDEX tasks_project_idx ON \${tasks} (\${tasks.columns.tenant_id}, \${tasks.columns.project_id})\`;
237
+
238
+ export const tasksOpen = index\`
239
+ CREATE INDEX tasks_open_idx ON \${tasks} (\${tasks.columns.tenant_id}, \${tasks.columns.id}) WHERE NOT \${tasks.columns.done}\`;
240
+ `;
241
+ export const POSTGRES_TENANT_TEMPLATE = {
242
+ src: {
243
+ "schema.ts": PG_SCHEMA("app", "Multi-tenant application data"),
244
+ "tables.ts": TENANT_TABLES,
245
+ "tasks.ts": TENANT_TASKS,
246
+ },
247
+ };
248
+ const PG_EVENTS_TABLE = `import { index, table } from "@intentius/chant-lexicon-sql/postgres";
249
+ import { analytics } from "./schema";
250
+
251
+ // Partitioned by month. A primary key on a partitioned table must include the
252
+ // partition key, hence (id, occurred_at).
253
+ export const events = table\`
254
+ CREATE TABLE \${analytics}.events (
255
+ id bigint GENERATED ALWAYS AS IDENTITY,
256
+ occurred_at timestamptz NOT NULL,
257
+ kind text NOT NULL,
258
+ payload jsonb NOT NULL DEFAULT '{}',
259
+ PRIMARY KEY (id, occurred_at)
260
+ ) PARTITION BY RANGE (occurred_at)\`;
261
+
262
+ // An index on the parent is created on every partition.
263
+ export const eventsKind = index\`
264
+ CREATE INDEX events_kind_idx ON \${events} (\${events.columns.kind}, \${events.columns.occurred_at})\`;
265
+ `;
266
+ const PG_EVENTS_PARTITIONS = `import { table } from "@intentius/chant-lexicon-sql/postgres";
267
+ import { analytics } from "./schema";
268
+ import { events } from "./events";
269
+
270
+ // One partition per month, bounds [from, to). Add the next month ahead of time:
271
+ // a row with no partition is an error.
272
+ export const events202601 = table\`
273
+ CREATE TABLE \${analytics}.events_2026_01 PARTITION OF \${events}
274
+ FOR VALUES FROM ('2026-01-01') TO ('2026-02-01')\`;
275
+
276
+ export const events202602 = table\`
277
+ CREATE TABLE \${analytics}.events_2026_02 PARTITION OF \${events}
278
+ FOR VALUES FROM ('2026-02-01') TO ('2026-03-01')\`;
279
+
280
+ export const events202603 = table\`
281
+ CREATE TABLE \${analytics}.events_2026_03 PARTITION OF \${events}
282
+ FOR VALUES FROM ('2026-03-01') TO ('2026-04-01')\`;
283
+ `;
284
+ export const POSTGRES_EVENTS_TEMPLATE = {
285
+ src: {
286
+ "schema.ts": PG_SCHEMA("analytics", "Product analytics"),
287
+ "events.ts": PG_EVENTS_TABLE,
288
+ "partitions.ts": PG_EVENTS_PARTITIONS,
289
+ },
290
+ };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "df5c358d8382253ad577b461a81167e208a9fcb804524e8535b35efebe82aef6",
4
+ "manifest.json": "6d7e2a30284000b1667c910443a5ab73ed2944fb54df48a182652d2a2a3e4549",
5
5
  "meta.json": "59ca03876cce0d293e15a7f0a90ef7f250d133e46773c73fedede4a2ec792119",
6
6
  "types/index.d.ts": "0286ebb367773e84c76818b0f69cb14cff8328fd2f8c65ed87563c8d5815458c",
7
7
  "rules/postgres-templates.ts": "082da542239ab9f9627026972281102a61ca269cb79e2514f402dfc400ccdcde",
@@ -79,5 +79,5 @@
79
79
  "rules/sqlpg125.ts": "cc7411c57c525613dc720ba00e986bbc4e5a84b8b0bcfa7a6ce0d058f039f025",
80
80
  "rules/sqlpg126.ts": "d57b82a5842273876510131d67bf4418cbd61eff821e245edebc644dafd502bb"
81
81
  },
82
- "composite": "785ccb05885083f9fb1cab0c4c8042d34b27bce38ece2de492dae3ad0eaae27f"
82
+ "composite": "248cf32e3f60e2998233ca8e6d9d1ca29b5555fae247163559b4359bd67e4f4c"
83
83
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The sql lexicon's chant audit catalog, contributed via `auditCatalog()`.
3
+ *
4
+ * Post-synth checks read the build's JSON output, so they are `yamlBased`. The
5
+ * source-level rules read TypeScript and are constructed with
6
+ * `yamlBased: false`; they are listed for a reader who meets them in a report.
7
+ */
8
+ import { applyLineage, auditRule } from "@intentius/chant/audit/catalog";
9
+ import { sqlPostSynthAuditEntries } from "./post-synth/audit-entries.js";
10
+ import { postgresPostSynthAuditEntries } from "./post-synth/postgres-audit-entries.js";
11
+ import { sqlAuditLineage } from "./audit-lineage.js";
12
+ function sourceRule(id, category, title, remediation) {
13
+ return { id, tier: "merge-worthy", fixKind: "guidance", category, title, remediation, yamlBased: false };
14
+ }
15
+ export const sqlAuditCatalog = {
16
+ SQLCH001: sourceRule("SQLCH001", "correctness", "ClickHouse DDL in a database, table or view template does not parse", "Fix the statement at the token the message names."),
17
+ SQLCH002: sourceRule("SQLCH002", "correctness", "A Nullable column in a ClickHouse sort key or primary key", "Make the column non-Nullable, drop it from the key, or set allow_nullable_key = 1 on the table."),
18
+ SQLCH003: sourceRule("SQLCH003", "correctness", "A column interpolated without .columns reads the entity's own field instead", "Write ${table.columns.name}."),
19
+ SQLCH004: sourceRule("SQLCH004", "correctness", "A typed ClickHouse tag's type argument does not name the object its template declares, or is not in sql-types.d.ts", "Make the type argument the qualified name the template declares, then run chant sql types."),
20
+ SQLPG001: sourceRule("SQLPG001", "correctness", "Postgres DDL in a tagged template does not parse, or holds another statement than its tag", "Fix the statement at the token the message names, or use the tag for the statement it holds; name every index."),
21
+ SQLPG002: sourceRule("SQLPG002", "correctness", "A column interpolated without .columns reads the entity's own field instead", "Write ${table.columns.name}."),
22
+ SQLPG003: sourceRule("SQLPG003", "correctness", "A sequence or relation named in a regclass string makes no reference", "Interpolate the declared object: nextval(${sequence}) or ${relation}::regclass."),
23
+ SQLPG004: sourceRule("SQLPG004", "correctness", "A declared extension the configured Postgres provider does not allow", "Use an extension the provider lists, or set sql.provider to the service the project deploys to."),
24
+ SQLPG005: sourceRule("SQLPG005", "correctness", "A typed Postgres tag's type argument does not name the object its template declares, or is not in sql-types.d.ts", "Make the type argument the qualified name the template declares, then run chant sql types."),
25
+ SQLCH101: auditRule("SQLCH101", "merge-worthy", "guidance", "A ClickHouse object names an engine the pinned server does not have", "Use an engine from the pinned server's system.table_engines (or system.database_engines for a database).", { category: "correctness" }),
26
+ SQL101: auditRule("SQL101", "merge-worthy", "guidance", "Two exports declare the same object", "Keep one declaration of the object and import it where the other was used.", { category: "correctness" }),
27
+ SQL102: auditRule("SQL102", "merge-worthy", "guidance", "sql-types.d.ts differs from what the build declares", "Run chant sql types to rewrite the file.", { category: "correctness" }),
28
+ ...sqlPostSynthAuditEntries,
29
+ ...postgresPostSynthAuditEntries,
30
+ };
31
+ applyLineage(sqlAuditCatalog, sqlAuditLineage);
@@ -0,0 +1,22 @@
1
+ const squawk = (rule, relation) => ({ tool: "squawk", rule, url: `https://squawkhq.com/docs/${rule}`, relation });
2
+ const wiki = (rule, anchor) => ({
3
+ tool: "postgres-wiki-dont-do-this",
4
+ rule,
5
+ url: `https://wiki.postgresql.org/wiki/Don%27t_Do_This#${anchor}`,
6
+ relation: "overlaps",
7
+ });
8
+ export const sqlAuditLineage = {
9
+ SQLPG103: [squawk("prefer-identity", "equivalent"), wiki("Don't use serial", "Don.27t_use_serial")],
10
+ SQLPG104: [squawk("prefer-timestamptz", "equivalent"), wiki("Don't use timestamp (without time zone)", "Don.27t_use_timestamp_.28without_time_zone.29")],
11
+ SQLPG105: [
12
+ {
13
+ tool: "strong_migrations",
14
+ rule: "Adding a json column",
15
+ url: "https://github.com/ankane/strong_migrations#adding-a-json-column",
16
+ relation: "overlaps",
17
+ },
18
+ ],
19
+ SQLPG106: [squawk("ban-char-field", "equivalent"), wiki("Don't use char(n)", "Don.27t_use_char.28n.29")],
20
+ SQLPG107: [wiki("Don't use money", "Don.27t_use_money")],
21
+ SQLPG108: [squawk("prefer-text-field", "overlaps"), wiki("Don't use varchar(n) by default", "Don.27t_use_varchar.28n.29_by_default")],
22
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Audit metadata for the ClickHouse post-synth checks (SQLCH1xx). Spread into
3
+ * `sqlAuditCatalog` by ../audit-catalog.ts. Deprecation checks are filed under
4
+ * best-practice: the audit categories have no deprecation of their own.
5
+ */
6
+ import { auditRule } from "@intentius/chant/audit/catalog";
7
+ export const sqlPostSynthAuditEntries = {
8
+ SQLCH102: auditRule("SQLCH102", "merge-worthy", "guidance", "A PRIMARY KEY that is not a prefix of ORDER BY", "Make the primary key a leading prefix of the sort key, or drop it to use the sort key.", { category: "correctness" }),
9
+ SQLCH103: auditRule("SQLCH103", "merge-worthy", "guidance", "An engine's version, sign or is_deleted column has an unsupported type", "Use UInt*, Date, DateTime or DateTime64 for a version, Int8 for a sign, UInt8 for is_deleted.", { category: "correctness" }),
10
+ SQLCH104: auditRule("SQLCH104", "merge-worthy", "guidance", "A MergeTree engine argument names a column the table does not declare", "Declare the column, or fix the name in the engine arguments.", { category: "correctness" }),
11
+ SQLCH105: auditRule("SQLCH105", "merge-worthy", "guidance", "A table key clause names a column the table does not declare", "Declare the column, or fix the name in ORDER BY, PRIMARY KEY, PARTITION BY or SAMPLE BY.", { category: "correctness" }),
12
+ SQLCH106: auditRule("SQLCH106", "merge-worthy", "guidance", "A skip index expression names a column the table does not declare", "Declare the column, or fix the index expression.", { category: "correctness" }),
13
+ SQLCH107: auditRule("SQLCH107", "merge-worthy", "guidance", "A TTL expression is built on a column that is not a Date or DateTime", "Base the TTL on a Date or DateTime column, or convert it in the expression.", { category: "correctness" }),
14
+ SQLCH108: auditRule("SQLCH108", "merge-worthy", "guidance", "CREATE OR REPLACE TABLE in a database that is not Atomic", "Create the database with ENGINE = Atomic or Replicated, or drop OR REPLACE.", { category: "correctness" }),
15
+ SQLCH109: auditRule("SQLCH109", "report-only", "guidance", "A materialized view selects *", "List the columns the view writes.", { category: "best-practice" }),
16
+ SQLCH110: auditRule("SQLCH110", "report-only", "guidance", "A materialized view writes a column its TO target does not declare", "Add the column to the target table, or drop it from the SELECT.", { category: "correctness" }),
17
+ SQLCH111: auditRule("SQLCH111", "report-only", "guidance", "A String column limited to a few values is not LowCardinality", "Declare the column LowCardinality(String).", { category: "best-practice" }),
18
+ SQLCH112: auditRule("SQLCH112", "report-only", "guidance", "A PARTITION BY expression is finer than a day", "Partition by month (toYYYYMM) or day, and keep the finer time in ORDER BY.", { category: "best-practice" }),
19
+ SQLCH113: auditRule("SQLCH113", "merge-worthy", "guidance", "A MergeTree table declares no sort key", "Add ORDER BY, or ORDER BY tuple() for no sorting.", { category: "correctness" }),
20
+ SQLCH114: auditRule("SQLCH114", "merge-worthy", "guidance", "A column codec is not one the pinned server has", "Use a codec from the pinned server's system.codecs.", { category: "correctness" }),
21
+ SQLCH115: auditRule("SQLCH115", "report-only", "guidance", "A secret- or PII-named column carries no comment, TTL or encryption", "Add a COMMENT saying what it holds, a TTL that bounds retention, or an AES_*_GCM_SIV codec.", { category: "security" }),
22
+ SQLCH116: auditRule("SQLCH116", "report-only", "guidance", "A view is SQL SECURITY DEFINER with no DEFINER", "Write DEFINER = <user> before SQL SECURITY DEFINER.", { category: "security" }),
23
+ SQLCH117: auditRule("SQLCH117", "report-only", "guidance", "A MergeTree setting is obsolete at the pinned server", "Remove the setting; the server ignores it.", { category: "best-practice" }),
24
+ SQLCH118: auditRule("SQLCH118", "merge-worthy", "guidance", "A MergeTree setting is not one the pinned server has", "Fix the setting name, or pick a setting from the pinned server's system.merge_tree_settings.", { category: "correctness" }),
25
+ SQLCH119: auditRule("SQLCH119", "report-only", "guidance", "An object uses a deprecated or experimental engine", "Use the engine that replaced it (Atomic for Ordinary), or accept the experimental engine knowingly.", { category: "best-practice" }),
26
+ SQLCH120: auditRule("SQLCH120", "report-only", "guidance", "A MergeTree engine uses the deprecated positional arguments", "Write a bare MergeTree with ORDER BY, PARTITION BY and SETTINGS.", { category: "best-practice" }),
27
+ SQLCH121: auditRule("SQLCH121", "merge-worthy", "guidance", "A column type names a family the pinned server does not have", "Use a type family from the pinned server's system.data_type_families, in the case it lists unless it is case-insensitive.", { category: "correctness" }),
28
+ SQLCH122: auditRule("SQLCH122", "merge-worthy", "guidance", "A column type's parameters do not fit its family", "Give the family the parameters it takes, each within its range.", { category: "correctness" }),
29
+ SQLCH123: auditRule("SQLCH123", "merge-worthy", "guidance", "A Nullable wraps a type ClickHouse does not allow inside Nullable", "Move Nullable inside (Array(Nullable(T)), LowCardinality(Nullable(T))) or drop it.", { category: "correctness" }),
30
+ SQLCH124: auditRule("SQLCH124", "merge-worthy", "guidance", "A table declares a column twice", "Remove or rename one of the two columns.", { category: "correctness" }),
31
+ SQLCH125: auditRule("SQLCH125", "merge-worthy", "guidance", "A codec parameter is outside what the codec takes", "Give the codec a parameter within its range or set, or drop the parameter for the default.", { category: "correctness" }),
32
+ SQLCH126: auditRule("SQLCH126", "merge-worthy", "guidance", "A GRANT column list names a column the table does not declare", "Fix the column name, or declare the column on the table.", { category: "correctness" }),
33
+ SQLCH127: auditRule("SQLCH127", "merge-worthy", "guidance", "An expression calls a function the pinned server does not have", "Fix the function name, use one from the pinned server's system.functions, or declare it with func.", { category: "correctness" }),
34
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Reading the ClickHouse objects in the sql build output for post-synth
3
+ * checks: typed views of the serialized objects and a few scanners for the
4
+ * expression text the parser keeps as written (sort keys, TTLs, index
5
+ * expressions). The scanners are deliberately small: they answer "is this a
6
+ * bare column name" and "which names does this expression mention", not "what
7
+ * does this expression evaluate to".
8
+ */
9
+ import { baseEngineName, splitTop } from "../../clickhouse/expressions.js";
10
+ import { readClickHouse } from "../../read/clickhouse.js";
11
+ import { clickhouseObjects } from "./sql-helpers.js";
12
+ export { bareName, baseEngineName, engineSpec, isMergeTree, isNullableType, keyElements, splitTop, typeFamily, unwrapType, } from "../../clickhouse/expressions.js";
13
+ export const isTable = (o) => o.type === "ClickHouse::Table";
14
+ export const isMaterializedView = (o) => o.type === "ClickHouse::MaterializedView";
15
+ export const isAnyView = (o) => o.type === "ClickHouse::View" || o.type === "ClickHouse::MaterializedView";
16
+ /** The build's tables as the public read API types them (`@intentius/chant-lexicon-sql/read`), so a project policy reads what the checks read. */
17
+ export function tablesOf(ctx) {
18
+ return readClickHouse(ctx).tables;
19
+ }
20
+ /** The build's views and materialized views, as the public read API types them. */
21
+ export function viewsOf(ctx) {
22
+ return readClickHouse(ctx).views;
23
+ }
24
+ /**
25
+ * The engine's own arguments, after the ZooKeeper path and replica name a
26
+ * `Replicated*` engine may lead with.
27
+ */
28
+ export function engineArguments(engine) {
29
+ const args = engine.args ?? [];
30
+ if (baseEngineName(engine) !== engine.name && args.length >= 2 && /^['{]/.test(args[0].trim()))
31
+ return args.slice(2);
32
+ return args;
33
+ }
34
+ const WORDS = new Set(("AS AND OR NOT IN IS NULL LIKE ILIKE BETWEEN CASE WHEN THEN ELSE END ASC DESC INTERVAL DISTINCT TRUE FALSE " +
35
+ "SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR NANOSECOND MICROSECOND MILLISECOND CAST").split(" "));
36
+ /** Names an expression mentions as columns: identifiers that are not calls, literals or keywords. */
37
+ export function mentionedNames(expr) {
38
+ const names = [];
39
+ const re = /'(?:[^'\\]|\\.)*'|`((?:[^`]|``)*)`|"((?:[^"]|"")*)"|\b([A-Za-z_][A-Za-z0-9_]*)\b(\s*\()?|\d[\w.]*/g;
40
+ let m;
41
+ let afterDot = false;
42
+ let last = 0;
43
+ while ((m = re.exec(expr)) !== null) {
44
+ afterDot = /\.\s*$/.test(expr.slice(last, m.index));
45
+ last = m.index + m[0].length;
46
+ const name = m[1] ?? m[2] ?? m[3];
47
+ if (name === undefined || m[4] !== undefined || afterDot)
48
+ continue;
49
+ if (m[3] !== undefined && WORDS.has(m[3].toUpperCase()))
50
+ continue;
51
+ names.push(name.replace(/``|""/g, (x) => x[0]));
52
+ }
53
+ return names;
54
+ }
55
+ export function columnByName(table, name) {
56
+ return table.columns.find((c) => c.name === name);
57
+ }
58
+ /** A check over the build's ClickHouse objects, so each check file holds its rule. */
59
+ export function checkOf(meta, run) {
60
+ return {
61
+ ...meta,
62
+ check(ctx) {
63
+ const out = [];
64
+ run(ctx, (d) => out.push({ checkId: meta.id, lexicon: "sql", ...d }));
65
+ return out;
66
+ },
67
+ };
68
+ }
69
+ /** The objects by export name and by SQL name, to resolve a `TO` target. */
70
+ export function resolveObject(ctx, ref) {
71
+ const all = clickhouseObjects(ctx);
72
+ return all.find((o) => o.export === ref) ?? all.find((o) => o.sqlName === ref || o.name === ref);
73
+ }
74
+ /** The codec names in a CODEC(...) list: `Delta, ZSTD(3)` is Delta and ZSTD. */
75
+ export function codecNames(codec) {
76
+ return splitTop(codec)
77
+ .map((c) => /^[A-Za-z_][\w]*/.exec(c.trim())?.[0])
78
+ .filter((n) => n !== undefined);
79
+ }