@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,160 @@
1
+ /**
2
+ * ClickHouse DDL into the import IR, and back out as `database`, `table` and
3
+ * `view` declarations.
4
+ *
5
+ * `chant import --from <env>` reads each object's `SHOW CREATE` and `chant
6
+ * import schema.sql` reads a file of CREATE statements; both arrive here as
7
+ * statements. The generated declaration is the statement itself, the
8
+ * server's canonical form when it came from a server (#3046 question 5), with
9
+ * two changes:
10
+ *
11
+ * - a qualified name of another imported object becomes a reference to its
12
+ * export (`FROM analytics.events` is `FROM ${events}`), and the database
13
+ * part of an object's own name becomes a reference to the database's export,
14
+ * so dependency order survives the import;
15
+ * - a backquoted identifier that needs no quoting is written bare, and one that
16
+ * does is double-quoted, so the template has no backquotes to escape.
17
+ *
18
+ * Columns written by name inside a view's SELECT stay text: which table a
19
+ * bare name belongs to is the server's to resolve, and a guess would be
20
+ * wrong lineage. An imported view therefore records its reads, not its column
21
+ * lineage, until its column references are written as `${t.columns.c}`.
22
+ */
23
+ import { isTrivia, tokenizeText } from "../tokens.js";
24
+ import { parseCreate, unquote } from "../parser.js";
25
+ import { CLICKHOUSE_ENTITY_TYPES } from "../entities.js";
26
+ import { MERGE_TREE_SETTINGS } from "../../generated/clickhouse.js";
27
+ const JS_RESERVED = new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public await".split(" "));
28
+ function camel(name) {
29
+ const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
30
+ if (words.length === 0)
31
+ return "object";
32
+ const s = words.map((w, i) => (i === 0 ? w[0].toLowerCase() + w.slice(1) : w[0].toUpperCase() + w.slice(1))).join("");
33
+ return /^[0-9]/.test(s) ? `t${s}` : s;
34
+ }
35
+ /**
36
+ * Export names for a set of objects. A table or view is its name in camel
37
+ * case, prefixed with its database when two databases share a name; a
38
+ * database is its name with `Db` after it, so `analytics` the database and
39
+ * `analytics` a table do not collide.
40
+ */
41
+ export function exportNames(objects) {
42
+ const relations = objects.filter((o) => o.type !== CLICKHOUSE_ENTITY_TYPES.database);
43
+ const count = new Map();
44
+ for (const o of relations)
45
+ count.set(o.name, (count.get(o.name) ?? 0) + 1);
46
+ const used = new Set();
47
+ return objects.map((o) => {
48
+ let base = o.type === CLICKHOUSE_ENTITY_TYPES.database
49
+ ? `${camel(o.name)}Db`
50
+ : (count.get(o.name) ?? 0) > 1 && o.database
51
+ ? camel(`${o.database}_${o.name}`)
52
+ : camel(o.name);
53
+ if (JS_RESERVED.has(base))
54
+ base = `${base}Table`;
55
+ let name = base;
56
+ for (let i = 2; used.has(name); i++)
57
+ name = `${base}${i}`;
58
+ used.add(name);
59
+ return name;
60
+ });
61
+ }
62
+ /** Split a file of statements on top-level `;`. */
63
+ export function splitStatements(text) {
64
+ const out = [];
65
+ let current = [];
66
+ const flush = () => {
67
+ const stmt = current.map((t) => t.text).join("").trim();
68
+ if (current.some((t) => !isTrivia(t)))
69
+ out.push(stmt);
70
+ current = [];
71
+ };
72
+ for (const t of tokenizeText(text, 0)) {
73
+ if (t.kind === "punct" && t.text === ";")
74
+ flush();
75
+ else
76
+ current.push(t);
77
+ }
78
+ flush();
79
+ return out;
80
+ }
81
+ const statementType = (node) => node.statement === "database"
82
+ ? CLICKHOUSE_ENTITY_TYPES.database
83
+ : node.statement === "table"
84
+ ? CLICKHOUSE_ENTITY_TYPES.table
85
+ : node.statement === "dictionary"
86
+ ? CLICKHOUSE_ENTITY_TYPES.dictionary
87
+ : node.statement === "function"
88
+ ? CLICKHOUSE_ENTITY_TYPES.function
89
+ : node.materialized
90
+ ? CLICKHOUSE_ENTITY_TYPES.materializedView
91
+ : CLICKHOUSE_ENTITY_TYPES.view;
92
+ function nameOf(tokens, span) {
93
+ const parts = tokens
94
+ .slice(span.from, span.to)
95
+ .filter((t) => !isTrivia(t) && !(t.kind === "punct" && t.text === "."))
96
+ .map((t) => unquote(t.text));
97
+ return parts.length >= 2 ? { database: parts[parts.length - 2], name: parts[parts.length - 1] } : { name: parts[0] ?? "" };
98
+ }
99
+ /** Read a statement's kind and name. Throws the parser's error when it does not parse. */
100
+ export function describeStatement(ddl) {
101
+ const tokens = tokenizeText(ddl, 0);
102
+ const node = parseCreate(tokens);
103
+ return { type: statementType(node), ...nameOf(tokens, node.name), ddl };
104
+ }
105
+ /**
106
+ * Leave out what the server adds and the declaration would not say: settings
107
+ * at the pinned server's default (`index_granularity = 8192`), and a view's
108
+ * column list, which the server infers from the SELECT. A server re-adds both
109
+ * on create, so the stripped statement creates the same object.
110
+ */
111
+ export function stripServerDefaults(ddl) {
112
+ const tokens = tokenizeText(ddl, 0);
113
+ let node;
114
+ try {
115
+ node = parseCreate(tokens);
116
+ }
117
+ catch {
118
+ return ddl;
119
+ }
120
+ const edits = [];
121
+ if ((node.statement === "table" || node.statement === "view") && node.settings && node.settingsClause) {
122
+ const text = (s) => tokens.slice(s.from, s.to).map((t) => t.text).join("").trim();
123
+ const kept = node.settings.filter((s) => {
124
+ const def = MERGE_TREE_SETTINGS[s.key];
125
+ return !def || text(s.value).replace(/^'|'$/g, "") !== def.default;
126
+ });
127
+ if (kept.length !== node.settings.length) {
128
+ edits.push({
129
+ span: node.settingsClause,
130
+ text: kept.length === 0 ? "" : `SETTINGS ${kept.map((s) => `${s.key} = ${text(s.value)}`).join(", ")}`,
131
+ });
132
+ }
133
+ }
134
+ if (node.statement === "view" && node.columnsSpan)
135
+ edits.push({ span: node.columnsSpan, text: "" });
136
+ if (edits.length === 0)
137
+ return ddl;
138
+ edits.sort((a, b) => b.span.from - a.span.from);
139
+ const out = [...tokens];
140
+ for (const e of edits) {
141
+ out.splice(e.span.from, e.span.to - e.span.from, { kind: "ws", text: e.text, part: 0, start: 0, end: 0 });
142
+ }
143
+ return out
144
+ .map((t) => t.text)
145
+ .join("")
146
+ .split("\n")
147
+ .filter((line) => line.trim().length > 0)
148
+ .join("\n")
149
+ .replace(/[ \t]+$/gm, "");
150
+ }
151
+ /** The IR for a set of objects. */
152
+ export function objectsToIR(objects, warnings = []) {
153
+ const names = exportNames(objects);
154
+ const resources = objects.map((o, i) => ({
155
+ logicalId: names[i],
156
+ type: o.type,
157
+ properties: { ...(o.database ? { database: o.database } : {}), name: o.name, ddl: o.ddl },
158
+ }));
159
+ return { resources, parameters: [], ...(warnings.length ? { warnings } : {}) };
160
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Live export: `chant import --from <env>` writes the server's schema as
3
+ * `database`, `table` and `view` declarations.
4
+ *
5
+ * Every object in the environment's databases is read with `SHOW CREATE`, the
6
+ * server's canonical form, which is the baseline #3046 question 5 asks for.
7
+ * By default what the server adds on its own is left out (settings at their
8
+ * default, a view's inferred column list), so the declaration says what an
9
+ * author would; `verbatim` keeps the statement as the server printed it.
10
+ *
11
+ * - `selector.type` is an entity type (`ClickHouse::Table`); `selector.name` is
12
+ * an object name, `db.name` or `name`.
13
+ * - `owned` keeps the objects whose comment carries chant's ownership marker
14
+ * (`../ownership.ts`), the trailer the applier stamps. The trailer itself is
15
+ * never written into a declaration: `readLiveSchema` takes it off.
16
+ * - a migration runner's history table (`schema_migrations`,
17
+ * `goose_db_version`) is left out with a warning (#3676), as on Postgres.
18
+ * - A SQL user-defined function belongs to no database, and every project's
19
+ * functions sit side by side on a server (#3718). Without a selector, an
20
+ * import adopts only the functions the imported objects use (and those
21
+ * functions call), and the ones `sql.profiles.<env>.importFunctions` names;
22
+ * a warning names the rest. ClickHouse stores a call as the function's body
23
+ * (#3745), so a use is found by that body (`./inlined.ts`) as well as by
24
+ * the name. A function carries no comment, so `owned` does
25
+ * not apply to it.
26
+ * - An object of a kind no declaration holds (a window view, a quota, a
27
+ * settings profile, a named collection, ...; `../live/undeclarable.ts`)
28
+ * fails the import, which names every one of them (#3911).
29
+ * `skipUndeclarable` (`--skip-undeclarable`) imports the rest and lists
30
+ * them as warnings. A selector or `owned` import asks for objects chant can
31
+ * hold, so it does not look.
32
+ */
33
+ import { bindClickHouse } from "../live/bind.js";
34
+ import { readLiveSchema } from "../live/catalog.js";
35
+ import { objectsToIR, stripServerDefaults } from "./ir.js";
36
+ import { isChantManaged } from "../ownership.js";
37
+ import { CLICKHOUSE_ENTITY_TYPES } from "../entities.js";
38
+ import { isTrivia, tokenizeText } from "../tokens.js";
39
+ import { unquote } from "../parser.js";
40
+ import { inlinedFunctions } from "./inlined.js";
41
+ import { readUndeclarable } from "../live/undeclarable.js";
42
+ import { refuseUndeclarable } from "../../core/undeclarable.js";
43
+ /** The functions among `names` a statement calls: a name followed by `(`. */
44
+ export function calledFunctions(ddl, names) {
45
+ const tokens = tokenizeText(ddl, 0).filter((t) => !isTrivia(t));
46
+ const out = new Set();
47
+ tokens.forEach((t, i) => {
48
+ if (t.kind !== "ident" && t.kind !== "qident")
49
+ return;
50
+ const next = tokens[i + 1];
51
+ if (next?.kind !== "punct" || next.text !== "(")
52
+ return;
53
+ const name = t.kind === "qident" ? unquote(t.text) : t.text;
54
+ if (names.has(name))
55
+ out.add(name);
56
+ });
57
+ return [...out];
58
+ }
59
+ /** Whether `patterns` (names, or prefixes ending in `*`) name a function. */
60
+ const named = (patterns, name) => patterns.some((p) => (p.endsWith("*") ? name.startsWith(p.slice(0, -1)) : p === name));
61
+ /**
62
+ * The functions an import adopts (#3718): those the objects call, those
63
+ * whose bodies the objects hold in place of a call (#3745, `./inlined.ts`),
64
+ * those `patterns` name, and the functions those call in turn; and the rest.
65
+ */
66
+ export function scopedFunctions(objects, functions, patterns = []) {
67
+ const byName = new Map(functions.map((f) => [f.name, f]));
68
+ const names = new Set(byName.keys());
69
+ const adopted = new Set(functions.filter((f) => named(patterns, f.name)).map((f) => f.name));
70
+ for (const o of objects)
71
+ for (const n of calledFunctions(o.statement, names))
72
+ adopted.add(n);
73
+ for (const n of inlinedFunctions(objects.map((o) => o.statement), functions))
74
+ adopted.add(n);
75
+ const queue = [...adopted];
76
+ while (queue.length > 0) {
77
+ for (const n of calledFunctions(byName.get(queue.pop()).statement, names)) {
78
+ if (!adopted.has(n)) {
79
+ adopted.add(n);
80
+ queue.push(n);
81
+ }
82
+ }
83
+ }
84
+ return { adopted: functions.filter((f) => adopted.has(f.name)), skipped: functions.filter((f) => !adopted.has(f.name)).map((f) => f.name) };
85
+ }
86
+ export async function exportResources(options) {
87
+ const target = await bindClickHouse(options);
88
+ // A selector or an owned import asks only for what chant can hold.
89
+ const undeclarable = !options.selector && !options.owned ? await readUndeclarable(target) : { objects: [], unread: [] };
90
+ const warnings = undeclarable.unread.map((u) => `the server was not checked for objects of one kind chant cannot declare, so some may be left out: ${u}`);
91
+ refuseUndeclarable("clickhouse", undeclarable.objects, options.skipUndeclarable, warnings);
92
+ // What is refused, and with --skip-undeclarable left out, is not also read as a table (a window view is in system.tables).
93
+ const refused = new Set(undeclarable.objects.filter((o) => o.schema !== undefined).map((o) => `${o.schema}.${o.name}`));
94
+ const all = (await readLiveSchema(target)).filter((o) => !(o.database !== undefined && refused.has(`${o.database}.${o.name}`)));
95
+ const isFunction = (o) => o.type === CLICKHOUSE_ENTITY_TYPES.function;
96
+ // A selector picks what it names, functions included; without one, functions go with what calls them (#3718).
97
+ const live = options.selector ? all : all.filter((o) => !isFunction(o));
98
+ const selected = live.filter((o) => {
99
+ if (options.owned && !isChantManaged(o.comment))
100
+ return false;
101
+ if (options.selector?.type && o.type !== options.selector.type)
102
+ return false;
103
+ const name = options.selector?.name;
104
+ if (name !== undefined && name !== o.name && name !== `${o.database}.${o.name}`)
105
+ return false;
106
+ return true;
107
+ });
108
+ if (!options.selector) {
109
+ const { adopted, skipped } = scopedFunctions(selected.filter((o) => !o.foreign), all.filter(isFunction), target.importFunctions);
110
+ selected.push(...adopted);
111
+ if (skipped.length > 0) {
112
+ warnings.push(`${skipped.length} SQL function${skipped.length === 1 ? "" : "s"} on the server ${skipped.length === 1 ? "is" : "are"} not imported, since no imported object uses ${skipped.length === 1 ? "it" : "them"}: ${skipped.join(", ")}. Name ${skipped.length === 1 ? "it" : "them"} in sql.profiles.${options.environment}.importFunctions to import ${skipped.length === 1 ? "it" : "them"}.`);
113
+ }
114
+ }
115
+ const objects = selected
116
+ // The `default` database exists on every server; declaring it would make apply create what is there.
117
+ .filter((o) => !(o.type === "ClickHouse::Database" && o.name === "default"))
118
+ .filter((o) => {
119
+ if (!o.foreign)
120
+ return true;
121
+ warnings.push(`${o.database ? `${o.database}.` : ""}${o.name} is kept by ${o.foreign}; left out, since declaring it would have chant change what that tool owns`);
122
+ return false;
123
+ })
124
+ .map((o) => ({
125
+ type: o.type,
126
+ ...(o.database ? { database: o.database } : {}),
127
+ name: o.name,
128
+ ddl: options.verbatim ? o.statement : stripServerDefaults(o.statement),
129
+ }));
130
+ return objectsToIR(objects, warnings);
131
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `chant import schema.sql` for ClickHouse: a file of CREATE statements into
3
+ * the import IR. A statement that is not a CREATE, or does not parse, is an
4
+ * error naming it (`../../files/clickhouse.ts`); none is left out.
5
+ */
6
+ import { objectsToIR } from "./ir.js";
7
+ import { readClickHouse } from "../../files/clickhouse.js";
8
+ import { SqlFileError } from "../../files/common.js";
9
+ export class ClickHouseSqlParser {
10
+ parse(content) {
11
+ const problems = [];
12
+ const objects = readClickHouse(content, { origin: "the DDL" }, problems);
13
+ if (problems.length > 0)
14
+ throw new SqlFileError("the DDL", problems);
15
+ return objectsToIR(objects);
16
+ }
17
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Which ClickHouse server a chant environment reads: the config lookup and
3
+ * the binding, shared by observation and live export so both read the same
4
+ * server.
5
+ */
6
+ import { ClickHouseQueryError, clickhouseQuery } from "../http.js";
7
+ import { parseTopology, toTopology } from "../topology.js";
8
+ import { credentialSource, POSTGRES_ONLY_TOKEN_SOURCES, TokenSourceError } from "../../token-source.js";
9
+ export const isUnresolvedTarget = (v) => "reason" in v;
10
+ /**
11
+ * The server an environment reads. Pure: `config` and `env` are passed in.
12
+ *
13
+ * 1. `sql.profiles.<environment>`. A profile naming a credential variable that
14
+ * is not set is `no-credentials`: the profile said how to authenticate.
15
+ * 2. `CLICKHOUSE_URL`, with `CLICKHOUSE_USER` and `CLICKHOUSE_PASSWORD` when set,
16
+ * and `CLICKHOUSE_TOPOLOGY` (`single`, `cluster:<name>`, `replicated`,
17
+ * `cloud`).
18
+ * 3. Otherwise `no-binding`.
19
+ */
20
+ export function resolveClickHouseTarget(input) {
21
+ const env = input.env ?? process.env;
22
+ const profile = input.environment !== undefined ? input.config?.sql?.profiles?.[input.environment] : undefined;
23
+ if (profile) {
24
+ const source = `sql.profiles.${input.environment}`;
25
+ const endpoint = { url: profile.url.replace(/\/+$/, "") };
26
+ if (profile.user) {
27
+ const value = env[profile.user.env];
28
+ if (value === undefined)
29
+ return { reason: "no-credentials", detail: `${source}.user names ${profile.user.env}, which is not set` };
30
+ endpoint.user = value;
31
+ }
32
+ const password = profile.password;
33
+ if (password && "env" in password) {
34
+ const value = env[password.env];
35
+ if (value === undefined)
36
+ return { reason: "no-credentials", detail: `${source}.password names ${password.env}, which is not set` };
37
+ endpoint.password = value;
38
+ }
39
+ else if (password) {
40
+ if (POSTGRES_ONLY_TOKEN_SOURCES.has(password.token)) {
41
+ return { reason: "no-credentials", detail: `${source}.password is a ${password.token} token, which only a Postgres server takes; use a command source for ClickHouse` };
42
+ }
43
+ endpoint.token = credentialSource(password, { url: profile.url, ...(endpoint.user !== undefined ? { user: endpoint.user } : {}), env, source: `${source}.password` });
44
+ }
45
+ return {
46
+ endpoint,
47
+ source,
48
+ ...(profile.databases ? { databases: profile.databases } : {}),
49
+ defaultDatabase: profile.defaultDatabase ?? "default",
50
+ ...(profile.topology !== undefined ? { topology: toTopology(profile.topology) } : {}),
51
+ ...(profile.access === true ? { access: true } : {}),
52
+ ...(profile.importFunctions ? { importFunctions: profile.importFunctions } : {}),
53
+ };
54
+ }
55
+ const url = env.CLICKHOUSE_URL;
56
+ if (!url) {
57
+ const where = input.environment !== undefined ? `sql.profiles.${input.environment} is not declared and ` : "";
58
+ return { reason: "no-binding", detail: `${where}CLICKHOUSE_URL is not set, so there is no ClickHouse server to read` };
59
+ }
60
+ return {
61
+ endpoint: {
62
+ url: url.replace(/\/+$/, ""),
63
+ ...(env.CLICKHOUSE_USER !== undefined ? { user: env.CLICKHOUSE_USER } : {}),
64
+ ...(env.CLICKHOUSE_PASSWORD !== undefined ? { password: env.CLICKHOUSE_PASSWORD } : {}),
65
+ },
66
+ source: "env CLICKHOUSE_URL",
67
+ defaultDatabase: "default",
68
+ ...(env.CLICKHOUSE_TOPOLOGY ? { topology: parseTopology(env.CLICKHOUSE_TOPOLOGY) } : {}),
69
+ };
70
+ }
71
+ /** A target that could not be resolved. */
72
+ export class ClickHouseBindingError extends Error {
73
+ constructor(unresolved) {
74
+ super(unresolved.detail);
75
+ this.unresolved = unresolved;
76
+ this.name = "ClickHouseBindingError";
77
+ }
78
+ }
79
+ async function loadConfig(cwd) {
80
+ try {
81
+ const { loadChantConfig } = await import("@intentius/chant/config");
82
+ return (await loadChantConfig(cwd)).config;
83
+ }
84
+ catch {
85
+ // No project config: the environment variables still bind.
86
+ return undefined;
87
+ }
88
+ }
89
+ /** Resolve the environment's server. Throws {@link ClickHouseBindingError} when there is none. */
90
+ export async function bindClickHouse(options = {}) {
91
+ const config = options.config ?? (await loadConfig(options.cwd ?? process.cwd()));
92
+ const target = resolveClickHouseTarget({ environment: options.environment, config, env: options.env });
93
+ if (isUnresolvedTarget(target))
94
+ throw new ClickHouseBindingError(target);
95
+ return target;
96
+ }
97
+ /** What a failed bind or read means, in the observation vocabulary. */
98
+ export function classifyClickHouseFailure(err) {
99
+ if (err instanceof ClickHouseBindingError)
100
+ return { reason: err.unresolved.reason, detail: err.unresolved.detail };
101
+ if (err instanceof TokenSourceError)
102
+ return { reason: "no-credentials", detail: err.message };
103
+ if (err instanceof ClickHouseQueryError) {
104
+ // 516 AUTHENTICATION_FAILED and 497 ACCESS_DENIED arrive as 401/403 or in the message.
105
+ if (err.status === 401 || err.status === 403 || /AUTHENTICATION_FAILED|ACCESS_DENIED|Code: 516|Code: 497/.test(err.serverMessage)) {
106
+ return { reason: "no-credentials", detail: `${err.message} (the server refused the credentials)` };
107
+ }
108
+ return { reason: "read-failed", detail: err.message };
109
+ }
110
+ return { reason: "read-failed", detail: err instanceof Error ? err.message.split("\n")[0] : String(err) };
111
+ }
112
+ /** `SELECT version()` on the target. */
113
+ export async function serverVersion(target) {
114
+ const [row] = await clickhouseQuery(target.endpoint, "SELECT version() AS v");
115
+ return row?.v ?? "";
116
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Reading a live server's schema: the databases and the tables, views,
3
+ * materialized views and dictionaries in them, from `system.databases`,
4
+ * `system.tables` and `SHOW CREATE`, and the SQL user-defined functions,
5
+ * from `system.functions`.
6
+ */
7
+ import { clickhouseQuery } from "../http.js";
8
+ import { CLICKHOUSE_ENTITY_TYPES } from "../entities.js";
9
+ import { isChantWorkingObject, stripMarkerFromStatement } from "../ownership.js";
10
+ import { foreignTool } from "../../core/foreign-tables.js";
11
+ import { parseCreate, unquote } from "../parser.js";
12
+ import { tokenizeText } from "../tokens.js";
13
+ /** The server's own databases, never part of a declared schema. */
14
+ export const SYSTEM_DATABASES = new Set(["system", "information_schema", "INFORMATION_SCHEMA"]);
15
+ const quote = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
16
+ const ident = (s) => `\`${s.replace(/`/g, "``")}\``;
17
+ /** The entity type a `system.tables` engine stands for. */
18
+ export function entityTypeOfEngine(engine) {
19
+ if (engine === "View")
20
+ return CLICKHOUSE_ENTITY_TYPES.view;
21
+ if (engine === "MaterializedView")
22
+ return CLICKHOUSE_ENTITY_TYPES.materializedView;
23
+ return CLICKHOUSE_ENTITY_TYPES.table;
24
+ }
25
+ /**
26
+ * Every object in the target's databases, sorted by database then name.
27
+ * A materialized view's inner table (`.inner_id.<uuid>`, `.inner.<name>`)
28
+ * belongs to its view and is left out, as are temporary tables. A
29
+ * dictionary is read (#3682) from `system.tables`, where a reader granted
30
+ * `SELECT` on the database sees it without a grant on `system.dictionaries`;
31
+ * a table with the `Dictionary` engine is a table.
32
+ *
33
+ * A table another tool keeps its migration history in (`schema_migrations`,
34
+ * `goose_db_version`, `../../core/foreign-tables.ts`) is read and marked with
35
+ * the tool, as the Postgres reader marks it (#3676): import leaves it out
36
+ * with a warning, and a plan names it in a hint instead of dropping it.
37
+ *
38
+ * So are chant's own working objects (`../ownership.ts`
39
+ * `isChantWorkingObject`): a rebuild migration's new, dual-write and retained
40
+ * tables, and the effect receipts database. None of them is a declaration, so
41
+ * a plan, an import or a prune that saw them would propose dropping, importing
42
+ * or pruning the very objects a rebuild in progress depends on.
43
+ */
44
+ export async function readLiveSchema(target, opts = {}) {
45
+ const q = (sql) => clickhouseQuery(target.endpoint, sql);
46
+ const scope = target.databases
47
+ ? `IN (${target.databases.map(quote).join(", ")})`
48
+ : `NOT IN (${[...SYSTEM_DATABASES].map(quote).join(", ")})`;
49
+ const databases = await q(`SELECT name, engine, toString(uuid) AS uuid, comment FROM system.databases WHERE name ${scope} ORDER BY name`);
50
+ // `system.tables` shows no comment for a table over a table function, whatever it was created with: its statement has it (#3911).
51
+ const tables = await q(`SELECT database, name, engine, toString(uuid) AS uuid, comment, startsWith(create_table_query, 'CREATE DICTIONARY') AS dictionary, ` +
52
+ `if(comment = '' AND position(create_table_query, ' COMMENT ') > 0, create_table_query, '') AS proxy FROM system.tables ` +
53
+ `WHERE database ${scope} AND NOT is_temporary AND name NOT LIKE '.inner%' ` +
54
+ `ORDER BY database, name`);
55
+ const working = new Set(databases.filter((d) => isChantWorkingObject(d.comment)).map((d) => d.name));
56
+ const out = [];
57
+ const statement = async (kind, what) => opts.withStatements === false
58
+ ? ""
59
+ : stripMarkerFromStatement((await q(`SHOW CREATE ${kind} ${what}`))[0]?.statement ?? "");
60
+ for (const d of databases) {
61
+ if (working.has(d.name))
62
+ continue;
63
+ out.push({
64
+ type: CLICKHOUSE_ENTITY_TYPES.database,
65
+ name: d.name,
66
+ engine: d.engine,
67
+ ...(d.uuid && !/^0{8}-/.test(d.uuid) ? { uuid: d.uuid } : {}),
68
+ ...(d.comment ? { comment: d.comment } : {}),
69
+ statement: await statement("DATABASE", ident(d.name)),
70
+ });
71
+ }
72
+ for (const t of tables) {
73
+ if (t.proxy)
74
+ t.comment = commentOf(t.proxy) ?? t.comment;
75
+ if (working.has(t.database) || isChantWorkingObject(t.comment))
76
+ continue;
77
+ const type = Number(t.dictionary) === 1 ? CLICKHOUSE_ENTITY_TYPES.dictionary : entityTypeOfEngine(t.engine);
78
+ const foreign = type === CLICKHOUSE_ENTITY_TYPES.table ? foreignTool(t.name) : undefined;
79
+ out.push({
80
+ type,
81
+ database: t.database,
82
+ name: t.name,
83
+ engine: t.engine,
84
+ ...(t.uuid && !/^0{8}-/.test(t.uuid) ? { uuid: t.uuid } : {}),
85
+ ...(t.comment ? { comment: t.comment } : {}),
86
+ ...(foreign ? { foreign } : {}),
87
+ statement: await statement("TABLE", `${ident(t.database)}.${ident(t.name)}`),
88
+ });
89
+ }
90
+ // SQL user-defined functions belong to no database: every one is read, and a caller keeps those it declares.
91
+ const functions = await q(`SELECT name, create_query AS statement FROM system.functions WHERE origin = 'SQLUserDefined' ORDER BY name`);
92
+ for (const f of functions) {
93
+ out.push({ type: CLICKHOUSE_ENTITY_TYPES.function, name: f.name, engine: "", statement: opts.withStatements === false ? "" : f.statement });
94
+ }
95
+ return out;
96
+ }
97
+ /** A `CREATE` statement's own `COMMENT`, its value unquoted; undefined when it has none or does not parse. */
98
+ function commentOf(statement) {
99
+ try {
100
+ const tokens = tokenizeText(statement, 0);
101
+ const node = parseCreate(tokens);
102
+ if (!node.comment)
103
+ return undefined;
104
+ const literal = tokens
105
+ .slice(node.comment.from, node.comment.to)
106
+ .map((t) => t.text)
107
+ .join("")
108
+ .trim();
109
+ return /^'.*'$/s.test(literal) ? literal.slice(1, -1).replace(/\\(.)/g, "$1").replace(/''/g, "'") : unquote(literal);
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
115
+ /**
116
+ * The users, roles, row policies, quotas, settings profiles and grantees'
117
+ * grants the build declares, as the server holds them (#3682, #3911):
118
+ * `SHOW CREATE USER`, `SHOW CREATE ROLE`, `SHOW CREATE ROW POLICY`,
119
+ * `SHOW CREATE QUOTA`, `SHOW CREATE SETTINGS PROFILE`, and for a grantee every line of
120
+ * `SHOW GRANTS FOR`, one per line. Nothing else is read, so a project that
121
+ * declares none needs no access to `system.users` and the like, and an
122
+ * object made by hand is never seen. One that does not exist is left out.
123
+ */
124
+ export async function readLiveAccess(target, declared, opts = {}) {
125
+ if (declared.length === 0)
126
+ return [];
127
+ const q = (sql) => clickhouseQuery(target.endpoint, sql);
128
+ const first = async (sql) => (opts.withStatements === false ? "" : Object.values((await q(sql))[0] ?? {})[0] ?? "");
129
+ const all = async (sql) => (await q(sql)).map((r) => Object.values(r)[0] ?? "");
130
+ const names = (kind) => [...new Set(declared.filter((d) => d.kind === kind).map((d) => d.name))];
131
+ const users = names("user");
132
+ const roles = names("role");
133
+ const grantees = names("grants");
134
+ const principals = [...new Set([...users, ...roles, ...grantees])];
135
+ const list = (xs) => xs.map(quote).join(", ");
136
+ const existingUsers = new Set(principals.length ? (await q(`SELECT name FROM system.users WHERE name IN (${list(principals)})`)).map((r) => r.name) : []);
137
+ const existingRoles = new Set(principals.length ? (await q(`SELECT name FROM system.roles WHERE name IN (${list(principals)})`)).map((r) => r.name) : []);
138
+ const out = [];
139
+ for (const name of users.filter((u) => existingUsers.has(u))) {
140
+ out.push({ type: CLICKHOUSE_ENTITY_TYPES.user, name, engine: "", statement: await first(`SHOW CREATE USER ${ident(name)}`) });
141
+ }
142
+ for (const name of roles.filter((r) => existingRoles.has(r))) {
143
+ out.push({ type: CLICKHOUSE_ENTITY_TYPES.role, name, engine: "", statement: await first(`SHOW CREATE ROLE ${ident(name)}`) });
144
+ }
145
+ for (const [kind, type, table, show] of [
146
+ ["quota", CLICKHOUSE_ENTITY_TYPES.quota, "system.quotas", "QUOTA"],
147
+ ["settingsProfile", CLICKHOUSE_ENTITY_TYPES.settingsProfile, "system.settings_profiles", "SETTINGS PROFILE"],
148
+ ]) {
149
+ const wanted = names(kind);
150
+ if (wanted.length === 0)
151
+ continue;
152
+ const existing = new Set((await q(`SELECT name FROM ${table} WHERE name IN (${list(wanted)})`)).map((r) => r.name));
153
+ for (const name of wanted.filter((n) => existing.has(n))) {
154
+ out.push({ type, name, engine: "", statement: await first(`SHOW CREATE ${show} ${ident(name)}`) });
155
+ }
156
+ }
157
+ const policies = declared.filter((d) => d.kind === "rowPolicy");
158
+ if (policies.length > 0) {
159
+ const live = await q(`SELECT short_name AS name, database, table FROM system.row_policies WHERE short_name IN (${list(policies.map((p) => p.name))})`);
160
+ for (const p of policies) {
161
+ if (!live.some((l) => l.name === p.name && l.database === p.database && l.table === p.table))
162
+ continue;
163
+ out.push({
164
+ type: CLICKHOUSE_ENTITY_TYPES.rowPolicy,
165
+ name: p.name,
166
+ database: p.database,
167
+ table: p.table,
168
+ engine: "",
169
+ statement: await first(`SHOW CREATE ROW POLICY ${ident(p.name)} ON ${ident(p.database)}.${ident(p.table)}`),
170
+ });
171
+ }
172
+ }
173
+ for (const name of grantees.filter((g) => existingUsers.has(g) || existingRoles.has(g))) {
174
+ const lines = opts.withStatements === false ? [] : await all(`SHOW GRANTS FOR ${ident(name)}`);
175
+ out.push({ type: CLICKHOUSE_ENTITY_TYPES.grant, name, engine: "", statement: lines.join("\n") });
176
+ }
177
+ return out;
178
+ }
179
+ /** The canonical kinds a profile's `access` switch decides (#3716). */
180
+ export const ACCESS_KINDS = new Set(["user", "role", "rowPolicy", "quota", "settingsProfile", "grants"]);
181
+ /** The entity types a profile's `access` switch decides (#3716). */
182
+ export const ACCESS_ENTITY_TYPES = new Set([
183
+ CLICKHOUSE_ENTITY_TYPES.user,
184
+ CLICKHOUSE_ENTITY_TYPES.role,
185
+ CLICKHOUSE_ENTITY_TYPES.rowPolicy,
186
+ CLICKHOUSE_ENTITY_TYPES.quota,
187
+ CLICKHOUSE_ENTITY_TYPES.settingsProfile,
188
+ CLICKHOUSE_ENTITY_TYPES.grant,
189
+ ]);
190
+ /** Why an access declaration is not read, planned or applied where the profile does not manage access. */
191
+ export const ACCESS_UNMANAGED_DETAIL = "access is not managed in this environment: set sql.profiles.<env>.access to plan and apply users, roles, row policies, quotas, settings profiles and grants";
192
+ /** The hint a plan gives for the access declarations it left out. */
193
+ export function accessUnmanagedHint(n, environment) {
194
+ return `${n} access declaration${n === 1 ? " is" : "s are"} not planned: ${environment}'s profile does not manage access (sql.profiles.<env>.access)`;
195
+ }
196
+ /** The access objects among declared canonical objects, for `readLiveAccess`. */
197
+ export function declaredAccess(objects) {
198
+ return objects
199
+ .filter((o) => ACCESS_KINDS.has(o.kind))
200
+ .map((o) => ({ kind: o.kind, name: o.name, ...(o.database !== undefined ? { database: o.database } : {}), ...(o.table !== undefined ? { table: o.table } : {}) }));
201
+ }
202
+ /** The users, roles, row policies, quotas and settings profiles among declared entities, for `readLiveAccess`. */
203
+ export function accessOf(entities, defaultDatabase) {
204
+ const kinds = {
205
+ [CLICKHOUSE_ENTITY_TYPES.user]: "user",
206
+ [CLICKHOUSE_ENTITY_TYPES.role]: "role",
207
+ [CLICKHOUSE_ENTITY_TYPES.rowPolicy]: "rowPolicy",
208
+ [CLICKHOUSE_ENTITY_TYPES.quota]: "quota",
209
+ [CLICKHOUSE_ENTITY_TYPES.settingsProfile]: "settingsProfile",
210
+ };
211
+ const out = [];
212
+ for (const e of entities.values()) {
213
+ const kind = kinds[e.entityType];
214
+ if (!kind)
215
+ continue;
216
+ const name = String(e.props.name ?? "");
217
+ if (kind === "rowPolicy")
218
+ out.push({ kind, name, database: typeof e.props.database === "string" ? e.props.database : defaultDatabase, table: String(e.props.table ?? "") });
219
+ else
220
+ out.push({ kind, name });
221
+ }
222
+ return out;
223
+ }