@intentius/chant-lexicon-sql 0.124.18 → 0.124.21

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 (198) hide show
  1. package/dist/clickhouse/access.d.ts +36 -7
  2. package/dist/clickhouse/access.d.ts.map +1 -1
  3. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  4. package/dist/clickhouse/entities.d.ts +60 -0
  5. package/dist/clickhouse/entities.d.ts.map +1 -1
  6. package/dist/clickhouse/import/generator.d.ts +1 -7
  7. package/dist/clickhouse/import/generator.d.ts.map +1 -1
  8. package/dist/clickhouse/import/live-export.d.ts +8 -0
  9. package/dist/clickhouse/import/live-export.d.ts.map +1 -1
  10. package/dist/clickhouse/live/catalog.d.ts +6 -5
  11. package/dist/clickhouse/live/catalog.d.ts.map +1 -1
  12. package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
  13. package/dist/clickhouse/live/undeclarable.d.ts +32 -0
  14. package/dist/clickhouse/live/undeclarable.d.ts.map +1 -0
  15. package/dist/clickhouse/parser.d.ts +16 -0
  16. package/dist/clickhouse/parser.d.ts.map +1 -1
  17. package/dist/clickhouse/plan/deep.d.ts.map +1 -1
  18. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  19. package/dist/clickhouse/plan/normalize.d.ts +6 -3
  20. package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
  21. package/dist/clickhouse/plan/rules.d.ts +3 -0
  22. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  23. package/dist/clickhouse/testing/fake-server.d.ts +6 -0
  24. package/dist/clickhouse/testing/fake-server.d.ts.map +1 -1
  25. package/dist/clickhouse/topology.d.ts.map +1 -1
  26. package/dist/clickhouse.d.ts +1 -1
  27. package/dist/clickhouse.d.ts.map +1 -1
  28. package/dist/codegen/coverage-page.d.ts +7 -0
  29. package/dist/codegen/coverage-page.d.ts.map +1 -0
  30. package/dist/codegen/docs.d.ts.map +1 -1
  31. package/dist/codegen/registry.d.ts.map +1 -1
  32. package/dist/core/undeclarable.d.ts +38 -0
  33. package/dist/core/undeclarable.d.ts.map +1 -0
  34. package/dist/coverage/clickhouse-commands.json +24 -0
  35. package/dist/coverage/clickhouse.json +25 -0
  36. package/dist/coverage/postgres-commands.json +245 -0
  37. package/dist/coverage/postgres.json +51 -0
  38. package/dist/coverage.d.ts +68 -0
  39. package/dist/coverage.d.ts.map +1 -1
  40. package/dist/files/clickhouse.d.ts +2 -1
  41. package/dist/files/clickhouse.d.ts.map +1 -1
  42. package/dist/files/common.d.ts +12 -0
  43. package/dist/files/common.d.ts.map +1 -1
  44. package/dist/files/postgres.d.ts +1 -1
  45. package/dist/files/postgres.d.ts.map +1 -1
  46. package/dist/import-parser.d.ts +7 -2
  47. package/dist/import-parser.d.ts.map +1 -1
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/integrity.json +10 -10
  51. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  52. package/dist/lint/post-synth/sql101.d.ts.map +1 -1
  53. package/dist/lint/rules/sqlch001.d.ts +1 -1
  54. package/dist/lint/rules/sqlch001.d.ts.map +1 -1
  55. package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
  56. package/dist/lint/rules/templates.d.ts +1 -1
  57. package/dist/lint/rules/templates.d.ts.map +1 -1
  58. package/dist/lsp/postgres-template.d.ts.map +1 -1
  59. package/dist/manifest.json +1 -1
  60. package/dist/meta.json +40 -0
  61. package/dist/okf/index.md +9 -1
  62. package/dist/okf/rules/SQL101.md +1 -0
  63. package/dist/okf/rules/SQLCH001.md +2 -2
  64. package/dist/okf/rules/SQLPG113.md +2 -0
  65. package/dist/okf/types/PostgresAggregate.md +13 -0
  66. package/dist/okf/types/PostgresCast.md +9 -0
  67. package/dist/okf/types/PostgresCollation.md +9 -0
  68. package/dist/okf/types/PostgresCompositeType.md +13 -0
  69. package/dist/okf/types/PostgresEventTrigger.md +9 -0
  70. package/dist/okf/types/PostgresRangeType.md +13 -0
  71. package/dist/okf/types/Quota.md +9 -0
  72. package/dist/okf/types/SettingsProfile.md +9 -0
  73. package/dist/plugin.d.ts.map +1 -1
  74. package/dist/postgres/apply/apply.d.ts.map +1 -1
  75. package/dist/postgres/apply/statements.d.ts +4 -3
  76. package/dist/postgres/apply/statements.d.ts.map +1 -1
  77. package/dist/postgres/entities.d.ts +129 -6
  78. package/dist/postgres/entities.d.ts.map +1 -1
  79. package/dist/postgres/entity-types.d.ts +6 -0
  80. package/dist/postgres/entity-types.d.ts.map +1 -1
  81. package/dist/postgres/import/generator.d.ts +3 -2
  82. package/dist/postgres/import/generator.d.ts.map +1 -1
  83. package/dist/postgres/import/live-export.d.ts +8 -0
  84. package/dist/postgres/import/live-export.d.ts.map +1 -1
  85. package/dist/postgres/live/catalog-kinds.d.ts +28 -0
  86. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -0
  87. package/dist/postgres/live/catalog.d.ts +23 -8
  88. package/dist/postgres/live/catalog.d.ts.map +1 -1
  89. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  90. package/dist/postgres/live/undeclarable.d.ts +42 -0
  91. package/dist/postgres/live/undeclarable.d.ts.map +1 -0
  92. package/dist/postgres/parser.d.ts +88 -5
  93. package/dist/postgres/parser.d.ts.map +1 -1
  94. package/dist/postgres/plan/deep.d.ts.map +1 -1
  95. package/dist/postgres/plan/diff.d.ts.map +1 -1
  96. package/dist/postgres/plan/normalize.d.ts +17 -2
  97. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  98. package/dist/postgres/plan/rules.d.ts +3 -0
  99. package/dist/postgres/plan/rules.d.ts.map +1 -1
  100. package/dist/postgres/plan/schema.d.ts +3 -2
  101. package/dist/postgres/plan/schema.d.ts.map +1 -1
  102. package/dist/postgres/testing/fake-client.d.ts +6 -0
  103. package/dist/postgres/testing/fake-client.d.ts.map +1 -1
  104. package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
  105. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  106. package/dist/postgres.d.ts +1 -1
  107. package/dist/postgres.d.ts.map +1 -1
  108. package/dist/rules/postgres-names.ts +15 -2
  109. package/dist/rules/postgres-templates.ts +2 -2
  110. package/dist/rules/sql101.ts +1 -0
  111. package/dist/rules/sqlch001.ts +10 -3
  112. package/dist/rules/sqlpg001.ts +6 -21
  113. package/dist/rules/sqlpg113.ts +1 -1
  114. package/dist/rules/templates.ts +2 -2
  115. package/dist/validate.d.ts.map +1 -1
  116. package/package.json +2 -2
  117. package/src/clickhouse/access.ts +197 -22
  118. package/src/clickhouse/apply/statements.ts +17 -5
  119. package/src/clickhouse/entities.ts +78 -2
  120. package/src/clickhouse/import/generator.ts +16 -3
  121. package/src/clickhouse/import/live-export.ts +17 -2
  122. package/src/clickhouse/live/catalog.ts +57 -10
  123. package/src/clickhouse/live/describe-resources.ts +3 -1
  124. package/src/clickhouse/live/kinds.e2e.test.ts +179 -0
  125. package/src/clickhouse/live/undeclarable.e2e.test.ts +65 -0
  126. package/src/clickhouse/live/undeclarable.ts +66 -0
  127. package/src/clickhouse/parser.test.ts +45 -2
  128. package/src/clickhouse/parser.ts +111 -3
  129. package/src/clickhouse/plan/deep.ts +25 -6
  130. package/src/clickhouse/plan/diff.ts +41 -1
  131. package/src/clickhouse/plan/normalize.ts +12 -4
  132. package/src/clickhouse/plan/rules.ts +22 -1
  133. package/src/clickhouse/quota-profile.test.ts +186 -0
  134. package/src/clickhouse/table-function.test.ts +54 -0
  135. package/src/clickhouse/testdata/server-ddl-baseline.json +17 -14
  136. package/src/clickhouse/testing/fake-server.ts +5 -0
  137. package/src/clickhouse/topology.ts +4 -2
  138. package/src/clickhouse.ts +6 -0
  139. package/src/codegen/classifier-page.test.ts +6 -0
  140. package/src/codegen/coverage-page.ts +77 -0
  141. package/src/codegen/docs.ts +5 -0
  142. package/src/codegen/postgres-classifier-page.ts +1 -1
  143. package/src/codegen/registry.ts +8 -0
  144. package/src/core/undeclarable.test.ts +141 -0
  145. package/src/core/undeclarable.ts +66 -0
  146. package/src/coverage/clickhouse-commands.json +24 -0
  147. package/src/coverage/clickhouse.json +25 -0
  148. package/src/coverage/postgres-commands.json +245 -0
  149. package/src/coverage/postgres.json +51 -0
  150. package/src/coverage.test.ts +78 -0
  151. package/src/coverage.ts +142 -0
  152. package/src/files/clickhouse.ts +20 -7
  153. package/src/files/common.ts +18 -0
  154. package/src/files/postgres.ts +25 -6
  155. package/src/generated/lexicon-sql.json +40 -0
  156. package/src/import-parser.ts +12 -4
  157. package/src/index.ts +2 -2
  158. package/src/lint/post-synth/postgres-names.ts +15 -2
  159. package/src/lint/post-synth/sql101.ts +1 -0
  160. package/src/lint/post-synth/sqlpg113.ts +1 -1
  161. package/src/lint/rules/postgres-templates.ts +2 -2
  162. package/src/lint/rules/sqlch001.ts +10 -3
  163. package/src/lint/rules/sqlpg001.ts +6 -21
  164. package/src/lint/rules/templates.ts +2 -2
  165. package/src/lsp/postgres-template.ts +14 -0
  166. package/src/mcp/index.ts +5 -5
  167. package/src/mcp/postgres.ts +1 -1
  168. package/src/plugin.ts +13 -2
  169. package/src/postgres/apply/apply.ts +8 -2
  170. package/src/postgres/apply/statements.ts +29 -6
  171. package/src/postgres/entities.test.ts +2 -2
  172. package/src/postgres/entities.ts +316 -28
  173. package/src/postgres/entity-types.ts +6 -0
  174. package/src/postgres/import/generator.ts +31 -4
  175. package/src/postgres/import/live-export.ts +25 -2
  176. package/src/postgres/kinds.test.ts +140 -0
  177. package/src/postgres/live/catalog-kinds.ts +312 -0
  178. package/src/postgres/live/catalog.ts +33 -11
  179. package/src/postgres/live/describe-resources.ts +14 -5
  180. package/src/postgres/live/kinds.e2e.test.ts +182 -0
  181. package/src/postgres/live/roundtrip.e2e.test.ts +9 -3
  182. package/src/postgres/live/undeclarable.e2e.test.ts +83 -0
  183. package/src/postgres/live/undeclarable.ts +141 -0
  184. package/src/postgres/parser.test.ts +2 -1
  185. package/src/postgres/parser.ts +307 -17
  186. package/src/postgres/plan/deep.ts +33 -0
  187. package/src/postgres/plan/diff.ts +45 -0
  188. package/src/postgres/plan/normalize.ts +153 -2
  189. package/src/postgres/plan/rules.ts +21 -0
  190. package/src/postgres/plan/schema.ts +6 -4
  191. package/src/postgres/testing/fake-client.ts +5 -0
  192. package/src/postgres/testing/roundtrip-schema.ts +19 -1
  193. package/src/postgres.ts +19 -0
  194. package/src/skill-defs.ts +1 -1
  195. package/src/skills/chant-sql-postgres.md +7 -3
  196. package/src/skills/chant-sql.md +3 -1
  197. package/src/unknown-clauses.test.ts +2 -1
  198. package/src/validate.ts +8 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A table over a table function (#3911): `CREATE TABLE t AS numbers(10)`,
3
+ * with or without its columns, as the tag reads it, as the server prints it
4
+ * back, and the one statement a change takes. `live/kinds.e2e.test.ts` runs
5
+ * it against a server.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { table, CLICKHOUSE_ENTITY_TYPES } from "./entities";
10
+ import { canonicalObject, objectKey } from "./plan/normalize";
11
+ import { diffSchemas } from "./plan/diff";
12
+ import { planStatements, type DeclaredObject } from "./apply/statements";
13
+ import { renderStatement } from "./topology";
14
+
15
+ const shown = "CREATE TABLE default.nums\n(\n `number` UInt64\n) AS numbers(10)\nCOMMENT 'counting'";
16
+
17
+ describe("a table over a table function", () => {
18
+ test("the tag reads the call, and the columns when written", () => {
19
+ const bare = table`CREATE TABLE default.nums AS numbers(10) COMMENT 'counting'`;
20
+ expect(bare.props).toMatchObject({ name: "nums", database: "default", asFunction: "numbers(10)", columns: [], comment: "counting" });
21
+ expect(bare.props.engine).toBeUndefined();
22
+ const typed = table`CREATE TABLE default.nums (number UInt64) AS numbers(10)`;
23
+ expect(typed.props.columns.map((c) => [c.name, c.type])).toEqual([["number", "UInt64"]]);
24
+ });
25
+
26
+ test("a declaration with no columns agrees with the columns the server inferred; one that lists them is compared on them", () => {
27
+ const same = (declared: string) => diffSchemas([{ key: "k", canonical: canonicalObject(shown) }], [{ key: "k", canonical: canonicalObject(declared) }]).changes;
28
+ expect(same("CREATE TABLE default.nums AS numbers(10) COMMENT 'counting'")).toEqual([]);
29
+ expect(same("CREATE TABLE default.nums (number UInt64) AS numbers(10) COMMENT 'counting'")).toEqual([]);
30
+ expect(same("CREATE TABLE default.nums (number UInt32) AS numbers(20) COMMENT 'counting'").map((c) => [c.field, c.rule, c.class])).toEqual([
31
+ ["asFunction", "SQLCH219", "metadata"],
32
+ ["columns.number", "SQLCH219", "metadata"],
33
+ ]);
34
+ });
35
+
36
+ test("a change replaces it whole, its comment with it; a table with data becoming one is a rebuild", () => {
37
+ const ddl = "CREATE TABLE default.nums AS numbers(20)";
38
+ const canonical = canonicalObject(ddl);
39
+ const obj: DeclaredObject = { exportName: "nums", type: CLICKHOUSE_ENTITY_TYPES.table, key: objectKey(canonical), ddl, canonical, dependsOn: [] };
40
+ const live = canonicalObject(shown);
41
+ const changes = diffSchemas([{ key: obj.key, canonical: live }], [{ key: obj.key, canonical }]).changes;
42
+ expect(changes.map((c) => c.field)).toEqual(["comment", "asFunction"]);
43
+ const entry = planStatements({ declared: [obj], changes, current: new Map([[obj.key, live]]) }).objects[0]!;
44
+ expect(entry.verdict === "alter" ? entry.steps.map((s) => s.sql) : []).toEqual(["CREATE OR REPLACE TABLE default.nums AS numbers(20) COMMENT '[chant managed-by=chant]'"]);
45
+
46
+ const stored = canonicalObject("CREATE TABLE default.nums (number UInt64) ENGINE = MergeTree ORDER BY number");
47
+ const rebuild = diffSchemas([{ key: obj.key, canonical: stored }], [{ key: obj.key, canonical }]).changes;
48
+ expect(rebuild.map((c) => [c.field, c.rule, c.class])).toEqual([["asFunction", "SQLCH223", "rebuild"]]);
49
+ });
50
+
51
+ test("ON CLUSTER after the name", () => {
52
+ expect(renderStatement("CREATE TABLE default.nums AS numbers(10)", { kind: "cluster", cluster: "main" })).toBe("CREATE TABLE default.nums ON CLUSTER `main` AS numbers(10)");
53
+ });
54
+ });
@@ -22,6 +22,14 @@
22
22
  "YEARS"
23
23
  ],
24
24
  "failures": [
25
+ "CREATE TABLE ... AS <table> is not declared: the server copies the other table's columns and engine and keeps no link to it. Declare them (a composite can share them), or write AS <table function>(...) for a table over a table function at 'AS'",
26
+ "CREATE TABLE ... AS <table> is not declared: the server copies the other table's columns and engine and keeps no link to it. Declare them (a composite can share them), or write AS <table function>(...) for a table over a table function at 'as'",
27
+ "CREATE TABLE ... AS SELECT is not declared: it creates the table and fills it once, and the server keeps the columns, not the query. Declare the table's columns and fill it with INSERT ... SELECT, or declare a materialized view at 'AS'",
28
+ "CREATE TABLE ... AS SELECT is not declared: it creates the table and fills it once, and the server keeps the columns, not the query. Declare the table's columns and fill it with INSERT ... SELECT, or declare a materialized view at 'as'",
29
+ "CREATE TABLE ... CLONE AS is not declared: it copies another table's columns, engine and data once. Declare the columns and engine at 'CLONE'",
30
+ "CREATE TABLE ... EMPTY AS SELECT is not declared: the server keeps the query's columns, not the query. Declare the columns at 'empty'",
31
+ "declare one quota per template at ','",
32
+ "declare one settings profile per template at ','",
25
33
  "expected ')' at 'WITH'",
26
34
  "expected '='",
27
35
  "expected '=' at '('",
@@ -48,20 +56,15 @@
48
56
  "expected an expression",
49
57
  "expected an expression at ')'",
50
58
  "expected an expression at 'TYPE'",
51
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'AS'",
52
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'CLONE'",
53
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'COMMENT'",
54
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'ENGINE'",
55
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'Engine'",
56
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'ORDER'",
57
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'SQL'",
58
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'TO'",
59
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'as'",
60
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'empty'",
61
- "expected the column list (CREATE TABLE ... AS is not declared here) at 'engine'",
59
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'COMMENT'",
60
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'ENGINE'",
61
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'Engine'",
62
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'ORDER'",
63
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'SQL'",
64
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'TO'",
65
+ "expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns) at 'engine'",
62
66
  "unexpected end of statement",
63
- "unexpected token after the table definition at 'AS'",
64
- "unexpected token after the table definition at 'SETTINGS'",
65
- "unexpected token after the table definition at 'as'"
67
+ "unexpected ipv6_prefix_bits: expected IN, KEYED BY, KEY BY, NOT KEYED, FOR, TO at 'ipv6_prefix_bits'",
68
+ "unexpected token after the table definition at 'SETTINGS'"
66
69
  ]
67
70
  }
@@ -26,6 +26,8 @@ export interface FakeOptions {
26
26
  version?: string;
27
27
  /** The SQL user-defined functions `system.functions` lists. Default: none. */
28
28
  functions?: Array<{ name: string; statement: string }>;
29
+ /** What the undeclarable-kind probes (`../live/undeclarable.ts`) find, by the coverage table's kind. Default: nothing. */
30
+ undeclarable?: Array<{ kind: string; database?: string; name: string }>;
29
31
  }
30
32
 
31
33
  const row = (o: Record<string, unknown>) => `${JSON.stringify(o)}\n`;
@@ -38,6 +40,9 @@ export async function fakeClickHouse(objects: FakeObject[], options: FakeOptions
38
40
  queries.push(body);
39
41
  if (options.fail) return { status: options.fail.status, text: options.fail.body };
40
42
  if (/version\(\)/.test(body)) return ok(row({ v: options.version ?? "26.8.15.10" }));
43
+ // A probe for one undeclarable kind (`../live/undeclarable.ts`), which its comment names.
44
+ const probe = /^\/\* chant undeclarable: (.+?) \*\//.exec(body);
45
+ if (probe) return ok((options.undeclarable ?? []).filter((u) => u.kind === probe[1]).map((u) => row({ database: u.database ?? "", name: u.name })).join(""));
41
46
  if (/FROM system\.databases/.test(body)) {
42
47
  return ok(
43
48
  objects
@@ -352,7 +352,7 @@ function renderOtherCreate(tokens: Token[], topology: Topology): string {
352
352
  if (w.accept("OR")) w.accept("REPLACE");
353
353
  w.accept("TEMPORARY");
354
354
  // Access control (`./access.ts`) belongs to no database: like a database's own DDL, it reaches every replica through the cluster.
355
- if (w.accept("USER", "ROLE", "POLICY", "QUOTA") || w.acceptSeq("ROW", "POLICY") || w.acceptSeq("SETTINGS", "PROFILE")) {
355
+ if (w.accept("USER", "ROLE", "POLICY", "QUOTA", "PROFILE") || w.acceptSeq("ROW", "POLICY") || w.acceptSeq("SETTINGS", "PROFILE")) {
356
356
  w.acceptSeq("IF", "NOT", "EXISTS") || w.acceptSeq("OR", "REPLACE");
357
357
  if (!w.name()) return applyEdits(tokens, []);
358
358
  const edit = onClusterEdit(w, databaseCluster(topology, true), w.end());
@@ -430,7 +430,9 @@ export function renderStatement(sql: string, topology: Topology): string {
430
430
  if (word(head, "DROP", "DETACH", "ATTACH")) w.accept("TEMPORARY");
431
431
  if (w.accept("DATABASE")) return renderAfterName(tokens, w, databaseCluster(topology, word(head, "DROP")));
432
432
  // A function and access control belong to no database: like the database's own DDL, they reach every replica through the cluster.
433
- if (w.accept("FUNCTION", "USER", "ROLE", "POLICY") || w.acceptSeq("ROW", "POLICY")) return renderAfterName(tokens, w, databaseCluster(topology, true));
433
+ if (w.accept("FUNCTION", "USER", "ROLE", "POLICY", "QUOTA", "PROFILE") || w.acceptSeq("ROW", "POLICY") || w.acceptSeq("SETTINGS", "PROFILE")) {
434
+ return renderAfterName(tokens, w, databaseCluster(topology, true));
435
+ }
434
436
  // `TRUNCATE t` may leave out TABLE.
435
437
  if (w.accept("TABLE", "VIEW", "DICTIONARY") || word(head, "TRUNCATE")) return renderAfterName(tokens, w, objectCluster(topology));
436
438
  return sql;
package/src/clickhouse.ts CHANGED
@@ -18,6 +18,8 @@ export {
18
18
  role,
19
19
  policy,
20
20
  grant,
21
+ quota,
22
+ settingsProfile,
21
23
  literal,
22
24
  SqlLiteral,
23
25
  SqlTemplateError,
@@ -35,6 +37,10 @@ export {
35
37
  type ClickHouseRole,
36
38
  type ClickHouseRowPolicy,
37
39
  type ClickHouseGrant,
40
+ type ClickHouseQuota,
41
+ type ClickHouseSettingsProfile,
42
+ type QuotaProps,
43
+ type SettingsProfileProps,
38
44
  type UserProps,
39
45
  type RoleProps,
40
46
  type RowPolicyProps,
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { expect, test } from "vitest";
4
4
  import { renderClassifierPage } from "./classifier-page";
5
5
  import { renderPostgresClassifierPage } from "./postgres-classifier-page";
6
+ import { renderCoveragePage } from "./coverage-page";
6
7
 
7
8
  test("the committed classifier page is what the rules render (run npm run docs)", () => {
8
9
  const committed = readFileSync(join(import.meta.dirname, "..", "..", "docs", "pages", "change-classifier.mdx"), "utf-8");
@@ -14,3 +15,8 @@ test("the committed Postgres classifier page is what the rules render (run npm r
14
15
  const committed = readFileSync(join(import.meta.dirname, "..", "..", "docs", "pages", "postgres-change-classifier.mdx"), "utf-8");
15
16
  expect(committed).toBe(renderPostgresClassifierPage());
16
17
  });
18
+
19
+ test("the committed object coverage page is what the coverage tables render (run npm run docs)", () => {
20
+ const committed = readFileSync(join(import.meta.dirname, "..", "..", "docs", "pages", "object-coverage.mdx"), "utf-8");
21
+ expect(committed).toBe(renderCoveragePage());
22
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `docs/pages/object-coverage.mdx`, written from the coverage tables
3
+ * (`../coverage/<dialect>.json`) by `npm run docs`, so the page cannot
4
+ * disagree with what import refuses.
5
+ */
6
+
7
+ import { KIND_COVERAGE, PINNED_COMMANDS, type KindRow } from "../coverage";
8
+ import type { SqlDialect } from "../dialects";
9
+
10
+ const cell = (s: string) => s.replace(/\|/g, "\\|").replace(/[{}<>]/g, (c) => `\\${c}`);
11
+
12
+ const STATUS: Record<KindRow["status"], string> = { declared: "declared", refused: "refused", "out-of-scope": "out of scope" };
13
+
14
+ function table(dialect: SqlDialect): string {
15
+ const rows = [...KIND_COVERAGE[dialect]].sort((a, b) => (a.command < b.command ? -1 : a.command > b.command ? 1 : 0));
16
+ return [
17
+ "| Command | Kind | Status | Declared by, or why not |",
18
+ "|---|---|---|---|",
19
+ ...rows.map((r) => {
20
+ const command = `\`${r.command}\`${r.form ? ` (${cell(r.form)})` : ""}`;
21
+ const why = r.status === "declared" ? `\`${r.entity}\`${r.note ? `: ${cell(r.note)}` : ""}` : cell(r.reason ?? "");
22
+ return `| ${command} | ${cell(r.kind)} | ${STATUS[r.status]} | ${why} |`;
23
+ }),
24
+ ].join("\n");
25
+ }
26
+
27
+ function counts(dialect: SqlDialect): string {
28
+ const rows = KIND_COVERAGE[dialect];
29
+ const n = (s: KindRow["status"]) => rows.filter((r) => r.status === s).length;
30
+ return `${n("declared")} declared, ${n("refused")} refused, ${n("out-of-scope")} out of scope`;
31
+ }
32
+
33
+ export function renderCoveragePage(): string {
34
+ const pg = PINNED_COMMANDS.postgres.map((p) => p.version).join(", ");
35
+ const ch = PINNED_COMMANDS.clickhouse.map((p) => p.version).join(", ");
36
+ return `---
37
+ title: "Object Coverage"
38
+ label: "Object Coverage"
39
+ description: "Every kind of object each pinned Postgres and ClickHouse server can create, marked declared, refused with a reason, or out of scope, and what import does with each"
40
+ diataxis: reference
41
+ order: 3
42
+ ---
43
+
44
+ {/* Written by src/codegen/coverage-page.ts from src/coverage/postgres.json and src/coverage/clickhouse.json. Edit the tables, then run npm run docs. */}
45
+
46
+ Each row is a \`CREATE\` command a pinned server has, or one form of a command where chant treats the forms apart (\`CREATE TYPE ... AS ENUM\` is declared, \`CREATE TYPE ... AS RANGE\` is not). The commands come from the servers' own sources at each pinned version (\`scripts/sql-create-commands.ts\`), and a test fails when a pinned server has a command with no row here, so a new server release cannot add a kind nobody looked at.
47
+
48
+ - **declared**: a tag declares it, and import writes it as that declaration.
49
+ - **refused**: no declaration holds it yet. \`chant import --from <env>\` fails when the server has one, naming every such object (kind, schema, name) at once, and \`chant import schema.sql\` fails on the statement. Nothing is left out without a word.
50
+ - **out of scope**: the environment's, not a schema's: import never reads it.
51
+
52
+ \`--skip-undeclarable\` (\`--parser-option skipUndeclarable\` for a file) imports everything else and lists the refused objects as warnings, for an adopter who keeps those objects by hand for now:
53
+
54
+ \`\`\`bash
55
+ chant import --from prod --skip-undeclarable
56
+ chant import schema.sql --parser-option skipUndeclarable
57
+ \`\`\`
58
+
59
+ What a managed provider, an extension or the server's configuration files define is never refused: an extension's aggregates and casts are the extension's, declared with it, and a quota from \`users.xml\` is the server's. An import with \`--type\`, \`--name\` or \`--owned\` asks only for objects chant can hold, and does not look for the rest.
60
+
61
+ ## Postgres
62
+
63
+ Pinned majors: ${pg}. ${counts("postgres")}.
64
+
65
+ ${table("postgres")}
66
+
67
+ ## ClickHouse
68
+
69
+ Pinned release: ${ch}. ${counts("clickhouse")}.
70
+
71
+ ${table("clickhouse")}
72
+
73
+ ## Declaring a refused kind
74
+
75
+ A kind becomes declarable in three steps: a tag that declares it, the catalog reader reading it (\`src/postgres/live/catalog.ts\`, \`src/clickhouse/live/catalog.ts\`), and its row flipped to \`declared\` with the entity type, its reason taken out. The row's flip is what stops import refusing the kind. After moving a pin, run \`npx tsx scripts/sql-create-commands.ts\` and give each new command a row.
76
+ `;
77
+ }
@@ -9,6 +9,7 @@ import { writeFileSync } from "fs";
9
9
  import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
10
10
  import { renderClassifierPage } from "./classifier-page";
11
11
  import { renderPostgresClassifierPage } from "./postgres-classifier-page";
12
+ import { renderCoveragePage } from "./coverage-page";
12
13
 
13
14
  const overview = `The sql lexicon declares database schema in TypeScript. It has two dialects,
14
15
  each a subpath of the one package: ClickHouse at
@@ -143,6 +144,9 @@ Both dialects:
143
144
  - [Composites](./composites/): five ClickHouse and five Postgres composites.
144
145
  - [Lint Rules and Checks](./lint-rules/): every lint rule and post-synth
145
146
  check.
147
+ - [Object Coverage](./object-coverage/): every kind of object the pinned
148
+ servers can create, declared, refused or out of scope, and what import
149
+ does with each.
146
150
 
147
151
  ## Schema changes and \`chant migrate\`
148
152
 
@@ -201,6 +205,7 @@ export async function generateDocs(opts?: { verbose?: boolean }): Promise<void>
201
205
 
202
206
  writeFileSync(join(pkgDir, "docs", "pages", "change-classifier.mdx"), renderClassifierPage());
203
207
  writeFileSync(join(pkgDir, "docs", "pages", "postgres-change-classifier.mdx"), renderPostgresClassifierPage());
208
+ writeFileSync(join(pkgDir, "docs", "pages", "object-coverage.mdx"), renderCoveragePage());
204
209
 
205
210
  const config: DocsConfig = {
206
211
  name: "sql",
@@ -72,7 +72,7 @@ Two rules change class across the supported majors: a STORED generated column's
72
72
 
73
73
  ## Identity and renames
74
74
 
75
- An object is identified by its export name between two builds, and by its schema-qualified name against a server, which has no export names. Tables, views, materialized views, sequences and indexes share one namespace per schema, types and domains another, and schemas and extensions are matched by name. Functions and procedures share a namespace of their own and are told apart by their input parameter types, so an overload is another object; a trigger is matched by its name on its table.
75
+ An object is identified by its export name between two builds, and by its schema-qualified name against a server, which has no export names. Tables, views, materialized views, sequences and indexes share one namespace per schema, types and domains another, and schemas and extensions are matched by name. Functions, procedures and aggregates share a namespace of their own and are told apart by their input parameter types, so an overload is another object; composite and range types share the types' namespace, collations have one of their own, a cast is named by its two types and an event trigger by its name in the database; a trigger is matched by its name on its table.
76
76
 
77
77
  A rename is declared where it happens. Before a CREATE, \`-- previously: <old name>\` says the object had another name; on a column's line, it says the column did:
78
78
 
@@ -23,6 +23,8 @@ const KINDS: Record<string, string> = {
23
23
  Role: CLICKHOUSE_ENTITY_TYPES.role,
24
24
  RowPolicy: CLICKHOUSE_ENTITY_TYPES.rowPolicy,
25
25
  Grant: CLICKHOUSE_ENTITY_TYPES.grant,
26
+ Quota: CLICKHOUSE_ENTITY_TYPES.quota,
27
+ SettingsProfile: CLICKHOUSE_ENTITY_TYPES.settingsProfile,
26
28
  PostgresSchema: POSTGRES_ENTITY_TYPES.schema,
27
29
  PostgresTable: POSTGRES_ENTITY_TYPES.table,
28
30
  PostgresIndex: POSTGRES_ENTITY_TYPES.index,
@@ -30,7 +32,13 @@ const KINDS: Record<string, string> = {
30
32
  PostgresMaterializedView: POSTGRES_ENTITY_TYPES.materializedView,
31
33
  PostgresSequence: POSTGRES_ENTITY_TYPES.sequence,
32
34
  PostgresEnum: POSTGRES_ENTITY_TYPES.enum,
35
+ PostgresCompositeType: POSTGRES_ENTITY_TYPES.composite,
36
+ PostgresRangeType: POSTGRES_ENTITY_TYPES.range,
33
37
  PostgresDomain: POSTGRES_ENTITY_TYPES.domain,
38
+ PostgresAggregate: POSTGRES_ENTITY_TYPES.aggregate,
39
+ PostgresCollation: POSTGRES_ENTITY_TYPES.collation,
40
+ PostgresCast: POSTGRES_ENTITY_TYPES.cast,
41
+ PostgresEventTrigger: POSTGRES_ENTITY_TYPES.eventTrigger,
34
42
  PostgresExtension: POSTGRES_ENTITY_TYPES.extension,
35
43
  PostgresFunction: POSTGRES_ENTITY_TYPES.function,
36
44
  PostgresProcedure: POSTGRES_ENTITY_TYPES.procedure,
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Import never leaves an object out without a word (#3911): an object of a
3
+ * kind no declaration holds fails the import, which names every one, and
4
+ * `skipUndeclarable` turns the failure into a warning per object. Against
5
+ * fakes; the e2e tests (`../postgres/live/undeclarable.e2e.test.ts`,
6
+ * `../clickhouse/live/undeclarable.e2e.test.ts`) run the same against servers.
7
+ */
8
+
9
+ import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
10
+ import { fakeClient, type FakeCatalog } from "../postgres/testing/fake-client";
11
+ import { fakeClickHouse, type FakeServer } from "../clickhouse/testing/fake-server";
12
+ import { exportResources as exportClickHouse } from "../clickhouse/import/live-export";
13
+ import { SqlFileParser } from "../import-parser";
14
+ import { UndeclarableObjectsError } from "./undeclarable";
15
+
16
+ const pg: { catalog: FakeCatalog; provider?: string } = { catalog: {} };
17
+
18
+ vi.mock("../postgres/live/bind", async (orig) => ({
19
+ ...(await orig<typeof import("../postgres/live/bind")>()),
20
+ bindPostgres: async () => ({
21
+ target: { endpoint: { url: "postgres://x/db" }, source: "test", defaultSchema: "public", ...(pg.provider ? { provider: pg.provider } : {}) },
22
+ client: fakeClient(pg.catalog),
23
+ }),
24
+ }));
25
+
26
+ const { exportResources: exportPostgres } = await import("../postgres/import/live-export");
27
+
28
+ const CATALOG: FakeCatalog = {
29
+ schemas: [{ name: "app" }],
30
+ tables: [{ schema: "app", name: "users", columns: [{ name: "id", type: "bigint", notnull: true }] }],
31
+ undeclarable: [
32
+ { kind: "access method", name: "heap2" },
33
+ { kind: "conversion", schema: "app", name: "latin1_utf8" },
34
+ { kind: "rule", schema: "app", name: "users_notify ON users" },
35
+ { kind: "base type", schema: "app", name: "later" },
36
+ ],
37
+ };
38
+
39
+ describe("Postgres live import", () => {
40
+ test("fails naming every object of a kind chant cannot declare, and why", async () => {
41
+ pg.catalog = CATALOG;
42
+ const err = await exportPostgres({ environment: "prod" }).then(
43
+ () => undefined,
44
+ (e: unknown) => e,
45
+ );
46
+ expect(err).toBeInstanceOf(UndeclarableObjectsError);
47
+ const message = (err as Error).message;
48
+ for (const line of [" - access method heap2", " - conversion app.latin1_utf8", " - rule app.users_notify ON users", " - base type app.later"]) {
49
+ expect(message).toContain(line);
50
+ }
51
+ expect(message).toContain(" - conversion: no tag declares an encoding conversion");
52
+ expect(message).toContain("--skip-undeclarable");
53
+ });
54
+
55
+ test("with skipUndeclarable, imports the rest and warns once per object", async () => {
56
+ pg.catalog = CATALOG;
57
+ const ir = await exportPostgres({ environment: "prod", skipUndeclarable: true });
58
+ expect(ir.resources.map((r) => r.properties.name)).toContain("users");
59
+ const left = (ir.warnings ?? []).filter((w) => w.includes("is left out (--skip-undeclarable)"));
60
+ expect(left).toHaveLength(4);
61
+ expect(left[0]).toBe("access method heap2 is left out (--skip-undeclarable): no tag declares an access method; an extension usually installs one, and the extension is declared instead");
62
+ });
63
+
64
+ test("a selector or owned import does not look", async () => {
65
+ pg.catalog = CATALOG;
66
+ await expect(exportPostgres({ environment: "prod", selector: { type: "Postgres::Table" } })).resolves.toBeDefined();
67
+ await expect(exportPostgres({ environment: "prod", owned: true })).resolves.toBeDefined();
68
+ });
69
+
70
+ test("a managed provider's own objects are its, with a warning, not a refusal", async () => {
71
+ pg.catalog = { ...CATALOG, undeclarable: [{ kind: "conversion", schema: "auth", name: "x" }] };
72
+ pg.provider = "supabase";
73
+ try {
74
+ const ir = await exportPostgres({ environment: "prod" });
75
+ expect(ir.warnings).toContain("conversion auth.x is kept by Supabase; left out");
76
+ } finally {
77
+ pg.provider = undefined;
78
+ }
79
+ });
80
+ });
81
+
82
+ describe("ClickHouse live import", () => {
83
+ let server: FakeServer;
84
+ beforeAll(async () => {
85
+ server = await fakeClickHouse(
86
+ [
87
+ { name: "analytics", engine: "Atomic", statement: "CREATE DATABASE analytics\nENGINE = Atomic" },
88
+ { database: "analytics", name: "events", engine: "MergeTree", statement: "CREATE TABLE analytics.events\n(\n `id` UInt64\n)\nENGINE = MergeTree\nORDER BY id" },
89
+ { database: "analytics", name: "wv", engine: "WindowView", statement: "CREATE WINDOW VIEW analytics.wv AS SELECT 1" },
90
+ ],
91
+ {
92
+ undeclarable: [
93
+ { kind: "named collection", name: "nc1" },
94
+ { kind: "workload", name: "all" },
95
+ { kind: "window view", database: "analytics", name: "wv" },
96
+ ],
97
+ },
98
+ );
99
+ });
100
+ afterAll(() => server.close());
101
+ const run = (extra: Record<string, unknown> = {}) => exportClickHouse({ environment: "x", config: {}, env: { CLICKHOUSE_URL: server.url }, ...extra });
102
+
103
+ test("fails naming them", async () => {
104
+ await expect(run()).rejects.toThrow(/ - named collection nc1\n - window view analytics\.wv\n - workload all\n/);
105
+ });
106
+
107
+ test("with skipUndeclarable, warns, and the window view is not imported as a table", async () => {
108
+ const ir = await run({ skipUndeclarable: true });
109
+ expect(ir.resources.map((r) => r.properties.name)).toEqual(["analytics", "events"]);
110
+ expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(3);
111
+ });
112
+ });
113
+
114
+ describe("file import", () => {
115
+ const DDL = `CREATE SCHEMA app;
116
+ CREATE TABLE app.t (id int PRIMARY KEY);
117
+ CREATE AGGREGATE app.total(int) (sfunc = int4pl, stype = int);
118
+ CREATE CONVERSION app.latin1_utf8 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
119
+ CREATE TYPE app.pair AS (a int, b text);
120
+ CREATE TYPE app.later;
121
+ CREATE COLLATION app.ci (provider = icu, locale = 'und');`;
122
+
123
+ test("a CREATE of a kind chant cannot declare is an error naming it", () => {
124
+ expect(() => new SqlFileParser("postgres").parse(DDL)).toThrow(/conversion: chant cannot declare it \(no tag declares an encoding conversion\): CREATE CONVERSION app\.latin1_utf8/);
125
+ });
126
+
127
+ test("skipUndeclarable imports the rest and lists them as warnings", () => {
128
+ const ir = new SqlFileParser("postgres", { skipUndeclarable: true }).parse(DDL);
129
+ // An aggregate, a composite type and a collation are declared kinds (#3911): imported, not refused.
130
+ expect(ir.resources.map((r) => r.properties.name)).toEqual(["app", "t", "total", "pair", "ci"]);
131
+ expect(ir.warnings).toEqual([
132
+ "left out (skipUndeclarable): conversion: chant cannot declare it (no tag declares an encoding conversion): CREATE CONVERSION app.latin1_utf8 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8",
133
+ "left out (skipUndeclarable): base type: chant cannot declare it (no tag declares a base type; its input and output functions are C code an extension installs): CREATE TYPE app.later",
134
+ ]);
135
+ });
136
+
137
+ test("ClickHouse: a named collection in a file is refused too; a quota is declared (#3911)", () => {
138
+ expect(() => new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE NAMED COLLECTION c AS url = 'x'")).toThrow(/named collection: chant cannot declare it/);
139
+ expect(new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE QUOTA q1").resources.map((r) => r.type)).toEqual(["ClickHouse::Database", "ClickHouse::Quota"]);
140
+ });
141
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Objects an import finds that no declaration can hold (#3911).
3
+ *
4
+ * Import reads every kind of object the server has, not only the kinds chant
5
+ * declares, so none is left out without a word. An object of a kind the
6
+ * coverage table marks refused (`../coverage.ts`) is collected here, and the
7
+ * import fails naming every one of them at once: kind, schema and name.
8
+ * `--skip-undeclarable` imports the rest and lists them as warnings instead,
9
+ * so an adopter is not stuck behind an aggregate they mean to keep by hand.
10
+ */
11
+
12
+ import { kindRow } from "../coverage";
13
+ import type { SqlDialect } from "../dialects";
14
+
15
+ /** One object of a kind chant cannot declare. */
16
+ export interface UndeclarableObject {
17
+ /** The coverage table's kind: `aggregate`, `composite type`. */
18
+ kind: string;
19
+ /** The Postgres schema or ClickHouse database it is in; absent for a database-wide or server-wide object. */
20
+ schema?: string;
21
+ /** Its name, with what identifies it besides: an aggregate's argument types, a rule's table, a cast's types. */
22
+ name: string;
23
+ /** The role or user that owns it, where that tells a managed provider's own object from the database owner's. */
24
+ owner?: string;
25
+ }
26
+
27
+ /** `aggregate app.total(integer)`. */
28
+ export const describeUndeclarable = (o: UndeclarableObject): string => `${o.kind} ${o.schema ? `${o.schema}.` : ""}${o.name}`;
29
+
30
+ const DOCS = "https://intentius.io/chant/lexicons/sql/object-coverage/";
31
+
32
+ /** The import's refusal: every undeclarable object, then why for each kind among them. */
33
+ export class UndeclarableObjectsError extends Error {
34
+ constructor(
35
+ readonly dialect: SqlDialect,
36
+ readonly objects: readonly UndeclarableObject[],
37
+ ) {
38
+ const kinds = [...new Set(objects.map((o) => o.kind))];
39
+ const n = objects.length;
40
+ super(
41
+ [
42
+ `the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} server has ${n === 1 ? "an object" : `${n} objects`} of a kind chant cannot declare, so the import would leave ${n === 1 ? "it" : "them"} out:`,
43
+ ...objects.map((o) => ` - ${describeUndeclarable(o)}`),
44
+ "Why, by kind:",
45
+ ...kinds.map((k) => ` - ${k}: ${kindRow(dialect, k).reason ?? "not declared"}`),
46
+ `To import everything else and list ${n === 1 ? "it" : "these"} as warnings, run the import again with --skip-undeclarable. Which kinds chant declares: ${DOCS}`,
47
+ ].join("\n"),
48
+ );
49
+ this.name = "UndeclarableObjectsError";
50
+ }
51
+ }
52
+
53
+ /** One warning per object, for `--skip-undeclarable`. */
54
+ export function undeclarableWarnings(dialect: SqlDialect, objects: readonly UndeclarableObject[]): string[] {
55
+ return objects.map((o) => `${describeUndeclarable(o)} is left out (--skip-undeclarable): ${kindRow(dialect, o.kind).reason ?? "chant cannot declare it"}`);
56
+ }
57
+
58
+ /**
59
+ * Refuse the import when it found undeclarable objects, or, with `skip`,
60
+ * add a warning per object to `warnings`. Nothing found is nothing to do.
61
+ */
62
+ export function refuseUndeclarable(dialect: SqlDialect, objects: readonly UndeclarableObject[], skip: boolean | undefined, warnings: string[]): void {
63
+ if (objects.length === 0) return;
64
+ if (!skip) throw new UndeclarableObjectsError(dialect, objects);
65
+ warnings.push(...undeclarableWarnings(dialect, objects));
66
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "source": "src/Parsers/ParserCreate*.h and src/Parsers/Access/ParserCreate*.h, at the pinned release's tag",
3
+ "version": "26.8.15.10",
4
+ "tag": "v26.8.15.10-lts",
5
+ "commands": [
6
+ "CREATE DATABASE",
7
+ "CREATE DICTIONARY",
8
+ "CREATE FUNCTION",
9
+ "CREATE HANDLER",
10
+ "CREATE INDEX",
11
+ "CREATE MASKING POLICY",
12
+ "CREATE NAMED COLLECTION",
13
+ "CREATE QUOTA",
14
+ "CREATE RESOURCE",
15
+ "CREATE ROLE",
16
+ "CREATE ROW POLICY",
17
+ "CREATE SETTINGS PROFILE",
18
+ "CREATE TABLE",
19
+ "CREATE USER",
20
+ "CREATE VIEW",
21
+ "CREATE WINDOW VIEW",
22
+ "CREATE WORKLOAD"
23
+ ]
24
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "dialect": "clickhouse",
3
+ "kinds": [
4
+ { "command": "CREATE DATABASE", "kind": "database", "status": "declared", "entity": "ClickHouse::Database" },
5
+ { "command": "CREATE DICTIONARY", "kind": "dictionary", "status": "declared", "entity": "ClickHouse::Dictionary" },
6
+ { "command": "CREATE FUNCTION", "kind": "SQL function", "status": "declared", "entity": "ClickHouse::Function" },
7
+ { "command": "CREATE FUNCTION", "form": "LANGUAGE WASM, or with a driver", "match": "\\bLANGUAGE\\b|\\bUSING\\s+DRIVER\\b", "kind": "non-SQL function", "status": "refused", "reason": "only a SQL function (CREATE FUNCTION f AS (x) -> ...) is declared" },
8
+ { "command": "CREATE HANDLER", "kind": "HTTP handler", "status": "refused", "reason": "an HTTP handler is server-level configuration, not a schema object" },
9
+ { "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "ClickHouse::Table", "note": "an index is part of its table: SHOW CREATE TABLE prints it, and the table's declaration holds it" },
10
+ { "command": "CREATE MASKING POLICY", "kind": "masking policy", "status": "refused", "reason": "masking policies are available only in ClickHouse Cloud, not on the pinned server" },
11
+ { "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "refused", "reason": "a named collection holds credentials (a password, a secret key), which are the environment's and never in a build; the server hides its values, so a plan could not compare them either" },
12
+ { "command": "CREATE QUOTA", "kind": "quota", "status": "declared", "entity": "ClickHouse::Quota", "note": "import reads only the access objects a build declares" },
13
+ { "command": "CREATE RESOURCE", "kind": "workload resource", "status": "refused", "reason": "a resource is server-level scheduling (a disk's reads or writes), configured with the server, not a schema object" },
14
+ { "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "ClickHouse::Role", "note": "import reads only the access objects a build declares" },
15
+ { "command": "CREATE ROW POLICY", "kind": "row policy", "status": "declared", "entity": "ClickHouse::RowPolicy", "note": "import reads only the access objects a build declares" },
16
+ { "command": "CREATE SETTINGS PROFILE", "kind": "settings profile", "status": "declared", "entity": "ClickHouse::SettingsProfile", "note": "import reads only the access objects a build declares" },
17
+ { "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "ClickHouse::Table" },
18
+ { "command": "CREATE TABLE", "form": "TEMPORARY", "match": "^CREATE\\s+TEMPORARY\\b", "kind": "temporary table", "status": "out-of-scope", "reason": "a temporary table lives in one session" },
19
+ { "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "ClickHouse::User", "note": "import reads only the access objects a build declares" },
20
+ { "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "ClickHouse::View" },
21
+ { "command": "CREATE VIEW", "form": "MATERIALIZED", "match": "^CREATE\\s+(?:OR\\s+REPLACE\\s+)?MATERIALIZED\\b", "kind": "materialized view", "status": "declared", "entity": "ClickHouse::MaterializedView" },
22
+ { "command": "CREATE WINDOW VIEW", "kind": "window view", "status": "refused", "reason": "a window view is experimental, and the pinned server refuses it under its default query analyzer" },
23
+ { "command": "CREATE WORKLOAD", "kind": "workload", "status": "refused", "reason": "a workload is server-level scheduling, configured with the server, not a schema object" }
24
+ ]
25
+ }