@intentius/chant-lexicon-sql 0.124.31 → 0.124.32

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,206 @@
1
+ /**
2
+ * Plain `.sql` files as a schema source (#3646): DDL read into the same
3
+ * declarations the tagged templates make. Also at the package root.
4
+ *
5
+ * - {@link readSqlFile}: the objects a file of DDL declares, each with its
6
+ * export name, type, qualified name and DDL.
7
+ * - {@link sqlFileDeclarations}: the same file as TypeScript, one `schema.ts`
8
+ * of `table`, `view`, ... templates, as `chant import <file>.sql` writes it.
9
+ * - {@link sqlFileEntities}: the declarations themselves, with the references
10
+ * between the files' objects, and to objects declared elsewhere, as
11
+ * interpolations, so dependency order comes from the graph. The build uses
12
+ * it for every `.sql` file in the source directory.
13
+ *
14
+ * Postgres reads each `CREATE`, `GRANT`, `REVOKE` and `ALTER DEFAULT
15
+ * PRIVILEGES` as an object, with `COMMENT ON`, `ALTER TABLE ... ROW LEVEL
16
+ * SECURITY` and `ALTER TABLE ... ADD <table constraint>` folded into the
17
+ * object they finish; ClickHouse reads `CREATE DATABASE`, `TABLE`, `VIEW`,
18
+ * `MATERIALIZED VIEW`, `DICTIONARY`, `FUNCTION`, `USER`, `ROLE` and `ROW
19
+ * POLICY`, and `GRANT` (#3711). Any other statement, and any
20
+ * statement that does not parse, throws a {@link SqlFileError} naming every
21
+ * such statement: none is left out. A statement that parses except for a
22
+ * clause the parser does not know (#3872) is read, kept as written, and the
23
+ * clause listed in its object's `unparsed` with the line it is on.
24
+ *
25
+ * A name is a reference where it is qualified (`app.users`), or bare where an
26
+ * object is named (`REFERENCES users`, `FROM users`, an index's table, a
27
+ * column of an enum type) and an object declared without a schema has it.
28
+ */
29
+ import { SqlFileError } from "./files/common.js";
30
+ import { postgresTag, readPostgres, templateStrings } from "./files/postgres.js";
31
+ import { clickhouseTag, readClickHouse } from "./files/clickhouse.js";
32
+ import { exportNames as pgExportNames, objectsToIR as pgObjectsToIR } from "./postgres/import/ir.js";
33
+ import { exportNames as chExportNames, objectsToIR as chObjectsToIR } from "./clickhouse/import/ir.js";
34
+ import { PostgresGenerator, postgresTemplates } from "./postgres/import/generator.js";
35
+ import { ClickHouseGenerator, clickhouseTemplates } from "./clickhouse/import/generator.js";
36
+ import { isPostgresObject } from "./postgres/entities.js";
37
+ import { isClickHouseObject } from "./clickhouse/entities.js";
38
+ export { SqlFileError };
39
+ const qualifierOf = (o) => ("schema" in o ? o.schema : "database" in o ? o.database : undefined);
40
+ const qualifiedName = (o) => (qualifierOf(o) ? `${qualifierOf(o)}.${o.name}` : o.name);
41
+ function readObjects(dialect, sources) {
42
+ const out = [];
43
+ for (const source of sources) {
44
+ const problems = [];
45
+ const objects = dialect === "postgres" ? readPostgres(source.ddl, source, problems) : readClickHouse(source.ddl, source, problems);
46
+ if (problems.length > 0)
47
+ throw new SqlFileError(source.origin, problems);
48
+ out.push(...objects.map((o) => ({ ...o, origin: source.origin })));
49
+ }
50
+ return out;
51
+ }
52
+ /** An object's line and unknown clauses, the fields that are set. */
53
+ const fileMeta = (o) => ({ ...(o.line !== undefined ? { line: o.line } : {}), ...(o.unparsed ? { unparsed: o.unparsed } : {}) });
54
+ /** Export names for objects read from files, unique across them. */
55
+ function namesFor(dialect, objects) {
56
+ return dialect === "postgres" ? pgExportNames(objects) : chExportNames(objects);
57
+ }
58
+ /** The objects a file of DDL declares. Throws a {@link SqlFileError} naming each statement it cannot read. */
59
+ export function readSqlFile(dialect, ddl, options) {
60
+ const objects = readObjects(dialect, [{ ...options, ddl }]);
61
+ const names = namesFor(dialect, objects);
62
+ return objects.map((o, i) => ({ export: names[i], type: o.type, name: qualifiedName(o), ddl: o.ddl, origin: o.origin, ...fileMeta(o) }));
63
+ }
64
+ /**
65
+ * A file of DDL as TypeScript declarations: the `schema.ts` `chant import`
66
+ * writes, with `header` as comment lines at its top, and the objects in it.
67
+ */
68
+ export function sqlFileDeclarations(dialect, ddl, options) {
69
+ const objects = readObjects(dialect, [{ ...options, ddl }]);
70
+ if (objects.length === 0)
71
+ throw new SqlFileError(options.origin, ["the DDL creates no objects"]);
72
+ const ir = dialect === "postgres" ? pgObjectsToIR(objects) : chObjectsToIR(objects);
73
+ const files = dialect === "postgres" ? new PostgresGenerator(options.header).generate(ir) : new ClickHouseGenerator(options.header).generate(ir);
74
+ const [file] = files;
75
+ if (!file)
76
+ throw new SqlFileError(options.origin, ["the DDL creates no objects"]);
77
+ return {
78
+ content: file.content,
79
+ objects: ir.resources.map((r, i) => ({ export: r.logicalId, type: r.type, name: qualifiedName(objects[i]), ddl: objects[i].ddl, origin: options.origin, ...fileMeta(objects[i]) })),
80
+ };
81
+ }
82
+ /** Where an entity read from a `.sql` file came from: its `origin`. Undefined for any other entity. */
83
+ export function sqlFileOf(entity) {
84
+ return typeof entity === "object" && entity !== null ? entity[FROM_FILE] : undefined;
85
+ }
86
+ /** The line an entity read from a `.sql` file starts on in it. Undefined for any other entity. */
87
+ export function sqlFileLineOf(entity) {
88
+ return typeof entity === "object" && entity !== null ? entity[FROM_LINE] : undefined;
89
+ }
90
+ const FROM_FILE = Symbol.for("chant.sql.fromFile");
91
+ const FROM_LINE = Symbol.for("chant.sql.fromLine");
92
+ /** A declared entity as a template target: its export, type and name. */
93
+ function targetOf(dialect, exportName, entity) {
94
+ const p = entity.props;
95
+ if (typeof p.name !== "string")
96
+ return undefined;
97
+ const qualifier = dialect === "postgres" ? p.schema : p.database;
98
+ return {
99
+ exportName,
100
+ type: entity.entityType,
101
+ ...(qualifier ? (dialect === "postgres" ? { schema: qualifier } : { database: qualifier }) : {}),
102
+ name: p.name,
103
+ ddl: typeof p.ddl === "string" ? p.ddl : "",
104
+ };
105
+ }
106
+ function templates(dialect, items, targets) {
107
+ return dialect === "postgres" ? postgresTemplates(items, targets) : clickhouseTemplates(items, targets);
108
+ }
109
+ /** The entities of one dialect in a set, by export name. */
110
+ function ofDialect(dialect, entities) {
111
+ const is = dialect === "postgres" ? isPostgresObject : isClickHouseObject;
112
+ return new Map([...(entities ?? new Map())].filter(([, e]) => is(e)));
113
+ }
114
+ /**
115
+ * The declarations the files make, by export name. `known` holds the
116
+ * entities declared elsewhere (the project's tagged templates): a file's
117
+ * reference to one of them is an interpolation of it, as an import would
118
+ * write it, and an export name or an object one of them already declares is
119
+ * an error naming both.
120
+ */
121
+ export function sqlFileEntities(dialect, sources, options = {}) {
122
+ const objects = readObjects(dialect, sources);
123
+ const names = namesFor(dialect, objects);
124
+ const known = ofDialect(dialect, options.known);
125
+ const problems = [];
126
+ const items = objects.map((o, i) => ({ exportName: names[i], type: o.type, ...("schema" in o && o.schema ? { schema: o.schema } : {}), ...("database" in o && o.database ? { database: o.database } : {}), name: o.name, ddl: o.ddl }));
127
+ const origin = new Map(objects.map((o, i) => [names[i], o.origin]));
128
+ const lineOf = new Map(objects.map((o, i) => [names[i], o.line]));
129
+ const knownItems = [...known].map(([n, e]) => targetOf(dialect, n, e)).filter((t) => t !== undefined);
130
+ const declared = new Map(knownItems.map((t) => [`${t.type} ${qualifiedName(t)}`, t.exportName]));
131
+ const seen = new Map();
132
+ const where = (at) => (sources.length > 1 && at ? `${at}: ` : "");
133
+ const label = sources.length === 1 ? sources[0].origin : "the .sql files";
134
+ for (const [i, it] of items.entries()) {
135
+ const what = `${it.type} ${qualifiedName(it)}`;
136
+ const at = objects[i].origin;
137
+ const q = qualifiedName(it);
138
+ if (options.known?.has(it.exportName))
139
+ problems.push(`${where(at)}${q} is exported as ${it.exportName}, a name another declaration already has; rename that one`);
140
+ const other = declared.get(what);
141
+ if (other)
142
+ problems.push(`${where(at)}${q} is declared twice, here and as ${other}; leave it to one of them`);
143
+ const again = seen.get(what);
144
+ if (again)
145
+ problems.push(`${where(at)}${q} is declared twice, here and in ${again}; leave it to one of them`);
146
+ seen.set(what, at);
147
+ }
148
+ if (problems.length > 0)
149
+ throw new SqlFileError(label, problems);
150
+ const { order, bodies } = templates(dialect, items, [...items, ...knownItems]);
151
+ const built = new Map();
152
+ const failed = new Set();
153
+ const tagOf = dialect === "postgres" ? postgresTag : clickhouseTag;
154
+ for (const it of order) {
155
+ const body = bodies.get(it.exportName);
156
+ const values = body.refs.map((r) => built.get(r) ?? known.get(r));
157
+ const missing = body.refs.find((_, i) => values[i] === undefined);
158
+ if (missing !== undefined) {
159
+ failed.add(it.exportName);
160
+ // What references an object that failed is not reported again.
161
+ if (failed.has(missing))
162
+ continue;
163
+ problems.push(`${where(origin.get(it.exportName))}${qualifiedName(it)} and ${missing} reference each other, which no declaration can; break the cycle`);
164
+ continue;
165
+ }
166
+ try {
167
+ const entity = tagOf(it.type)(templateStrings(body.parts), ...values);
168
+ Object.defineProperty(entity, FROM_FILE, { value: origin.get(it.exportName), enumerable: false });
169
+ if (lineOf.get(it.exportName) !== undefined)
170
+ Object.defineProperty(entity, FROM_LINE, { value: lineOf.get(it.exportName), enumerable: false });
171
+ built.set(it.exportName, entity);
172
+ }
173
+ catch (e) {
174
+ failed.add(it.exportName);
175
+ problems.push(`${where(origin.get(it.exportName))}${qualifiedName(it)}: ${e instanceof Error ? e.message : String(e)}`);
176
+ }
177
+ }
178
+ if (problems.length > 0)
179
+ throw new SqlFileError(label, problems);
180
+ // In the files' order, not the dependency order.
181
+ return new Map(items.filter((it) => built.has(it.exportName)).map((it) => [it.exportName, built.get(it.exportName)]));
182
+ }
183
+ /**
184
+ * The objects read from `.sql` files that each other object names in its
185
+ * DDL as text, by export name: a tagged template cannot import a file's
186
+ * object, so it names it, and this is the edge the dependency order needs.
187
+ * Empty when no object came from a file.
188
+ */
189
+ export function sqlFileReferences(dialect, objects) {
190
+ const fromFiles = [...objects].filter(([, e]) => sqlFileOf(e) !== undefined);
191
+ const out = new Map();
192
+ if (fromFiles.length === 0)
193
+ return out;
194
+ const targets = fromFiles.map(([n, e]) => targetOf(dialect, n, e)).filter((t) => t !== undefined);
195
+ for (const [name, entity] of objects) {
196
+ if (sqlFileOf(entity) !== undefined)
197
+ continue;
198
+ const item = targetOf(dialect, name, entity);
199
+ if (!item || !item.ddl)
200
+ continue;
201
+ const refs = templates(dialect, [item], targets).bodies.get(name)?.refs ?? [];
202
+ if (refs.length > 0)
203
+ out.set(name, [...new Set(refs)]);
204
+ }
205
+ return out;
206
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `chant sql types [--output <path>] [--check]` (#3751): build the project the way
3
+ * `chant build` does, folded, and write the declarations
4
+ * {@link renderSqlTypes} renders from its entities, so a tag given its
5
+ * qualified name as the type argument has typed columns.
6
+ *
7
+ * The file goes to `<sourceDir>/sql-types.d.ts` (the project root when
8
+ * `sourceDir` is unset), where any tsconfig that includes the schema includes
9
+ * it too. `--output` puts it elsewhere, as a workspace `generated` entry's
10
+ * generator does. The command refuses to write beside a `sql-types.ts`: tsc
11
+ * resolves the `.ts` first and would drop the declarations. `chant build`
12
+ * never writes the file. `--check` renders the same text and compares it with
13
+ * the file instead of writing it: exit 1, naming the file, when they differ.
14
+ */
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { basename, dirname, join, relative, resolve } from "node:path";
17
+ import { renderSqlTypes, SQL_TYPES_FILE } from "./sql-types.js";
18
+ const USAGE = "usage: chant sql types [--output <path>] [--check]";
19
+ /** The `--output` value, or an error message. */
20
+ function parseArgs(args) {
21
+ let output;
22
+ let check = false;
23
+ for (let i = 0; i < args.length; i++) {
24
+ const a = args[i];
25
+ if (a === "--output") {
26
+ output = args[++i];
27
+ if (!output)
28
+ return { error: "--output needs a path" };
29
+ }
30
+ else if (a === "--check")
31
+ check = true;
32
+ else if (a.startsWith("--output="))
33
+ output = a.slice("--output=".length);
34
+ else
35
+ return { error: `unknown argument ${a}` };
36
+ }
37
+ return output === undefined ? { check } : { output, check };
38
+ }
39
+ /** The file tsc would read instead of `path`: `sql-types.ts` beside `sql-types.d.ts`. */
40
+ export function shadowingSource(path) {
41
+ if (!path.endsWith(".d.ts"))
42
+ return undefined;
43
+ const source = `${path.slice(0, -".d.ts".length)}.ts`;
44
+ return existsSync(source) ? source : undefined;
45
+ }
46
+ export async function runTypes(ctx) {
47
+ const args = parseArgs(ctx.rawArgs);
48
+ if ("error" in args) {
49
+ console.error(`${args.error}\n${USAGE}`);
50
+ return 1;
51
+ }
52
+ const cwd = process.cwd();
53
+ const { loadChantConfigUpward } = await import("@intentius/chant/config");
54
+ const { config, configPath } = await loadChantConfigUpward(cwd);
55
+ const projectDir = configPath ? dirname(configPath) : cwd;
56
+ const sourceDir = resolve(projectDir, config.sourceDir ?? ".");
57
+ const output = args.output !== undefined ? resolve(cwd, args.output) : join(sourceDir, SQL_TYPES_FILE);
58
+ const shadow = shadowingSource(output);
59
+ if (shadow) {
60
+ console.error(`chant sql types: ${relative(cwd, shadow) || basename(shadow)} sits beside ${relative(cwd, output) || basename(output)}, and tsc reads it instead of the declarations; rename one`);
61
+ return 1;
62
+ }
63
+ const { build } = await import("@intentius/chant/build");
64
+ const { resolveBuildModes, resolveProjectBuildOptions } = await import("@intentius/chant/cli/build-options");
65
+ const { loadPlugins, resolveProjectLexicons } = await import("@intentius/chant/cli/plugins");
66
+ const { sqlSerializer } = await import("./serializer.js");
67
+ const plugins = await loadPlugins(await resolveProjectLexicons(projectDir, { lexicon: "sql" }));
68
+ // The fold build's entities: what `chant build` declares, `.sql` files and composite members included.
69
+ const options = resolveProjectBuildOptions({ config, configDir: projectDir, plugins, modes: resolveBuildModes(config, { fold: true }) });
70
+ const result = await build(sourceDir, [sqlSerializer], undefined, options);
71
+ if (result.errors.length > 0) {
72
+ for (const e of result.errors)
73
+ console.error(e instanceof Error ? e.message : String(e));
74
+ console.error("chant sql types: the build failed, so nothing was written");
75
+ return 1;
76
+ }
77
+ const text = renderSqlTypes(result.entities, { projectDir, config: config });
78
+ const shown = relative(cwd, output);
79
+ const named = shown && !shown.startsWith("..") ? shown : output;
80
+ if (args.check) {
81
+ // Compares only; the file is never written.
82
+ const onDisk = existsSync(output) ? readFileSync(output, "utf8") : undefined;
83
+ if (onDisk === text) {
84
+ console.log(`${named} is current`);
85
+ return 0;
86
+ }
87
+ console.error(onDisk === undefined
88
+ ? `chant sql types: ${named} does not exist; run \`chant sql types\` to write it`
89
+ : `chant sql types: ${named} differs from what the build declares; run \`chant sql types\` to rewrite it`);
90
+ return 1;
91
+ }
92
+ mkdirSync(dirname(output), { recursive: true });
93
+ writeFileSync(output, text);
94
+ const count = (text.match(/^ {4}"/gm) ?? []).length;
95
+ console.log(`wrote ${named}: ${count} relation${count === 1 ? "" : "s"}`);
96
+ return 0;
97
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * The declarations `chant sql types` writes (#3751): one `sql-types.d.ts`
3
+ * that augments the dialect's `SqlSchema` with every table, view and
4
+ * dictionary the build declares, keyed by qualified name.
5
+ *
6
+ * {@link renderSqlTypes} is pure: the entities of a build in, the file's text
7
+ * out. It reads the objects as the serializer writes them, so a `.sql`
8
+ * file's objects and a composite's members are covered exactly as the build
9
+ * covers them, and it reads each template's slot record (`core/typed.ts`) for
10
+ * the `${}` positions a foreign key or a sort key consumes.
11
+ *
12
+ * Each column is a `Column<sqlType, { ts, nullable, cmp }>`:
13
+ *
14
+ * - `sqlType` is the type as the catalog spells it (`bigint`,
15
+ * `timestamp(3) with time zone`, `Nullable(String)`);
16
+ * - `ts` is its value in application code (`./postgres/row-types.ts`,
17
+ * `./clickhouse/row-types.ts`);
18
+ * - `nullable` is whether the value can be NULL. A Postgres view's columns
19
+ * always can, as the server's own catalog says; a column whose type the
20
+ * generator cannot tell is `never`, which fits every slot;
21
+ * - `cmp` is how it compares in a key: SQLPG124's type identity in Postgres
22
+ * (`./lint/post-synth/postgres-names.ts`'s `typeIdentity`), the type
23
+ * family in ClickHouse; `never` when it cannot be told.
24
+ *
25
+ * A column that carries a clause chant does not read (#3872) keeps the type
26
+ * that was read, with `nullable` and `cmp` `never`.
27
+ *
28
+ * A view's column that is one column reference (`${t.columns.c}`, `o.${t.columns.c}`)
29
+ * is that column's type; any other expression is `unknown`.
30
+ */
31
+ import { existsSync, readFileSync } from "node:fs";
32
+ import { join, relative, resolve, sep } from "node:path";
33
+ import { GENERATED_MARKER } from "@intentius/chant/discovery/files";
34
+ import { getProvenance } from "@intentius/chant/provenance";
35
+ import { isPostgresObject } from "./postgres/entities.js";
36
+ import { isClickHouseObject, quoteIdentifier } from "./clickhouse/entities.js";
37
+ import { quoteIdent } from "./postgres/keywords.js";
38
+ import { getTypedSlots, relationKey } from "./core/typed.js";
39
+ import { sqlSerializer } from "./serializer.js";
40
+ import { sqlFileOf } from "./sql-files.js";
41
+ import { filesDialect } from "./files/build-root.js";
42
+ import { POSTGRES_ROW_TYPES } from "./postgres/row-types.js";
43
+ import { clickhouseRowType } from "./clickhouse/row-types.js";
44
+ import { typeFamily } from "./lint/post-synth/clickhouse-helpers.js";
45
+ import { PINNED_MAJOR } from "./lint/post-synth/postgres-helpers.js";
46
+ import { parseType, projectFromObjects, resolveType, typeIdentity } from "./lint/post-synth/postgres-names.js";
47
+ /** The file `chant sql types` writes, in the project's source directory. */
48
+ export const SQL_TYPES_FILE = "sql-types.d.ts";
49
+ /**
50
+ * Where `chant sql types` writes the file by default: the project's
51
+ * `sourceDir`, or the project root when it is unset.
52
+ */
53
+ export function sqlTypesPath(projectDir, config) {
54
+ const sourceDir = typeof config?.sourceDir === "string" ? config.sourceDir : ".";
55
+ return join(resolve(projectDir, sourceDir), SQL_TYPES_FILE);
56
+ }
57
+ /** The file on disk against what the build's entities render: `missing`, `stale` or `current`. */
58
+ export function sqlTypesState(entities, file, opts = {}) {
59
+ if (!existsSync(file))
60
+ return "missing";
61
+ let onDisk;
62
+ try {
63
+ onDisk = readFileSync(file, "utf8");
64
+ }
65
+ catch {
66
+ return "missing";
67
+ }
68
+ return onDisk === renderSqlTypes(entities, opts) ? "current" : "stale";
69
+ }
70
+ /** The module each dialect's `SqlSchema` is augmented in. */
71
+ export const SQL_TYPES_MODULE = {
72
+ postgres: "@intentius/chant-lexicon-sql/postgres",
73
+ clickhouse: "@intentius/chant-lexicon-sql/clickhouse",
74
+ };
75
+ const UNKNOWN_COLUMN = (name) => ({ name, sqlType: "unknown", ts: "unknown", nullable: "never" });
76
+ /** The columns that carry a clause chant does not read (#3872). */
77
+ const opaqueColumns = (o) => new Set((o.unparsed ?? []).flatMap((u) => (u.column !== undefined ? [u.column] : [])));
78
+ /**
79
+ * A column with a clause chant does not read (#3872): its type is what was
80
+ * read, but whether it can be NULL and how it compares in a key are not
81
+ * told, so it fits every slot rather than a guessed one.
82
+ */
83
+ const opaque = (c) => ({ name: c.name, sqlType: c.sqlType, ts: c.ts, nullable: "never" });
84
+ const posix = (p) => p.split(sep).join("/");
85
+ /** Where a declaration came from: its file relative to the project, and its export. */
86
+ function originOf(entity, exportName, projectDir) {
87
+ const prov = getProvenance(entity);
88
+ const file = sqlFileOf(entity) ?? (prov?.sourceFile ? relative(projectDir, prov.sourceFile) : undefined);
89
+ const composite = prov?.composite ? ` (${prov.composite})` : "";
90
+ return `${file ? posix(file) : "?"}: ${exportName}${composite}`;
91
+ }
92
+ /** The names a relation's `.columns` record holds at run time, in order. */
93
+ const runtimeColumns = (entity) => Object.keys(entity?.columns ?? {});
94
+ /** Whether `expr` is the column `column` alone, bare or qualified (`o.user_id`). */
95
+ function isBareColumn(expr, quoted) {
96
+ const unqualified = expr.trim().replace(/^(?:"(?:[^"]|"")+"|`(?:[^`]|``)+`|[A-Za-z_][\w$]*)\./, "");
97
+ return unqualified === quoted;
98
+ }
99
+ /** A view's column that passes one column through: that column's export and name. */
100
+ function passthrough(o, name, quote) {
101
+ const edge = (o.lineage ?? []).find((e) => e.output === name);
102
+ const ref = edge?.from.length === 1 ? edge.from[0] : undefined;
103
+ if (!edge || !ref)
104
+ return undefined;
105
+ const dot = ref.indexOf(".");
106
+ if (dot < 0)
107
+ return undefined;
108
+ const column = ref.slice(dot + 1);
109
+ return isBareColumn(edge.expr, quote(column)) ? { export: ref.slice(0, dot), column } : undefined;
110
+ }
111
+ // ── Postgres ────────────────────────────────────────────────────────────
112
+ const PG_RELATIONS = new Set(["Postgres::Table", "Postgres::View", "Postgres::MaterializedView"]);
113
+ /** A built-in type spelled as `format_type()` spells it, with its modifiers. */
114
+ function spell(canonical, t) {
115
+ let s = canonical;
116
+ if (t.fields)
117
+ s = `${s} ${t.fields.toLowerCase()}`;
118
+ if (t.modifiers && t.modifiers.length > 0) {
119
+ const mods = `(${t.modifiers.join(",")})`;
120
+ const zoned = /^(time|timestamp)( with(?:out)? time zone)$/.exec(s);
121
+ s = zoned ? `${zoned[1]}${mods}${zoned[2]}` : `${s}${mods}`;
122
+ }
123
+ return s;
124
+ }
125
+ const arrayOf = (ts) => `${/[|&]/.test(ts) && !/^\{.*\}$/s.test(ts) ? `(${ts})` : ts}[]`;
126
+ /** A Postgres type's spelling, row type, and whether the type itself forbids NULL (a serial, a NOT NULL domain). */
127
+ function pgType(p, text, depth = 0) {
128
+ const t = parseType(text);
129
+ if (!t || depth > 5)
130
+ return { sqlType: text?.trim() || "unknown", ts: "unknown", notNull: false };
131
+ const r = resolveType(p, t, { serial: true });
132
+ const array = (x) => (t.array ? { sqlType: `${x.sqlType}[]`, ts: arrayOf(x.ts), notNull: x.notNull } : x);
133
+ if (r.kind === "builtin") {
134
+ const ts = POSTGRES_ROW_TYPES[r.canonical] ?? "unknown";
135
+ return array({ sqlType: spell(r.canonical, t), ts, notNull: r.serial === true });
136
+ }
137
+ if (r.kind === "declared") {
138
+ const o = r.object;
139
+ const sqlType = String(o.sqlName ?? o.name);
140
+ if (o.type === "Postgres::Enum") {
141
+ const labels = o.labels ?? [];
142
+ return array({ sqlType, ts: labels.length > 0 ? labels.map((l) => JSON.stringify(l)).join(" | ") : "string", notNull: false });
143
+ }
144
+ if (o.type === "Postgres::Domain") {
145
+ const base = pgType(p, o.dataType, depth + 1);
146
+ return array({ sqlType, ts: base.ts, notNull: o.notNull === true || base.notNull });
147
+ }
148
+ // A row type: the driver hands back its text.
149
+ return array({ sqlType, ts: "string", notNull: false });
150
+ }
151
+ return { sqlType: text.trim(), ts: "unknown", notNull: false };
152
+ }
153
+ function postgresEntries(objects, major, entities, projectDir) {
154
+ const p = projectFromObjects(objects, major);
155
+ const done = new Map();
156
+ const entryOf = (exportName) => {
157
+ if (done.has(exportName))
158
+ return done.get(exportName);
159
+ done.set(exportName, undefined);
160
+ const o = p.byExport.get(exportName);
161
+ const entity = entities.get(exportName);
162
+ if (!o || !entity || !PG_RELATIONS.has(o.type))
163
+ return undefined;
164
+ const names = runtimeColumns(entity);
165
+ let columns;
166
+ if (o.type === "Postgres::Table") {
167
+ const t = o;
168
+ const own = new Map(t.columns.map((c) => [c.name, c]));
169
+ // A partition's `col WITH OPTIONS` has no type of its own: its parent's stands.
170
+ const inherited = new Map();
171
+ for (const parent of [...(t.inherits ?? []), ...(t.partitionOf !== undefined ? [t.partitionOf] : [])]) {
172
+ const pt = typeof parent === "string" ? p.byExport.get(parent) : undefined;
173
+ for (const c of pt?.columns ?? [])
174
+ if (!inherited.has(c.name))
175
+ inherited.set(c.name, c.type);
176
+ }
177
+ const pk = new Set(t.primaryKey?.columns ?? []);
178
+ const unread = opaqueColumns(o);
179
+ columns = names.map((name) => {
180
+ const c = own.get(name);
181
+ const text = c?.type ?? inherited.get(name);
182
+ if (text === undefined)
183
+ return UNKNOWN_COLUMN(name);
184
+ const ty = pgType(p, text);
185
+ const notNull = c?.notNull === true || pk.has(name) || c?.generated?.kind === "identity" || ty.notNull;
186
+ const entry = { name, sqlType: ty.sqlType, ts: ty.ts, nullable: !notNull, cmp: typeIdentity(p, text) };
187
+ return unread.has(name) ? opaque(entry) : entry;
188
+ });
189
+ }
190
+ else {
191
+ columns = names.map((name) => {
192
+ const via = passthrough(o, name, quoteIdent);
193
+ const source = via && entryOf(via.export)?.columns.find((c) => c.name === via.column);
194
+ // A view's column is nullable to Postgres whatever it reads.
195
+ return source ? { ...source, name, nullable: source.nullable === "never" ? "never" : true } : UNKNOWN_COLUMN(name);
196
+ });
197
+ }
198
+ const slots = new Map();
199
+ for (const s of getTypedSlots(entity)) {
200
+ if (s.kind !== "references")
201
+ continue;
202
+ const cmp = columns.find((c) => c.name === s.column)?.cmp;
203
+ if (cmp !== undefined)
204
+ slots.set(s.index, { type: `KeyColumn<${JSON.stringify(cmp)}>`, note: `the column ${s.column} references` });
205
+ }
206
+ const entry = { key: relationKey(o), origin: originOf(entity, exportName, projectDir), columns, slots };
207
+ done.set(exportName, entry);
208
+ return entry;
209
+ };
210
+ return objects.filter((o) => PG_RELATIONS.has(o.type)).map((o) => entryOf(o.export)).filter((e) => e !== undefined);
211
+ }
212
+ // ── ClickHouse ──────────────────────────────────────────────────────────
213
+ const CH_RELATIONS = new Set(["ClickHouse::Table", "ClickHouse::View", "ClickHouse::MaterializedView", "ClickHouse::Dictionary"]);
214
+ function chColumn(name, type, nullableModifier) {
215
+ if (!type)
216
+ return UNKNOWN_COLUMN(name);
217
+ const sqlType = nullableModifier === true && !/^Nullable\s*\(/i.test(type.trim()) ? `Nullable(${type.trim()})` : type.trim();
218
+ const row = clickhouseRowType(sqlType);
219
+ return { name, sqlType, ts: row.ts, nullable: row.nullable, cmp: typeFamily(sqlType) || undefined };
220
+ }
221
+ function clickhouseEntries(objects, entities, projectDir) {
222
+ const byExport = new Map(objects.map((o) => [o.export, o]));
223
+ const done = new Map();
224
+ const entryOf = (exportName) => {
225
+ if (done.has(exportName))
226
+ return done.get(exportName);
227
+ done.set(exportName, undefined);
228
+ const o = byExport.get(exportName);
229
+ const entity = entities.get(exportName);
230
+ if (!o || !entity || !CH_RELATIONS.has(o.type))
231
+ return undefined;
232
+ const declared = new Map((o.columns ?? []).map((c) => [c.name, c]));
233
+ const unread = opaqueColumns(o);
234
+ const columns = runtimeColumns(entity).map((name) => {
235
+ const c = declared.get(name);
236
+ if (c)
237
+ return unread.has(name) ? opaque(chColumn(name, c.type, c.nullable)) : chColumn(name, c.type, c.nullable);
238
+ const via = passthrough(o, name, quoteIdentifier);
239
+ const source = via && entryOf(via.export)?.columns.find((x) => x.name === via.column);
240
+ return source ? { ...source, name } : UNKNOWN_COLUMN(name);
241
+ });
242
+ const slots = new Map();
243
+ for (const s of getTypedSlots(entity))
244
+ if (s.kind === "sortKey")
245
+ slots.set(s.index, { type: "NotNullColumn", note: "a sort key element" });
246
+ const entry = { key: relationKey(o), origin: originOf(entity, exportName, projectDir), columns, slots };
247
+ done.set(exportName, entry);
248
+ return entry;
249
+ };
250
+ return objects.filter((o) => CH_RELATIONS.has(o.type)).map((o) => entryOf(o.export)).filter((e) => e !== undefined);
251
+ }
252
+ // ── Rendering ───────────────────────────────────────────────────────────
253
+ const propertyName = (name) => (/^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name));
254
+ function renderEntry(e, lines) {
255
+ lines.push(` /** ${e.origin.replace(/\*\//g, "* /")} */`, ` ${JSON.stringify(e.key)}: {`, " columns: {");
256
+ for (const c of e.columns) {
257
+ const nullable = c.nullable === "never" ? "never" : String(c.nullable);
258
+ const cmp = c.cmp === undefined ? "never" : JSON.stringify(c.cmp);
259
+ lines.push(` ${propertyName(c.name)}: Column<${JSON.stringify(c.sqlType)}, { ts: ${c.ts}; nullable: ${nullable}; cmp: ${cmp} }>;`);
260
+ }
261
+ lines.push(" };");
262
+ if (e.slots.size > 0) {
263
+ const last = Math.max(...e.slots.keys());
264
+ const types = Array.from({ length: last + 1 }, (_, i) => e.slots.get(i)?.type ?? "unknown");
265
+ const notes = [...e.slots].sort(([a], [b]) => a - b).map(([i, s]) => `\`\${}\` ${i + 1} is ${s.note}`);
266
+ lines.push(` /** ${notes.join("; ")}. */`, ` slots: [${types.join(", ")}];`);
267
+ }
268
+ lines.push(" };");
269
+ }
270
+ /** The dialect a build's entities declare, else the config's, else ClickHouse. */
271
+ function dialectOf(entities, config) {
272
+ try {
273
+ return filesDialect(entities, config);
274
+ }
275
+ catch {
276
+ return "clickhouse";
277
+ }
278
+ }
279
+ /**
280
+ * The `sql-types.d.ts` for a build's entities: one module that augments the
281
+ * dialect's `SqlSchema` with each table, view and dictionary, in key order.
282
+ * Pure, so a check can render it in memory and compare it with the file.
283
+ */
284
+ export function renderSqlTypes(entities, opts = {}) {
285
+ const config = opts.config ?? {};
286
+ const projectDir = opts.projectDir ?? process.cwd();
287
+ const dialect = dialectOf(entities, config);
288
+ const sqlEntities = new Map([...entities].filter(([, e]) => isPostgresObject(e) || isClickHouseObject(e)));
289
+ let entries = [];
290
+ if (sqlEntities.size > 0) {
291
+ const out = sqlSerializer.serialize(new Map(entities), undefined, { config });
292
+ const doc = JSON.parse(typeof out === "string" ? out : out.primary);
293
+ entries = dialect === "postgres" ? postgresEntries(doc.objects, doc.postgresMajor ?? PINNED_MAJOR, entities, projectDir) : clickhouseEntries(doc.objects, entities, projectDir);
294
+ }
295
+ const byKey = new Map();
296
+ for (const e of entries)
297
+ if (!byKey.has(e.key))
298
+ byKey.set(e.key, e);
299
+ const sorted = [...byKey.values()].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
300
+ const brands = new Set();
301
+ if (sorted.length > 0)
302
+ brands.add("Column");
303
+ for (const e of sorted)
304
+ for (const s of e.slots.values())
305
+ brands.add(s.type.replace(/<.*$/, ""));
306
+ const module = SQL_TYPES_MODULE[dialect];
307
+ const lines = [
308
+ `// ${GENERATED_MARKER}. \`chant sql types\` writes it from the build's entities.`,
309
+ `import type ${brands.size > 0 ? `{ ${[...brands].sort().join(", ")} }` : "{}"} from ${JSON.stringify(module)};`,
310
+ "",
311
+ `declare module ${JSON.stringify(module)} {`,
312
+ ];
313
+ if (sorted.length === 0)
314
+ lines.push(" interface SqlSchema {}");
315
+ else {
316
+ lines.push(" interface SqlSchema {");
317
+ for (const e of sorted)
318
+ renderEntry(e, lines);
319
+ lines.push(" }");
320
+ }
321
+ lines.push("}");
322
+ return `${lines.join("\n")}\n`;
323
+ }