@intentius/chant-lexicon-sql 0.124.31 → 0.124.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/dist/change-set.js +250 -0
  2. package/dist/clickhouse/access.js +722 -0
  3. package/dist/clickhouse/apply/apply.js +243 -0
  4. package/dist/clickhouse/apply/mutations.js +66 -0
  5. package/dist/clickhouse/apply/statements.js +545 -0
  6. package/dist/clickhouse/catalog-types.js +7 -0
  7. package/dist/clickhouse/container.js +152 -0
  8. package/dist/clickhouse/engine-syntax.js +159 -0
  9. package/dist/clickhouse/entities.js +593 -0
  10. package/dist/clickhouse/expressions.js +107 -0
  11. package/dist/clickhouse/http.js +55 -0
  12. package/dist/clickhouse/import/generator.js +192 -0
  13. package/dist/clickhouse/import/inlined.js +207 -0
  14. package/dist/clickhouse/import/ir.js +160 -0
  15. package/dist/clickhouse/import/live-export.js +131 -0
  16. package/dist/clickhouse/import/parser.js +17 -0
  17. package/dist/clickhouse/live/bind.js +116 -0
  18. package/dist/clickhouse/live/catalog.js +223 -0
  19. package/dist/clickhouse/live/describe-resources.js +91 -0
  20. package/dist/clickhouse/live/undeclarable.js +59 -0
  21. package/dist/clickhouse/overlays/codecs.js +57 -0
  22. package/dist/clickhouse/overlays/engines.js +101 -0
  23. package/dist/clickhouse/overlays/kinds.js +1 -0
  24. package/dist/clickhouse/overlays/projections.js +26 -0
  25. package/dist/clickhouse/overlays/skip-indexes.js +40 -0
  26. package/dist/clickhouse/overlays/ttl.js +34 -0
  27. package/dist/clickhouse/overlays/types.js +59 -0
  28. package/dist/clickhouse/ownership.js +83 -0
  29. package/dist/clickhouse/parser.js +674 -0
  30. package/dist/clickhouse/plan/commands.js +159 -0
  31. package/dist/clickhouse/plan/deep.js +345 -0
  32. package/dist/clickhouse/plan/diff.js +312 -0
  33. package/dist/clickhouse/plan/disruption.js +73 -0
  34. package/dist/clickhouse/plan/normalize.js +545 -0
  35. package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
  36. package/dist/clickhouse/plan/report.js +26 -0
  37. package/dist/clickhouse/plan/rules.js +72 -0
  38. package/dist/clickhouse/plan/schema.js +67 -0
  39. package/dist/clickhouse/plan/server-format.js +35 -0
  40. package/dist/clickhouse/rebuild/backfill.js +265 -0
  41. package/dist/clickhouse/rebuild/engines.js +146 -0
  42. package/dist/clickhouse/rebuild/observe.js +260 -0
  43. package/dist/clickhouse/rebuild/op.js +152 -0
  44. package/dist/clickhouse/rebuild/partitions.js +65 -0
  45. package/dist/clickhouse/rebuild/receipts.js +120 -0
  46. package/dist/clickhouse/rebuild/replicas.js +121 -0
  47. package/dist/clickhouse/rebuild/shards.js +76 -0
  48. package/dist/clickhouse/rebuild/steps.js +383 -0
  49. package/dist/clickhouse/rebuild/verify.js +279 -0
  50. package/dist/clickhouse/row-types.js +151 -0
  51. package/dist/clickhouse/testing/fake-server.js +47 -0
  52. package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
  53. package/dist/clickhouse/testing/writable-server.js +123 -0
  54. package/dist/clickhouse/tokens.js +23 -0
  55. package/dist/clickhouse/topology.js +463 -0
  56. package/dist/clickhouse/type-syntax.js +73 -0
  57. package/dist/clickhouse.js +25 -0
  58. package/dist/codegen/classifier-page.js +93 -0
  59. package/dist/codegen/clickhouse-module.js +249 -0
  60. package/dist/codegen/coverage-page.js +70 -0
  61. package/dist/codegen/docs-cli.js +3 -0
  62. package/dist/codegen/docs.js +222 -0
  63. package/dist/codegen/generate-cli.js +13 -0
  64. package/dist/codegen/generate.js +97 -0
  65. package/dist/codegen/package.js +28 -0
  66. package/dist/codegen/postgres-classifier-page.js +95 -0
  67. package/dist/codegen/postgres-module.js +418 -0
  68. package/dist/codegen/registry.js +52 -0
  69. package/dist/composites/audit-log-table.js +35 -0
  70. package/dist/composites/catalog.js +524 -0
  71. package/dist/composites/cdc-mirror.js +32 -0
  72. package/dist/composites/clickhouse.js +6 -0
  73. package/dist/composites/events-table.js +26 -0
  74. package/dist/composites/index.js +8 -0
  75. package/dist/composites/join-table.js +25 -0
  76. package/dist/composites/postgres.js +6 -0
  77. package/dist/composites/refreshed-view.js +24 -0
  78. package/dist/composites/replacing-table.js +28 -0
  79. package/dist/composites/rollup-view.js +29 -0
  80. package/dist/composites/sharded-table.js +27 -0
  81. package/dist/composites/soft-delete-table.js +34 -0
  82. package/dist/composites/tenant-table.js +26 -0
  83. package/dist/composites/typed.js +8 -0
  84. package/dist/config.js +206 -0
  85. package/dist/core/apply.js +93 -0
  86. package/dist/core/classifier.js +115 -0
  87. package/dist/core/container.js +69 -0
  88. package/dist/core/cursor.js +261 -0
  89. package/dist/core/diff.js +48 -0
  90. package/dist/core/entity.js +58 -0
  91. package/dist/core/find-templates.js +118 -0
  92. package/dist/core/foreign-tables.js +27 -0
  93. package/dist/core/handoff.js +32 -0
  94. package/dist/core/index.js +64 -0
  95. package/dist/core/interpolation.js +191 -0
  96. package/dist/core/normalize.js +43 -0
  97. package/dist/core/ownership.js +119 -0
  98. package/dist/core/receipts.js +22 -0
  99. package/dist/core/references.js +61 -0
  100. package/dist/core/statements.js +42 -0
  101. package/dist/core/template.js +79 -0
  102. package/dist/core/testing/http-stub.js +21 -0
  103. package/dist/core/tokens.js +202 -0
  104. package/dist/core/typed.js +40 -0
  105. package/dist/core/undeclarable.js +46 -0
  106. package/dist/coverage.js +161 -0
  107. package/dist/detect.js +32 -0
  108. package/dist/dialects.js +18 -0
  109. package/dist/files/build-root.js +72 -0
  110. package/dist/files/clickhouse.js +138 -0
  111. package/dist/files/common.js +58 -0
  112. package/dist/files/postgres.js +389 -0
  113. package/dist/generated/clickhouse-types.js +5 -0
  114. package/dist/generated/clickhouse.js +4361 -0
  115. package/dist/generated/postgres-types.js +227 -0
  116. package/dist/generated/postgres.js +3667 -0
  117. package/dist/import-generator.js +14 -0
  118. package/dist/import-parser.js +71 -0
  119. package/dist/index.js +24 -0
  120. package/dist/init-templates.js +290 -0
  121. package/dist/integrity.json +2 -2
  122. package/dist/lint/audit-catalog.js +31 -0
  123. package/dist/lint/audit-lineage.js +22 -0
  124. package/dist/lint/post-synth/audit-entries.js +34 -0
  125. package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
  126. package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
  127. package/dist/lint/post-synth/index.js +112 -0
  128. package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
  129. package/dist/lint/post-synth/postgres-helpers.js +65 -0
  130. package/dist/lint/post-synth/postgres-names.js +507 -0
  131. package/dist/lint/post-synth/sql-helpers.js +23 -0
  132. package/dist/lint/post-synth/sql101.js +59 -0
  133. package/dist/lint/post-synth/sql102.js +34 -0
  134. package/dist/lint/post-synth/sqlch101.js +34 -0
  135. package/dist/lint/post-synth/sqlch102.js +23 -0
  136. package/dist/lint/post-synth/sqlch103.js +45 -0
  137. package/dist/lint/post-synth/sqlch104.js +41 -0
  138. package/dist/lint/post-synth/sqlch105.js +36 -0
  139. package/dist/lint/post-synth/sqlch106.js +27 -0
  140. package/dist/lint/post-synth/sqlch107.js +39 -0
  141. package/dist/lint/post-synth/sqlch108.js +28 -0
  142. package/dist/lint/post-synth/sqlch109.js +21 -0
  143. package/dist/lint/post-synth/sqlch110.js +31 -0
  144. package/dist/lint/post-synth/sqlch111.js +41 -0
  145. package/dist/lint/post-synth/sqlch112.js +28 -0
  146. package/dist/lint/post-synth/sqlch113.js +19 -0
  147. package/dist/lint/post-synth/sqlch114.js +26 -0
  148. package/dist/lint/post-synth/sqlch115.js +30 -0
  149. package/dist/lint/post-synth/sqlch116.js +22 -0
  150. package/dist/lint/post-synth/sqlch117.js +26 -0
  151. package/dist/lint/post-synth/sqlch118.js +27 -0
  152. package/dist/lint/post-synth/sqlch119.js +32 -0
  153. package/dist/lint/post-synth/sqlch120.js +20 -0
  154. package/dist/lint/post-synth/sqlch121.js +11 -0
  155. package/dist/lint/post-synth/sqlch122.js +11 -0
  156. package/dist/lint/post-synth/sqlch123.js +10 -0
  157. package/dist/lint/post-synth/sqlch124.js +21 -0
  158. package/dist/lint/post-synth/sqlch125.js +55 -0
  159. package/dist/lint/post-synth/sqlch126.js +62 -0
  160. package/dist/lint/post-synth/sqlch127.js +132 -0
  161. package/dist/lint/post-synth/sqlpg101.js +25 -0
  162. package/dist/lint/post-synth/sqlpg102.js +35 -0
  163. package/dist/lint/post-synth/sqlpg103.js +23 -0
  164. package/dist/lint/post-synth/sqlpg104.js +23 -0
  165. package/dist/lint/post-synth/sqlpg105.js +22 -0
  166. package/dist/lint/post-synth/sqlpg106.js +22 -0
  167. package/dist/lint/post-synth/sqlpg107.js +22 -0
  168. package/dist/lint/post-synth/sqlpg108.js +28 -0
  169. package/dist/lint/post-synth/sqlpg109.js +39 -0
  170. package/dist/lint/post-synth/sqlpg110.js +38 -0
  171. package/dist/lint/post-synth/sqlpg111.js +23 -0
  172. package/dist/lint/post-synth/sqlpg112.js +25 -0
  173. package/dist/lint/post-synth/sqlpg113.js +29 -0
  174. package/dist/lint/post-synth/sqlpg114.js +39 -0
  175. package/dist/lint/post-synth/sqlpg115.js +48 -0
  176. package/dist/lint/post-synth/sqlpg116.js +26 -0
  177. package/dist/lint/post-synth/sqlpg117.js +24 -0
  178. package/dist/lint/post-synth/sqlpg118.js +22 -0
  179. package/dist/lint/post-synth/sqlpg119.js +41 -0
  180. package/dist/lint/post-synth/sqlpg120.js +83 -0
  181. package/dist/lint/post-synth/sqlpg121.js +33 -0
  182. package/dist/lint/post-synth/sqlpg122.js +27 -0
  183. package/dist/lint/post-synth/sqlpg123.js +69 -0
  184. package/dist/lint/post-synth/sqlpg124.js +50 -0
  185. package/dist/lint/post-synth/sqlpg125.js +41 -0
  186. package/dist/lint/post-synth/sqlpg126.js +38 -0
  187. package/dist/lint/rules/index.js +12 -0
  188. package/dist/lint/rules/postgres-templates.js +27 -0
  189. package/dist/lint/rules/sqlch001.js +79 -0
  190. package/dist/lint/rules/sqlch002.js +124 -0
  191. package/dist/lint/rules/sqlch003.js +69 -0
  192. package/dist/lint/rules/sqlch004.js +83 -0
  193. package/dist/lint/rules/sqlpg001.js +68 -0
  194. package/dist/lint/rules/sqlpg002.js +69 -0
  195. package/dist/lint/rules/sqlpg003.js +58 -0
  196. package/dist/lint/rules/sqlpg004.js +77 -0
  197. package/dist/lint/rules/sqlpg005.js +86 -0
  198. package/dist/lint/rules/templates.js +25 -0
  199. package/dist/lint/rules/typed-names.js +132 -0
  200. package/dist/live-dialect.js +32 -0
  201. package/dist/lsp/catalog.js +44 -0
  202. package/dist/lsp/completions.js +136 -0
  203. package/dist/lsp/hover.js +115 -0
  204. package/dist/lsp/postgres-catalog.js +176 -0
  205. package/dist/lsp/postgres-template.js +171 -0
  206. package/dist/lsp/postgres.js +266 -0
  207. package/dist/lsp/refs.js +60 -0
  208. package/dist/lsp/registry.d.ts.map +1 -1
  209. package/dist/lsp/registry.js +19 -0
  210. package/dist/lsp/template.js +143 -0
  211. package/dist/manifest.json +1 -1
  212. package/dist/mcp/index.d.ts.map +1 -1
  213. package/dist/mcp/index.js +293 -0
  214. package/dist/mcp/postgres.js +117 -0
  215. package/dist/migration-statements.js +243 -0
  216. package/dist/op/activities/clickhouse-apply.js +83 -0
  217. package/dist/op/activities/clickhouse-emulator.js +53 -0
  218. package/dist/op/activities/clickhouse-rebuild.js +119 -0
  219. package/dist/op/activities/index.js +26 -0
  220. package/dist/op/activities/postgres-apply.js +126 -0
  221. package/dist/op/activities/postgres-emulator.js +70 -0
  222. package/dist/op/activities/postgres-migration.js +156 -0
  223. package/dist/op/activities/sql-exec.js +57 -0
  224. package/dist/op/activity-contracts.js +103 -0
  225. package/dist/package-cli.js +14 -0
  226. package/dist/plugin.js +259 -0
  227. package/dist/postgres/access/acl.js +344 -0
  228. package/dist/postgres/access/changes.js +30 -0
  229. package/dist/postgres/access/import.js +61 -0
  230. package/dist/postgres/access/live.js +96 -0
  231. package/dist/postgres/apply/apply.js +557 -0
  232. package/dist/postgres/apply/statements.js +882 -0
  233. package/dist/postgres/catalog-types.js +18 -0
  234. package/dist/postgres/container.js +58 -0
  235. package/dist/postgres/entities.js +1390 -0
  236. package/dist/postgres/entity-types.js +28 -0
  237. package/dist/postgres/import/generator.js +283 -0
  238. package/dist/postgres/import/ir.js +53 -0
  239. package/dist/postgres/import/live-export.js +83 -0
  240. package/dist/postgres/keywords.js +32 -0
  241. package/dist/postgres/live/bind.js +130 -0
  242. package/dist/postgres/live/catalog-kinds.js +301 -0
  243. package/dist/postgres/live/catalog.js +644 -0
  244. package/dist/postgres/live/client.js +151 -0
  245. package/dist/postgres/live/describe-resources.js +135 -0
  246. package/dist/postgres/live/undeclarable.js +111 -0
  247. package/dist/postgres/migrate/backfill.js +170 -0
  248. package/dist/postgres/migrate/batches.js +122 -0
  249. package/dist/postgres/migrate/carry.js +518 -0
  250. package/dist/postgres/migrate/handoff.js +59 -0
  251. package/dist/postgres/migrate/names.js +61 -0
  252. package/dist/postgres/migrate/observe.js +360 -0
  253. package/dist/postgres/migrate/op.js +178 -0
  254. package/dist/postgres/migrate/receipts.js +117 -0
  255. package/dist/postgres/migrate/replication.js +106 -0
  256. package/dist/postgres/migrate/steps.js +514 -0
  257. package/dist/postgres/overlays/columns.js +31 -0
  258. package/dist/postgres/overlays/constraints.js +52 -0
  259. package/dist/postgres/overlays/extension-objects.js +88 -0
  260. package/dist/postgres/overlays/indexes.js +25 -0
  261. package/dist/postgres/overlays/kinds.js +2 -0
  262. package/dist/postgres/overlays/partitioning.js +23 -0
  263. package/dist/postgres/overlays/relations.js +25 -0
  264. package/dist/postgres/overlays/storage.js +65 -0
  265. package/dist/postgres/overlays/type-ddl.js +36 -0
  266. package/dist/postgres/overlays/types.js +74 -0
  267. package/dist/postgres/parser.js +1824 -0
  268. package/dist/postgres/plan/commands.js +137 -0
  269. package/dist/postgres/plan/deep.js +324 -0
  270. package/dist/postgres/plan/diff.js +499 -0
  271. package/dist/postgres/plan/disruption.js +64 -0
  272. package/dist/postgres/plan/normalize.js +889 -0
  273. package/dist/postgres/plan/report.js +21 -0
  274. package/dist/postgres/plan/rules.js +115 -0
  275. package/dist/postgres/plan/schema.js +105 -0
  276. package/dist/postgres/plan/server-normalize.js +166 -0
  277. package/dist/postgres/providers/aurora.js +44 -0
  278. package/dist/postgres/providers/azure.js +53 -0
  279. package/dist/postgres/providers/cloud-sql.js +41 -0
  280. package/dist/postgres/providers/index.js +73 -0
  281. package/dist/postgres/providers/neon.js +44 -0
  282. package/dist/postgres/providers/rds.js +47 -0
  283. package/dist/postgres/providers/supabase.js +45 -0
  284. package/dist/postgres/providers/types.js +21 -0
  285. package/dist/postgres/row-types.js +107 -0
  286. package/dist/postgres/testing/fake-client.js +34 -0
  287. package/dist/postgres/testing/migration.js +48 -0
  288. package/dist/postgres/testing/roundtrip-schema.js +64 -0
  289. package/dist/postgres/testing/server.js +47 -0
  290. package/dist/postgres/testing/writable-server.js +160 -0
  291. package/dist/postgres/tokens.js +41 -0
  292. package/dist/postgres.js +15 -0
  293. package/dist/read/clickhouse.js +82 -0
  294. package/dist/read/objects.js +44 -0
  295. package/dist/read/postgres.js +104 -0
  296. package/dist/read.js +52 -0
  297. package/dist/receipts.js +207 -0
  298. package/dist/serializer.js +172 -0
  299. package/dist/skill-defs.js +93 -0
  300. package/dist/spec/catalog.js +191 -0
  301. package/dist/spec/fetch.d.ts.map +1 -1
  302. package/dist/spec/fetch.js +90 -0
  303. package/dist/spec/pin.js +42 -0
  304. package/dist/spec/postgres-catalog.js +171 -0
  305. package/dist/spec/postgres-fetch.js +91 -0
  306. package/dist/spec/postgres-pin.js +82 -0
  307. package/dist/sql-files.js +206 -0
  308. package/dist/sql-types-command.js +97 -0
  309. package/dist/sql-types.js +323 -0
  310. package/dist/token-source.js +156 -0
  311. package/dist/validate-cli.js +5 -0
  312. package/dist/validate.js +151 -0
  313. package/dist/workspace-outputs.js +191 -0
  314. package/package.json +7 -7
  315. package/src/codegen/package.ts +2 -2
  316. package/src/lint/rules/typescript-7.test.ts +2 -1
  317. package/src/lsp/catalog.ts +2 -2
  318. package/src/lsp/postgres-catalog.ts +2 -2
  319. package/src/lsp/registry.ts +2 -1
  320. package/src/mcp/index.ts +4 -2
  321. package/src/spec/fetch.ts +2 -2
  322. package/src/spec/postgres-fetch.ts +2 -2
@@ -0,0 +1,545 @@
1
+ /**
2
+ * The statements that make one classified change (#3208): a `CREATE` with
3
+ * chant's marker in its comment, the `ALTER`s for the metadata and
4
+ * background-rewrite classes, and the `DROP` a prune issues.
5
+ *
6
+ * Each function here takes a declared object and the changes the classifier
7
+ * reported for it, and returns SQL; nothing is sent. The applier
8
+ * (`./apply.ts`) sends them, and the rebuild migration (#3198) builds its new
9
+ * table and swaps it in with the same pieces.
10
+ *
11
+ * The SQL is taken from the declaration's own text wherever it can be: a
12
+ * column's definition, an index's expression, a TTL, a setting's value are
13
+ * the spans the parser found in the declared `CREATE`, not the normalized
14
+ * forms the classifier compared. The normalized form is for comparing; what
15
+ * the server is sent is what the author wrote.
16
+ */
17
+ import { isTrivia, tokenizeText } from "../tokens.js";
18
+ import { parseCreate } from "../parser.js";
19
+ import { canonicalObject } from "../plan/normalize.js";
20
+ import { CLASSIFIER_RULES } from "../plan/rules.js";
21
+ import { stampedComment } from "../ownership.js";
22
+ import { CLICKHOUSE_ENTITY_TYPES } from "../entities.js";
23
+ import { renderFor } from "../topology.js";
24
+ import { accessClauseText, alterAccessField, alterQuotaOrProfileField, grantStatement, revokeStatement, SECRETLESS } from "../access.js";
25
+ /** A backquoted identifier. */
26
+ export const ident = (name) => `\`${name.replace(/`/g, "``")}\``;
27
+ /** `database.name` backquoted, or a database's bare name. */
28
+ export const qualifiedIdent = (database, name) => database !== undefined ? `${ident(database)}.${ident(name)}` : ident(name);
29
+ /** A single-quoted string literal. */
30
+ export const sqlString = (value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
31
+ /** `db.name` split at its first dot. */
32
+ export function splitQualified(name) {
33
+ const at = name.indexOf(".");
34
+ return at < 0 ? { name } : { database: name.slice(0, at), name: name.slice(at + 1) };
35
+ }
36
+ /** The step for a statement that makes a change under `rule`: a rewrite-class rule waits on its mutation. */
37
+ export function stepFor(sql, rule) {
38
+ const cls = CLASSIFIER_RULES[rule].class;
39
+ return { sql, rewrite: cls === "rewrite", rule, class: cls };
40
+ }
41
+ const parsedCache = new WeakMap();
42
+ function parsed(obj) {
43
+ let p = parsedCache.get(obj);
44
+ if (!p) {
45
+ const tokens = tokenizeText(obj.ddl, 0);
46
+ p = { tokens, node: parseCreate(tokens) };
47
+ parsedCache.set(obj, p);
48
+ }
49
+ return p;
50
+ }
51
+ const text = (tokens, span) => span && span.to > span.from
52
+ ? tokens
53
+ .slice(span.from, span.to)
54
+ .map((t) => t.text)
55
+ .join("")
56
+ .trim()
57
+ : undefined;
58
+ /** The object's name on the server, quoted. */
59
+ export const objectIdent = (obj) => qualifiedIdent(obj.canonical.database, obj.canonical.name);
60
+ /** The statement's end, past its significant tokens: trailing whitespace, comments and a `;` left out. */
61
+ function statementBody(tokens) {
62
+ let end = tokens.length;
63
+ while (end > 0 && (isTrivia(tokens[end - 1]) || tokens[end - 1].text === ";"))
64
+ end--;
65
+ return tokens
66
+ .slice(0, end)
67
+ .map((t) => t.text)
68
+ .join("");
69
+ }
70
+ /**
71
+ * The declared `CREATE` with chant's ownership marker in its comment: the
72
+ * declared comment, if any, with the trailer after it, or a `COMMENT` clause
73
+ * added at the end. Set in the `CREATE` itself, so no object is ever created
74
+ * unmarked. `orReplace` turns `CREATE VIEW` into `CREATE OR REPLACE VIEW`;
75
+ * `trailer` adds pairs to the marker (the rebuild migration's working
76
+ * objects, `../rebuild/`).
77
+ */
78
+ export function createStatement(obj, marker, opts = {}) {
79
+ const { tokens, node } = parsed(obj);
80
+ // A function has no comment to carry the marker: it is sent as declared.
81
+ if (node.statement === "function") {
82
+ const body = statementBody(tokens);
83
+ return opts.orReplace && !node.orReplace ? body.replace(/^(\s*(?:--[^\n]*\n\s*)*)CREATE\s+/i, "$1CREATE OR REPLACE ") : body;
84
+ }
85
+ const literal = sqlString(stampedComment(obj.canonical.comment, marker, opts.trailer));
86
+ let sql;
87
+ if (node.comment) {
88
+ sql =
89
+ tokens
90
+ .slice(0, node.comment.from)
91
+ .map((t) => t.text)
92
+ .join("") +
93
+ literal +
94
+ statementBody(tokens.slice(node.comment.to));
95
+ }
96
+ else {
97
+ sql = `${statementBody(tokens)} COMMENT ${literal}`;
98
+ }
99
+ if (opts.orReplace && (node.statement === "view" || node.statement === "dictionary" || node.statement === "table") && !node.orReplace) {
100
+ sql = sql.replace(/^(\s*(?:--[^\n]*\n\s*)*)CREATE\s+/i, "$1CREATE OR REPLACE ");
101
+ }
102
+ return sql;
103
+ }
104
+ /** Restamp the object's comment: the declared comment with this project's marker. */
105
+ export function commentStatement(obj, marker) {
106
+ const literal = sqlString(stampedComment(obj.canonical.comment, marker));
107
+ return obj.type === CLICKHOUSE_ENTITY_TYPES.database
108
+ ? `ALTER DATABASE ${objectIdent(obj)} MODIFY COMMENT ${literal}`
109
+ : `ALTER TABLE ${objectIdent(obj)} MODIFY COMMENT ${literal}`;
110
+ }
111
+ /** The `DROP` for an object a prune removes. `SYNC` so the name is free when it returns. */
112
+ export function dropStatement(type, database, name) {
113
+ if (type === CLICKHOUSE_ENTITY_TYPES.database)
114
+ return `DROP DATABASE ${ident(name)} SYNC`;
115
+ if (type === CLICKHOUSE_ENTITY_TYPES.function)
116
+ return `DROP FUNCTION ${ident(name)}`;
117
+ const what = type === CLICKHOUSE_ENTITY_TYPES.table ? "TABLE" : type === CLICKHOUSE_ENTITY_TYPES.dictionary ? "DICTIONARY" : "VIEW";
118
+ return `DROP ${what} ${qualifiedIdent(database, name)} SYNC`;
119
+ }
120
+ /** Changes a plan refuses to make in place: they run as the rebuild migration (#3198). */
121
+ export const isRebuild = (c) => c.class === "rebuild";
122
+ /** Changes that remove data from an object the build still declares: withheld unless the apply may delete. */
123
+ export const isDestructiveAlter = (c) => c.rule === "SQLCH202";
124
+ /**
125
+ * A refused change in a sentence: the rule, the restriction, where it is
126
+ * stated, and where the change goes instead: the rebuild migration Op for
127
+ * the table (`key`, `database.name`), or for a view or database, which the
128
+ * Op does not rebuild, a drop and a create.
129
+ */
130
+ export function refusalDetail(changes, key, type) {
131
+ const parts = changes.map((c) => {
132
+ const rule = CLASSIFIER_RULES[c.rule];
133
+ const values = c.before !== undefined || c.after !== undefined ? ` (${c.before ?? "none"} -> ${c.after ?? "none"})` : "";
134
+ return `${c.rule} ${rule.title} on ${c.field}${values}: ${rule.restriction} ${rule.cite}`;
135
+ });
136
+ const instead = type === undefined || type === CLICKHOUSE_ENTITY_TYPES.table
137
+ ? `Run it as the rebuild migration Op: ClickHouseRebuildOp({ table: ${JSON.stringify(key ?? "<database.table>")}, ... }) from @intentius/chant-lexicon-sql/clickhouse`
138
+ : "The rebuild migration Op rebuilds tables only; drop and create this object instead";
139
+ // A clause chant does not read (#3872) is refused for review: whether ALTER can make it is not known.
140
+ if (changes.every((c) => c.rule === "SQLCH290"))
141
+ return `needs review, since chant does not read the clause: ${parts.join("; ")}`;
142
+ return `needs a rebuild, which ALTER cannot make: ${parts.join("; ")}. ${instead}`;
143
+ }
144
+ /**
145
+ * The declaration under another name in the same database: the rebuild
146
+ * migration creates its new table from the declared `CREATE` this way.
147
+ */
148
+ export function renamedDeclaration(obj, name, defaultDatabase = "default") {
149
+ const { tokens, node } = parsed(obj);
150
+ if (node.statement === "database")
151
+ throw new Error(`${obj.key}: a database is not renamed this way`);
152
+ const ddl = tokens
153
+ .slice(0, node.name.from)
154
+ .map((t) => t.text)
155
+ .join("") +
156
+ qualifiedIdent(obj.canonical.database ?? defaultDatabase, name) +
157
+ tokens
158
+ .slice(node.name.to)
159
+ .map((t) => t.text)
160
+ .join("");
161
+ const canonical = canonicalObject(ddl, defaultDatabase);
162
+ return { ...obj, ddl, canonical, key: `${canonical.database}.${canonical.name}` };
163
+ }
164
+ // ── tables ────────────────────────────────────────────────────────────
165
+ /** A column's whole definition as declared: from its name to the comma or parenthesis that ends it. */
166
+ function columnDefinition(tokens, node, name) {
167
+ const col = node.columns.find((c) => c.name === name);
168
+ if (!col)
169
+ throw new Error(`no declared column ${name}`);
170
+ let depth = 0;
171
+ let i = col.nameSpan.from;
172
+ for (; i < tokens.length; i++) {
173
+ const t = tokens[i];
174
+ if (t.text === "(" || t.text === "[")
175
+ depth++;
176
+ else if (t.text === ")" || t.text === "]") {
177
+ if (depth === 0)
178
+ break;
179
+ depth--;
180
+ }
181
+ else if (t.text === "," && depth === 0)
182
+ break;
183
+ }
184
+ return tokens
185
+ .slice(col.nameSpan.from, i)
186
+ .filter((t) => t.kind !== "comment")
187
+ .map((t) => t.text)
188
+ .join("")
189
+ .trim();
190
+ }
191
+ function columnType(tokens, node, name) {
192
+ const col = node.columns.find((c) => c.name === name);
193
+ const type = text(tokens, col.type) ?? "";
194
+ return `${type}${col.nullable === true ? " NULL" : col.nullable === false ? " NOT NULL" : ""}`;
195
+ }
196
+ /** Where a column goes: after the declared column before it, or first. */
197
+ function position(node, name) {
198
+ const i = node.columns.findIndex((c) => c.name === name);
199
+ return i <= 0 ? "FIRST" : `AFTER ${ident(node.columns[i - 1].name)}`;
200
+ }
201
+ /** `columns.<name>` or `columns.<name>.<part>`. */
202
+ function columnField(field) {
203
+ const m = /^columns\.([^.]+)(?:\.(type|default|codec|ttl|comment))?$/.exec(field);
204
+ return m ? { column: m[1], ...(m[2] ? { part: m[2] } : {}) } : undefined;
205
+ }
206
+ /**
207
+ * The column order once the steps before it have run, and the moves that make
208
+ * it the declared order.
209
+ */
210
+ function reorderSteps(t, tokens, node, live, changes) {
211
+ if (!live)
212
+ return [];
213
+ const renamed = new Map(changes.filter((c) => c.rule === "SQLCH212").map((c) => [c.before, c.after]));
214
+ const dropped = new Set(changes.filter((c) => c.rule === "SQLCH202").map((c) => columnField(c.field).column));
215
+ const declared = node.columns.map((c) => c.name);
216
+ const current = live.columns.map((c) => renamed.get(c.name) ?? c.name).filter((n) => !dropped.has(n));
217
+ // Added columns went in at their declared place.
218
+ for (const [i, name] of declared.entries()) {
219
+ if (current.includes(name))
220
+ continue;
221
+ const prev = i === 0 ? -1 : current.indexOf(declared[i - 1]);
222
+ current.splice(prev + 1, 0, name);
223
+ }
224
+ const steps = [];
225
+ for (const [i, name] of declared.entries()) {
226
+ const want = i === 0 ? undefined : declared[i - 1];
227
+ const at = current.indexOf(name);
228
+ const have = at <= 0 ? undefined : current[at - 1];
229
+ if (have === want)
230
+ continue;
231
+ current.splice(at, 1);
232
+ current.splice(want === undefined ? 0 : current.indexOf(want) + 1, 0, name);
233
+ steps.push(stepFor(`ALTER TABLE ${t} MODIFY COLUMN ${ident(name)} ${columnType(tokens, node, name)} ${want === undefined ? "FIRST" : `AFTER ${ident(want)}`}`, "SQLCH209"));
234
+ }
235
+ return steps;
236
+ }
237
+ function tableSteps(obj, changes, live) {
238
+ const { tokens, node } = parsed(obj);
239
+ const t = objectIdent(obj);
240
+ const alter = (clause, rule) => stepFor(`ALTER TABLE ${t} ${clause}`, rule);
241
+ const steps = [];
242
+ const of = (rule) => changes.filter((c) => c.rule === rule);
243
+ for (const c of of("SQLCH212"))
244
+ steps.push(alter(`RENAME COLUMN ${ident(c.before)} TO ${ident(c.after)}`, "SQLCH212"));
245
+ // Added columns, in declared order, each at its declared place. Appending
246
+ // new columns to the sorting key has to happen in the same ALTER as adding
247
+ // them (SQLCH216), so then they go together.
248
+ const added = of("SQLCH201")
249
+ .map((c) => columnField(c.field).column)
250
+ .sort((a, b) => node.columns.findIndex((c) => c.name === a) - node.columns.findIndex((c) => c.name === b));
251
+ const adds = added.map((name) => `ADD COLUMN ${columnDefinition(tokens, node, name)} ${position(node, name)}`);
252
+ const sortKey = of("SQLCH216")[0];
253
+ if (sortKey)
254
+ steps.push(alter([...adds, `MODIFY ORDER BY ${text(tokens, node.orderBy)}`].join(", "), "SQLCH216"));
255
+ else
256
+ for (const a of adds)
257
+ steps.push(alter(a, "SQLCH201"));
258
+ for (const c of of("SQLCH202"))
259
+ steps.push(alter(`DROP COLUMN ${ident(columnField(c.field).column)}`, "SQLCH202"));
260
+ for (const c of changes) {
261
+ const f = columnField(c.field);
262
+ if (!f?.part)
263
+ continue;
264
+ const col = node.columns.find((x) => x.name === f.column);
265
+ const name = ident(f.column);
266
+ switch (c.rule) {
267
+ case "SQLCH210":
268
+ steps.push(alter(`MODIFY COLUMN ${name} ${columnType(tokens, node, f.column)}`, c.rule));
269
+ break;
270
+ case "SQLCH207":
271
+ if (col.default)
272
+ steps.push(alter(`MODIFY COLUMN ${name} ${col.default.kind}${col.default.expr ? ` ${text(tokens, col.default.expr)}` : ""}`, c.rule));
273
+ else
274
+ steps.push(alter(`MODIFY COLUMN ${name} REMOVE ${(c.before ?? "DEFAULT").split(" ")[0]}`, c.rule));
275
+ break;
276
+ case "SQLCH208":
277
+ steps.push(alter(col.codec ? `MODIFY COLUMN ${name} CODEC(${text(tokens, col.codec)})` : `MODIFY COLUMN ${name} REMOVE CODEC`, c.rule));
278
+ break;
279
+ case "SQLCH205":
280
+ steps.push(alter(col.ttl ? `MODIFY COLUMN ${name} TTL ${text(tokens, col.ttl)}` : `MODIFY COLUMN ${name} REMOVE TTL`, c.rule));
281
+ break;
282
+ case "SQLCH203":
283
+ steps.push(alter(`COMMENT COLUMN ${name} ${sqlString(obj.canonical.columns.find((x) => x.name === f.column)?.comment ?? "")}`, c.rule));
284
+ break;
285
+ }
286
+ }
287
+ for (const c of of("SQLCH204")) {
288
+ const name = c.field.slice("indexes.".length);
289
+ if (c.before !== undefined)
290
+ steps.push(alter(`DROP INDEX ${ident(name)}`, c.rule));
291
+ const ix = node.indexes.find((x) => x.name === name);
292
+ if (ix) {
293
+ steps.push(alter(`ADD INDEX ${ident(name)} ${text(tokens, ix.expr)} TYPE ${text(tokens, ix.type)}${ix.granularity ? ` GRANULARITY ${text(tokens, ix.granularity)}` : ""}`, c.rule));
294
+ }
295
+ }
296
+ for (const c of of("SQLCH214")) {
297
+ const name = c.field.slice("projections.".length);
298
+ if (c.before !== undefined)
299
+ steps.push(alter(`DROP PROJECTION ${ident(name)}`, c.rule));
300
+ const p = node.projections.find((x) => x.name === name);
301
+ if (p)
302
+ steps.push(alter(`ADD PROJECTION ${ident(name)} (${text(tokens, p.body)})`, c.rule));
303
+ }
304
+ for (const c of of("SQLCH215")) {
305
+ const name = c.field.slice("constraints.".length);
306
+ if (c.before !== undefined)
307
+ steps.push(alter(`DROP CONSTRAINT ${ident(name)}`, c.rule));
308
+ const k = node.constraints.find((x) => x.name === name);
309
+ if (k)
310
+ steps.push(alter(`ADD CONSTRAINT ${ident(name)} ${k.kind} ${text(tokens, k.expr)}`, c.rule));
311
+ }
312
+ for (const c of of("SQLCH205").filter((x) => x.field === "ttl")) {
313
+ steps.push(alter(node.ttl ? `MODIFY TTL ${text(tokens, node.ttl)}` : "REMOVE TTL", c.rule));
314
+ }
315
+ for (const c of of("SQLCH217"))
316
+ steps.push(alter(node.sampleBy ? `MODIFY SAMPLE BY ${text(tokens, node.sampleBy)}` : "REMOVE SAMPLE BY", c.rule));
317
+ for (const c of of("SQLCH206")) {
318
+ const key = c.field.slice("settings.".length);
319
+ const s = node.settings?.find((x) => x.key === key);
320
+ // A setting declared at its default compares as absent: set it as written.
321
+ steps.push(alter(s ? `MODIFY SETTING ${key} = ${text(tokens, s.value)}` : `RESET SETTING ${key}`, c.rule));
322
+ }
323
+ if (of("SQLCH209").length > 0)
324
+ steps.push(...reorderSteps(t, tokens, node, live, changes));
325
+ return steps;
326
+ }
327
+ function viewSteps(obj, changes, marker) {
328
+ const { tokens, node } = parsed(obj);
329
+ const t = objectIdent(obj);
330
+ const steps = [];
331
+ if (!node.materialized) {
332
+ if (changes.some((c) => c.rule === "SQLCH240"))
333
+ steps.push(stepFor(createStatement(obj, marker, { orReplace: true }), "SQLCH240"));
334
+ return steps;
335
+ }
336
+ if (changes.some((c) => c.rule === "SQLCH241"))
337
+ steps.push(stepFor(`ALTER TABLE ${t} MODIFY QUERY ${text(tokens, node.select)}`, "SQLCH241"));
338
+ if (changes.some((c) => c.rule === "SQLCH244") && node.refresh)
339
+ steps.push(stepFor(`ALTER TABLE ${t} MODIFY REFRESH ${text(tokens, node.refresh)}`, "SQLCH244"));
340
+ return steps;
341
+ }
342
+ /**
343
+ * The statements that make an existing object match its declaration, for the
344
+ * metadata and background-rewrite changes the classifier reported for it.
345
+ * Rebuild-class changes are not handled here: the caller refuses them.
346
+ * `live` is the object's current definition, which a column reorder needs.
347
+ *
348
+ * The object's own comment, and chant's marker in it, are not set here
349
+ * (`commentStatement`), except where a plain view is replaced whole.
350
+ */
351
+ export function alterSteps(obj, changes, opts = {}) {
352
+ const steps = [];
353
+ for (const c of changes.filter((x) => x.rule === "SQLCH231"))
354
+ steps.push(stepFor(`RENAME DATABASE ${ident(c.before)} TO ${ident(c.after)}`, "SQLCH231"));
355
+ for (const c of changes.filter((x) => x.rule === "SQLCH230")) {
356
+ const from = splitQualified(c.before);
357
+ const to = splitQualified(c.after);
358
+ const what = obj.type === CLICKHOUSE_ENTITY_TYPES.dictionary ? "DICTIONARY" : "TABLE";
359
+ steps.push(stepFor(`RENAME ${what} ${qualifiedIdent(from.database, from.name)} TO ${qualifiedIdent(to.database, to.name)}`, "SQLCH230"));
360
+ }
361
+ if (obj.type === CLICKHOUSE_ENTITY_TYPES.function && changes.some((c) => c.rule === "SQLCH260")) {
362
+ steps.push(stepFor(createStatement(obj, opts.marker, { orReplace: true }), "SQLCH260"));
363
+ }
364
+ // A dictionary is replaced whole, its comment and marker with it.
365
+ if (obj.type === CLICKHOUSE_ENTITY_TYPES.dictionary && changes.some((c) => c.rule === "SQLCH245")) {
366
+ steps.push(stepFor(createStatement(obj, opts.marker, { orReplace: true }), "SQLCH245"));
367
+ }
368
+ // A table over a table function stores nothing: it is replaced whole (#3911), its comment and marker with it.
369
+ if (obj.type === CLICKHOUSE_ENTITY_TYPES.table && changes.some((c) => c.rule === "SQLCH219"))
370
+ steps.push(stepFor(createStatement(obj, opts.marker, { orReplace: true }), "SQLCH219"));
371
+ else if (obj.type === CLICKHOUSE_ENTITY_TYPES.table)
372
+ steps.push(...tableSteps(obj, changes, opts.live));
373
+ else if (obj.type === CLICKHOUSE_ENTITY_TYPES.view || obj.type === CLICKHOUSE_ENTITY_TYPES.materializedView)
374
+ steps.push(...viewSteps(obj, changes, opts.marker));
375
+ return steps;
376
+ }
377
+ /** Whether the steps for these changes already set the object's comment (a plain view replaced whole). */
378
+ export function stepsSetComment(obj, changes) {
379
+ return ((obj.type === CLICKHOUSE_ENTITY_TYPES.view && changes.some((c) => c.rule === "SQLCH240")) ||
380
+ (obj.type === CLICKHOUSE_ENTITY_TYPES.table && changes.some((c) => c.rule === "SQLCH219")) ||
381
+ (obj.type === CLICKHOUSE_ENTITY_TYPES.dictionary && changes.some((c) => c.rule === "SQLCH245")));
382
+ }
383
+ /** Drop order: what reads from a table before the table (a dictionary before its source), a database last. */
384
+ const DROP_ORDER = { materializedView: 0, view: 1, dictionary: 2, table: 3, database: 4, function: 5 };
385
+ /**
386
+ * The statements that take the current schema to the declared one, from the
387
+ * classified changes between them. Pure: nothing is read or sent. The applier
388
+ * (`./apply.ts`) runs the steps against a server; `diffStatements`
389
+ * (`../../migration-statements.ts`) renders the same steps for a migration
390
+ * file. Anything that changes the SQL sent for a change belongs here, so
391
+ * both see it.
392
+ */
393
+ export function planStatements(input) {
394
+ const plan = planDeclared(input);
395
+ if (!input.topology)
396
+ return plan;
397
+ const topology = input.topology;
398
+ const render = (step) => ({ ...step, sql: renderFor(step.sql, topology) });
399
+ return {
400
+ objects: plan.objects.map((o) => (o.verdict === "create" || o.verdict === "alter" ? { ...o, steps: o.steps.map(render) } : o)),
401
+ drops: plan.drops.map((d) => ({ ...d, step: render(d.step) })),
402
+ };
403
+ }
404
+ const NEVER_DROPPED = new Set(["function", "user", "role", "rowPolicy", "quota", "settingsProfile", "grants"]);
405
+ /** The entity types of access control (`../access.ts`): no comment, no marker, never dropped. */
406
+ export const ACCESS_TYPES = new Set([
407
+ CLICKHOUSE_ENTITY_TYPES.user,
408
+ CLICKHOUSE_ENTITY_TYPES.role,
409
+ CLICKHOUSE_ENTITY_TYPES.rowPolicy,
410
+ CLICKHOUSE_ENTITY_TYPES.quota,
411
+ CLICKHOUSE_ENTITY_TYPES.settingsProfile,
412
+ CLICKHOUSE_ENTITY_TYPES.grant,
413
+ ]);
414
+ /** What a user, role, row policy, quota, settings profile or grantee's grants take (`../access.ts`). */
415
+ function accessStatements(obj, mine, live, deferred, environmentCreatesUsers) {
416
+ const kind = obj.canonical.kind;
417
+ const name = obj.canonical.name;
418
+ if (kind === "grants") {
419
+ const atom = (json) => JSON.parse(json ?? "{}");
420
+ const declared = obj.canonical.access ?? {};
421
+ const current = live?.access ?? {};
422
+ const created = mine.some((c) => c.rule === "SQLCH200");
423
+ const revokes = created ? [] : mine.filter((c) => c.rule === "SQLCH274").map((c) => stepFor(revokeStatement(atom(current[c.before]), name), "SQLCH274"));
424
+ const grants = (created ? Object.keys(declared) : mine.filter((c) => c.rule === "SQLCH273").map((c) => c.after)).map((key) => stepFor(grantStatement(atom(declared[key]), name), "SQLCH273"));
425
+ // A user's DEFAULT ROLE names roles it must already hold: set after its grants.
426
+ const defaultRole = deferred.get(name);
427
+ const after = defaultRole ? [defaultRole.step] : [];
428
+ if (defaultRole)
429
+ mine = [...mine, defaultRole.change];
430
+ return { verdict: created ? "create" : "alter", obj, changes: mine, steps: [...revokes, ...grants, ...after] };
431
+ }
432
+ const body = statementBody(tokenizeText(obj.ddl, 0));
433
+ if (mine.some((c) => c.rule === "SQLCH200")) {
434
+ const method = obj.canonical.access?.identified?.split(" ")[0];
435
+ if (kind === "user" && (method === undefined || !SECRETLESS.has(method))) {
436
+ if (environmentCreatesUsers) {
437
+ // #3717: the environment created the user with its password; the declared clauses are chant's to set.
438
+ const clauses = accessClauseText(obj.ddl);
439
+ const steps = USER_CLAUSES.filter((f) => clauses[f] !== undefined && !(f === "defaultRole" && deferred.has(name))).map((f) => stepFor(alterAccessField("user", name, f, obj.canonical.access ?? {}, clauses), "SQLCH271"));
440
+ return { verdict: "alter", obj, changes: mine, steps };
441
+ }
442
+ return {
443
+ verdict: "withheld",
444
+ obj,
445
+ changes: mine,
446
+ withheld: mine,
447
+ detail: `the user's password is the environment's, so chant does not create it: create ${name} with its password (CREATE USER ${ident(name)} IDENTIFIED BY ...), and the next apply makes the rest of it as declared`,
448
+ };
449
+ }
450
+ return { verdict: "create", obj, changes: mine, steps: [stepFor(body, "SQLCH200")] };
451
+ }
452
+ if (kind === "rowPolicy") {
453
+ // `CREATE ROW POLICY OR REPLACE`: ClickHouse takes OR REPLACE after the kind.
454
+ const replace = /^\s*(?:--[^\n]*\n\s*)*CREATE\s+(?:ROW\s+)?POLICY\s+OR\s+REPLACE\b/i.test(body)
455
+ ? body
456
+ : body.replace(/^(\s*(?:--[^\n]*\n\s*)*CREATE\s+(?:ROW\s+)?POLICY)\s+(?:IF\s+NOT\s+EXISTS\s+)?/i, "$1 OR REPLACE ");
457
+ return { verdict: "alter", obj, changes: mine, steps: mine.some((c) => c.rule === "SQLCH272") ? [stepFor(replace, "SQLCH272")] : [] };
458
+ }
459
+ if (kind === "quota" || kind === "settingsProfile") {
460
+ // ALTER, never OR REPLACE: a replaced profile is a new one, and what inherited the old one loses it.
461
+ const clauses = accessClauseText(obj.ddl);
462
+ const rule = kind === "quota" ? "SQLCH275" : "SQLCH276";
463
+ const steps = mine.filter((c) => c.rule === rule).map((c) => stepFor(alterQuotaOrProfileField(kind, name, c.field, obj.canonical.access ?? {}, clauses), rule));
464
+ return { verdict: "alter", obj, changes: mine, steps };
465
+ }
466
+ const clauses = accessClauseText(obj.ddl);
467
+ const rule = kind === "user" ? "SQLCH271" : "SQLCH270";
468
+ const steps = mine
469
+ .filter((c) => c.rule === rule && !(kind === "user" && c.field === "defaultRole" && deferred.has(name)))
470
+ .map((c) => stepFor(alterAccessField(kind, name, c.field, obj.canonical.access ?? {}, clauses), rule));
471
+ return { verdict: "alter", obj, changes: mine, steps };
472
+ }
473
+ /** A user's clauses the versioned path sets with `ALTER USER` (#3717), in order; `DEFAULT ROLE` last, since it names roles the user must hold. */
474
+ const USER_CLAUSES = ["host", "validUntil", "defaultDatabase", "grantees", "settings", "defaultRole"];
475
+ function deferredDefaultRoles(declared, byObject, keyOf, environmentCreatesUsers) {
476
+ const out = new Map();
477
+ const grantees = new Set(declared.filter((o) => o.canonical.kind === "grants").map((o) => o.canonical.name));
478
+ for (const o of declared) {
479
+ if (o.canonical.kind !== "user" || !grantees.has(o.canonical.name))
480
+ continue;
481
+ const mine = byObject.get(keyOf(o)) ?? [];
482
+ // A user the environment creates (#3717) gets its declared DEFAULT ROLE after its grants too.
483
+ const created = environmentCreatesUsers && o.canonical.access?.defaultRole !== undefined ? mine.find((c) => c.rule === "SQLCH200") : undefined;
484
+ const change = created ?? mine.find((c) => c.rule === "SQLCH271" && c.field === "defaultRole");
485
+ if (!change)
486
+ continue;
487
+ out.set(o.canonical.name, { step: stepFor(alterAccessField("user", o.canonical.name, "defaultRole", o.canonical.access ?? {}, accessClauseText(o.ddl)), "SQLCH271"), change });
488
+ }
489
+ return out;
490
+ }
491
+ function planDeclared(input) {
492
+ const keyOf = input.keyOf ?? ((o) => o.key);
493
+ const byObject = new Map();
494
+ for (const c of input.changes)
495
+ byObject.set(c.object, [...(byObject.get(c.object) ?? []), c]);
496
+ const deferred = deferredDefaultRoles(input.declared, byObject, keyOf, input.environmentCreatesUsers === true);
497
+ const objects = input.declared.map((obj) => {
498
+ const key = keyOf(obj);
499
+ const mine = byObject.get(key) ?? [];
500
+ if (ACCESS_TYPES.has(obj.type))
501
+ return accessStatements(obj, mine, input.current.get(key), deferred, input.environmentCreatesUsers === true);
502
+ const refused = mine.filter(isRebuild);
503
+ if (refused.length > 0)
504
+ return { verdict: "rebuild", obj, changes: mine, refused, detail: refusalDetail(refused, obj.key, obj.type) };
505
+ const destructive = mine.filter(isDestructiveAlter);
506
+ if (destructive.length > 0 && !input.allowDestructive) {
507
+ return {
508
+ verdict: "withheld",
509
+ obj,
510
+ changes: mine,
511
+ withheld: destructive,
512
+ detail: `drops ${destructive.map((c) => c.field).join(", ")}, which destroys the data in it; an apply that may delete (prune, ApplyOp delete "owned-only" or "gated") makes it`,
513
+ };
514
+ }
515
+ if (mine.some((c) => c.rule === "SQLCH200"))
516
+ return { verdict: "create", obj, changes: mine, steps: [stepFor(createStatement(obj, input.marker), "SQLCH200")] };
517
+ const renamedFrom = mine.find((c) => c.rule === "SQLCH230" || c.rule === "SQLCH231")?.before;
518
+ const currentKey = renamedFrom !== undefined && input.current.has(renamedFrom) ? renamedFrom : key;
519
+ const live = input.current.get(currentKey);
520
+ // A function has no comment, so nothing to restamp: it never carries the marker.
521
+ const restamp = obj.type !== CLICKHOUSE_ENTITY_TYPES.function && !stepsSetComment(obj, mine) && (mine.some((c) => c.field === "comment") || !(input.carriesMarker?.(currentKey) ?? true));
522
+ const steps = [
523
+ ...alterSteps(obj, mine, { ...(live ? { live } : {}), ...(input.marker ? { marker: input.marker } : {}) }),
524
+ ...(restamp ? [stepFor(commentStatement(obj, input.marker), "SQLCH203")] : []),
525
+ ];
526
+ return { verdict: "alter", obj, changes: mine, steps };
527
+ });
528
+ const drops = [];
529
+ for (const c of input.changes) {
530
+ if (c.rule !== "SQLCH250")
531
+ continue;
532
+ const o = input.current.get(c.object);
533
+ if (!o)
534
+ continue;
535
+ // A function and access control carry no marker, so chant never drops one.
536
+ if (NEVER_DROPPED.has(o.kind))
537
+ continue;
538
+ const type = CLICKHOUSE_ENTITY_TYPES[o.kind];
539
+ const database = o.kind === "database" || o.kind === "function" ? undefined : o.database;
540
+ drops.push({ key: c.object, type, ...(database !== undefined ? { database } : {}), name: o.name, step: stepFor(dropStatement(type, database, o.name), "SQLCH250") });
541
+ }
542
+ drops.sort((a, b) => (DROP_ORDER[kindOf(a.type)] ?? 9) - (DROP_ORDER[kindOf(b.type)] ?? 9));
543
+ return { objects, drops };
544
+ }
545
+ const kindOf = (type) => Object.entries(CLICKHOUSE_ENTITY_TYPES).find(([, t]) => t === type)?.[0] ?? "table";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The shapes of the generated ClickHouse catalog tables
3
+ * (`src/generated/clickhouse.ts`). The data is generated; these types are
4
+ * not, so the generated module stays data and a reader of it has one place to
5
+ * look for what a field means.
6
+ */
7
+ export {};