@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
@@ -16,17 +16,17 @@
16
16
  * With `schema`, the names the DDL leaves unqualified are qualified with it,
17
17
  * as an ORM's connection would: an object's own name, an index's, trigger's
18
18
  * or policy's table, a foreign key's table, a comment's target, and a column
19
- * type naming an enum or domain the file creates.
19
+ * type naming an enum, composite, range or domain type the file creates.
20
20
  */
21
21
 
22
22
  import type { Declarable } from "@intentius/chant/declarable";
23
23
  import { isTrivia, tokenizeText, type Token } from "../postgres/tokens";
24
- import { identValue, parseStatements, type NameNode, type StatementNode } from "../postgres/parser";
24
+ import { identValue, parseStatements, type NameNode, type Span, type StatementNode } from "../postgres/parser";
25
25
  import * as tags from "../postgres/entities";
26
26
  import { POSTGRES_ENTITY_TYPES, type PostgresEntityType } from "../postgres/entity-types";
27
27
  import { POSTGRES_TAG_OF } from "../postgres/import/generator";
28
28
  import type { ImportedPgObject } from "../postgres/import/ir";
29
- import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, type FileLine, type ReadOptions } from "./common";
29
+ import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
30
30
 
31
31
  type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
32
32
 
@@ -91,6 +91,11 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
91
91
  problems.push(`not a statement that declares an object: ${firstLine(sql)}`);
92
92
  continue;
93
93
  }
94
+ const undeclarable = undeclarableStatement("postgres", sql);
95
+ if (undeclarable) {
96
+ (options.undeclarable ?? problems).push(undeclarable);
97
+ continue;
98
+ }
94
99
  try {
95
100
  const tokens = tokenizeText(sql, 0);
96
101
  for (const node of parseStatements(tokens)) parsed.push({ tokens, node, line: lineAt(ddl, stmt[0]!.start) });
@@ -112,7 +117,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
112
117
  // The types the file creates, so a column of one is qualified with the rest.
113
118
  const fileTypes = new Set<string>();
114
119
  for (const { tokens, node } of parsed) {
115
- if (node.statement === "enum" || node.statement === "domain") {
120
+ if (node.statement === "enum" || node.statement === "domain" || node.statement === "composite" || node.statement === "range") {
116
121
  const pieces = nameOf(tokens, node.name);
117
122
  if (pieces.length === 1) fileTypes.add(pieces[0]!);
118
123
  }
@@ -133,7 +138,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
133
138
  };
134
139
  switch (node.statement) {
135
140
  case "comment":
136
- if (node.objectType !== "SCHEMA" && node.objectType !== "EXTENSION" && node.objectType !== "ROLE" && node.objectType !== "TRIGGER" && node.objectType !== "POLICY" && node.objectType !== "CONSTRAINT" && node.objectType !== "COLUMN") mark(node.target);
141
+ if (!["SCHEMA", "EXTENSION", "ROLE", "TRIGGER", "POLICY", "CONSTRAINT", "COLUMN", "CAST", "EVENT TRIGGER"].includes(node.objectType)) mark(node.target);
137
142
  if (node.objectType === "COLUMN" && nameOf(tokens, node.target).length === 2 && options.schema) {
138
143
  const first = tokens.slice(node.target.span.from, node.target.span.to).find((t) => !isTrivia(t));
139
144
  if (first) inserts.add(tokens.indexOf(first));
@@ -172,7 +177,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
172
177
  problems.push(`an index with no name (its declaration needs one): ${firstLine(tokensText(tokens))}`);
173
178
  continue;
174
179
  }
175
- const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant"].includes(node.statement);
180
+ const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant", "cast", "eventTrigger"].includes(node.statement);
176
181
  if (ownName && "name" in node) mark(node.name);
177
182
  const object: PgFileObject = { node, statements: [rewrite(tokens, inserts, options.schema)], constraints: [], line, unparsed: fileUnparsed("unknown" in node ? node.unknown : undefined, tokens, tokensText(tokens), line) };
178
183
  objects.push(object);
@@ -272,6 +277,10 @@ function ownerKey(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify:
272
277
  case "trigger":
273
278
  case "policy":
274
279
  return `${node.statement} ${nameOf(tokens, node.name).at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.table)))}`;
280
+ case "eventTrigger":
281
+ return `eventTrigger ${nameOf(tokens, node.name).at(-1) ?? ""}`;
282
+ case "cast":
283
+ return `cast ${castKey(tokens, node.source, node.target)}`;
275
284
  case "grant":
276
285
  case "comment":
277
286
  case "rowSecurity":
@@ -300,6 +309,10 @@ function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qua
300
309
  case "TRIGGER":
301
310
  case "POLICY":
302
311
  return node.on ? `${node.objectType.toLowerCase()} ${target.at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.on)))}` : undefined;
312
+ case "EVENT TRIGGER":
313
+ return `eventTrigger ${target.at(-1) ?? ""}`;
314
+ case "CAST":
315
+ return node.cast ? `cast ${castKey(tokens, node.cast.source, node.cast.target)}` : undefined;
303
316
  case "CONSTRAINT":
304
317
  case "DOMAIN CONSTRAINT":
305
318
  return node.on ? `rel ${qkey(qualify(nameOf(tokens, node.on)))}` : undefined;
@@ -308,6 +321,12 @@ function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qua
308
321
  }
309
322
  }
310
323
 
324
+ /** A cast's two types as one key, spacing and case aside. */
325
+ function castKey(tokens: readonly Token[], source: Span, target: Span): string {
326
+ const text = (sp: Span) => tokens.slice(sp.from, sp.to).filter((t) => !isTrivia(t)).map((t) => (t.kind === "ident" ? t.text.toLowerCase() : t.text)).join(" ");
327
+ return `(${text(source)} AS ${text(target)})`;
328
+ }
329
+
311
330
  /** The statement's tokens with `schema.` inserted before each marked one. */
312
331
  function rewrite(tokens: readonly Token[], inserts: ReadonlySet<number>, schema: string | undefined): Token[] {
313
332
  if (!schema || inserts.size === 0) return [...tokens];
@@ -49,6 +49,16 @@
49
49
  "kind": "resource",
50
50
  "lexicon": "sql"
51
51
  },
52
+ "Quota": {
53
+ "resourceType": "ClickHouse::Quota",
54
+ "kind": "resource",
55
+ "lexicon": "sql"
56
+ },
57
+ "SettingsProfile": {
58
+ "resourceType": "ClickHouse::SettingsProfile",
59
+ "kind": "resource",
60
+ "lexicon": "sql"
61
+ },
52
62
  "PostgresSchema": {
53
63
  "resourceType": "Postgres::Schema",
54
64
  "kind": "resource",
@@ -84,11 +94,41 @@
84
94
  "kind": "resource",
85
95
  "lexicon": "sql"
86
96
  },
97
+ "PostgresCompositeType": {
98
+ "resourceType": "Postgres::CompositeType",
99
+ "kind": "resource",
100
+ "lexicon": "sql"
101
+ },
102
+ "PostgresRangeType": {
103
+ "resourceType": "Postgres::RangeType",
104
+ "kind": "resource",
105
+ "lexicon": "sql"
106
+ },
87
107
  "PostgresDomain": {
88
108
  "resourceType": "Postgres::Domain",
89
109
  "kind": "resource",
90
110
  "lexicon": "sql"
91
111
  },
112
+ "PostgresAggregate": {
113
+ "resourceType": "Postgres::Aggregate",
114
+ "kind": "resource",
115
+ "lexicon": "sql"
116
+ },
117
+ "PostgresCollation": {
118
+ "resourceType": "Postgres::Collation",
119
+ "kind": "resource",
120
+ "lexicon": "sql"
121
+ },
122
+ "PostgresCast": {
123
+ "resourceType": "Postgres::Cast",
124
+ "kind": "resource",
125
+ "lexicon": "sql"
126
+ },
127
+ "PostgresEventTrigger": {
128
+ "resourceType": "Postgres::EventTrigger",
129
+ "kind": "resource",
130
+ "lexicon": "sql"
131
+ },
92
132
  "PostgresExtension": {
93
133
  "resourceType": "Postgres::Extension",
94
134
  "kind": "resource",
@@ -9,7 +9,9 @@
9
9
  * anything else is ClickHouse.
10
10
  *
11
11
  * A statement that declares nothing, or does not parse, is an error naming
12
- * it (`./sql-files.ts`); none is left out. A clause the parser does not know
12
+ * it (`./sql-files.ts`); none is left out. So is a `CREATE` of a kind chant
13
+ * cannot declare (#3911, `./coverage.ts`): `--parser-option
14
+ * skipUndeclarable` imports the rest and lists those as warnings. A clause the parser does not know
13
15
  * (#3872) is kept in the template as written.
14
16
  */
15
17
 
@@ -52,13 +54,19 @@ export function dialectOption(options: ParserOptions | undefined): SqlDialect |
52
54
  }
53
55
 
54
56
  export class SqlFileParser implements TemplateParser {
55
- constructor(private readonly dialect?: SqlDialect) {}
57
+ constructor(
58
+ private readonly dialect?: SqlDialect,
59
+ private readonly options: { skipUndeclarable?: boolean } = {},
60
+ ) {}
56
61
 
57
62
  parse(content: string): TemplateIR {
58
63
  const dialect = this.dialect ?? guessDialect(content);
59
64
  const problems: string[] = [];
60
- const ir = dialect === "postgres" ? pgObjectsToIR(readPostgres(content, { origin: "the DDL" }, problems)) : chObjectsToIR(readClickHouse(content, { origin: "the DDL" }, problems));
65
+ const skipped: string[] = [];
66
+ const read = { origin: "the DDL", ...(this.options.skipUndeclarable ? { undeclarable: skipped } : {}) };
67
+ const ir = dialect === "postgres" ? pgObjectsToIR(readPostgres(content, read, problems)) : chObjectsToIR(readClickHouse(content, read, problems));
61
68
  if (problems.length > 0) throw new SqlFileError(`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} DDL`, problems);
62
- return ir;
69
+ if (skipped.length === 0) return ir;
70
+ return { ...ir, warnings: [...(ir.warnings ?? []), ...skipped.map((s) => `left out (skipUndeclarable): ${s}`)] };
63
71
  }
64
72
  }
package/src/index.ts CHANGED
@@ -12,13 +12,13 @@ export { sqlConfigSchema, type SqlConfig } from "./config";
12
12
  // them as intrinsics (check-lexicon resolves a registered intrinsic against the
13
13
  // root's exports). Declarations import them from the dialect subpath,
14
14
  // `@intentius/chant-lexicon-sql/clickhouse`.
15
- export { database, table, view, dictionary, user, role, policy, grant, literal } from "./clickhouse/entities";
15
+ export { database, table, view, dictionary, user, role, policy, quota, settingsProfile, grant, literal } from "./clickhouse/entities";
16
16
 
17
17
  // The Postgres tags whose names ClickHouse does not export, for the same reason.
18
18
  // A tag folds as the function its file imports, so `table` imported from
19
19
  // `@intentius/chant-lexicon-sql/postgres` is Postgres's. Declarations import
20
20
  // them from the dialect subpath.
21
- export { schema, index, sequence, type, domain, extension, func, procedure, trigger } from "./postgres/entities";
21
+ export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, collation, cast } from "./postgres/entities";
22
22
 
23
23
  // A schema change's statements between two builds, rendered offline for a
24
24
  // migration file (#3644); also at `@intentius/chant-lexicon-sql/migration-statements`.
@@ -137,11 +137,24 @@ export function projectFromObjects(objects: OutputObject[], major: number): PgPr
137
137
  };
138
138
  }
139
139
 
140
- const TYPE_OBJECTS = new Set(["Postgres::Enum", "Postgres::Domain", "Postgres::Table", "Postgres::View", "Postgres::MaterializedView"]);
140
+ const TYPE_OBJECTS = new Set(["Postgres::Enum", "Postgres::CompositeType", "Postgres::RangeType", "Postgres::Domain", "Postgres::Table", "Postgres::View", "Postgres::MaterializedView"]);
141
141
  const ROUTINES = new Set(["Postgres::Function", "Postgres::Procedure"]);
142
142
 
143
+ /** A range type's multirange type's name: as written (its last piece), else as Postgres derives it. */
144
+ function multirangeOf(o: OutputObject): string | undefined {
145
+ if (o.type !== "Postgres::RangeType") return undefined;
146
+ const written = o.multirangeTypeName as string | undefined;
147
+ if (written) {
148
+ const last = written.split(".").pop()!.trim();
149
+ return /^"/.test(last) ? last.slice(1, -1).replace(/""/g, '"') : last.toLowerCase();
150
+ }
151
+ const name = String(o.name);
152
+ const at = name.indexOf("range");
153
+ return at >= 0 ? `${name.slice(0, at)}multi${name.slice(at)}` : `${name}_multirange`;
154
+ }
155
+
143
156
  function declared(p: PgProject, kinds: ReadonlySet<string>, schema: string | undefined, name: string): OutputObject | undefined {
144
- return p.objects.find((o) => kinds.has(o.type) && o.name === name && (schema === undefined || o.schema === schema));
157
+ return p.objects.find((o) => kinds.has(o.type) && (o.name === name || multirangeOf(o) === name) && (schema === undefined || o.schema === schema));
145
158
  }
146
159
 
147
160
  /** The extension that provides `name` in `section`, if the overlay knows one; declared ones first. */
@@ -14,6 +14,7 @@ const NOT_UNIQUE = new Set([
14
14
  "Postgres::DefaultPrivileges",
15
15
  "Postgres::Function",
16
16
  "Postgres::Procedure",
17
+ "Postgres::Aggregate",
17
18
  "Postgres::Trigger",
18
19
  "Postgres::Policy",
19
20
  ]);
@@ -1,7 +1,7 @@
1
1
  import { checkOf } from "./postgres-helpers";
2
2
  import { postgresObjects } from "./sql-helpers";
3
3
 
4
- const PLACED = new Set(["Postgres::Table", "Postgres::View", "Postgres::MaterializedView", "Postgres::Sequence", "Postgres::Enum", "Postgres::Domain"]);
4
+ const PLACED = new Set(["Postgres::Table", "Postgres::View", "Postgres::MaterializedView", "Postgres::Sequence", "Postgres::Enum", "Postgres::CompositeType", "Postgres::RangeType", "Postgres::Domain"]);
5
5
 
6
6
  /**
7
7
  * SQLPG113: an object in the public schema while the project declares schemas.
@@ -17,12 +17,12 @@ export const POSTGRES_TEMPLATE_SOURCES: readonly TemplateSource<PostgresTag>[] =
17
17
  {
18
18
  dialect: "postgres",
19
19
  modules: ["@intentius/chant-lexicon-sql/postgres"],
20
- tags: ["schema", "table", "index", "view", "sequence", "type", "domain", "extension", "func", "procedure", "trigger", "policy", "role", "grant"],
20
+ tags: ["schema", "table", "index", "view", "sequence", "type", "domain", "extension", "func", "procedure", "aggregate", "trigger", "eventTrigger", "collation", "cast", "policy", "role", "grant"],
21
21
  },
22
22
  {
23
23
  dialect: "postgres",
24
24
  modules: ["@intentius/chant-lexicon-sql"],
25
- tags: ["schema", "index", "sequence", "type", "domain", "extension", "func", "procedure", "trigger", "policy", "role", "grant"],
25
+ tags: ["schema", "index", "sequence", "type", "domain", "extension", "func", "procedure", "aggregate", "trigger", "eventTrigger", "collation", "cast", "policy", "role", "grant"],
26
26
  },
27
27
  ];
28
28
 
@@ -4,15 +4,22 @@ import { parseAccess } from "../../clickhouse/access";
4
4
  import { SqlSyntaxError } from "../../clickhouse/tokens";
5
5
  import { findTemplates, templatePosition, tokensOf } from "./templates";
6
6
 
7
- const ACCESS_TAGS = new Set(["user", "role", "policy", "grant"]);
8
- const ACCESS_STATEMENT: Record<string, string> = { user: "CREATE USER", role: "CREATE ROLE", rowPolicy: "CREATE ROW POLICY", grant: "GRANT" };
7
+ const ACCESS_TAGS = new Set(["user", "role", "policy", "quota", "settingsProfile", "grant"]);
8
+ const ACCESS_STATEMENT: Record<string, string> = {
9
+ user: "CREATE USER",
10
+ role: "CREATE ROLE",
11
+ rowPolicy: "CREATE ROW POLICY",
12
+ quota: "CREATE QUOTA",
13
+ settingsProfile: "CREATE SETTINGS PROFILE",
14
+ grant: "GRANT",
15
+ };
9
16
 
10
17
  const STATEMENT: Record<string, string> = { database: "CREATE DATABASE", table: "CREATE TABLE", view: "CREATE VIEW", dictionary: "CREATE DICTIONARY", function: "CREATE FUNCTION" };
11
18
  /** The tag a statement belongs in: `func` for a function, the statement's own name otherwise. */
12
19
  const tagOf = (statement: string): string => (statement === "function" ? "func" : statement);
13
20
 
14
21
  /**
15
- * SQLCH001: the DDL in a ClickHouse template (`database`, `table`, `view`, `dictionary`, `func`, `user`, `role`, `policy`, `grant`) does not parse, or holds another statement than its tag.
22
+ * SQLCH001: the DDL in a ClickHouse template (`database`, `table`, `view`, `dictionary`, `func`, `user`, `role`, `policy`, `quota`, `settingsProfile`, `grant`) does not parse, or holds another statement than its tag.
16
23
  *
17
24
  * The same parse runs when the build calls the tag, and fails the build there.
18
25
  * This rule reports it in the editor and in `chant lint`, at the token, before
@@ -1,27 +1,12 @@
1
1
  import type { LintContext, LintDiagnostic, LintRule } from "@intentius/chant/lint/rule";
2
2
  import { parseStatements, SqlSyntaxError } from "../../postgres/parser";
3
- import { followOnStray, STATEMENT_NAMES, type PostgresTag } from "../../postgres/entities";
3
+ import { followOnStray, STATEMENT_NAMES, STATEMENTS_OF, type PostgresTag } from "../../postgres/entities";
4
4
  import { findPostgresTemplates, postgresTokensOf, templatePosition, tokenPosition } from "./postgres-templates";
5
5
 
6
- /** The statement each tag holds. */
7
- const STATEMENT: Record<PostgresTag, string> = {
8
- schema: "schema",
9
- table: "table",
10
- index: "index",
11
- view: "view",
12
- sequence: "sequence",
13
- type: "enum",
14
- domain: "domain",
15
- extension: "extension",
16
- func: "function",
17
- procedure: "procedure",
18
- trigger: "trigger",
19
- policy: "policy",
20
- role: "role",
21
- grant: "grant",
22
- };
23
-
24
- const TAG_OF: Record<string, PostgresTag> = Object.fromEntries(Object.entries(STATEMENT).map(([tag, statement]) => [statement, tag as PostgresTag]));
6
+ /** The tag that declares each statement. */
7
+ const TAG_OF: Record<string, PostgresTag> = Object.fromEntries(
8
+ Object.entries(STATEMENTS_OF).flatMap(([tag, statements]) => statements.map((statement) => [statement, tag as PostgresTag])),
9
+ );
25
10
 
26
11
  /**
27
12
  * SQLPG001: the DDL in a Postgres template does not parse, or does not hold
@@ -60,7 +45,7 @@ export const sqlpg001: LintRule = {
60
45
  if (!tokens) continue;
61
46
  try {
62
47
  const [node, ...rest] = parseStatements(tokens);
63
- if (node!.statement !== STATEMENT[found.tag]) {
48
+ if (!STATEMENTS_OF[found.tag].includes(node!.statement)) {
64
49
  const use = TAG_OF[node!.statement];
65
50
  report(
66
51
  use
@@ -13,7 +13,7 @@ import { findSqlTemplates, type FoundTemplate as SqlFoundTemplate, type Template
13
13
 
14
14
  export { templatePosition, tokenPosition } from "../../core/find-templates";
15
15
 
16
- export type SqlTag = "database" | "table" | "view" | "dictionary" | "func" | "user" | "role" | "policy" | "grant";
16
+ export type SqlTag = "database" | "table" | "view" | "dictionary" | "func" | "user" | "role" | "policy" | "quota" | "settingsProfile" | "grant";
17
17
 
18
18
  /**
19
19
  * The ClickHouse tags, by the modules they are imported from. The package
@@ -22,7 +22,7 @@ export type SqlTag = "database" | "table" | "view" | "dictionary" | "func" | "us
22
22
  export const CLICKHOUSE_TEMPLATE_SOURCE: TemplateSource<SqlTag> = {
23
23
  dialect: "clickhouse",
24
24
  modules: ["@intentius/chant-lexicon-sql", "@intentius/chant-lexicon-sql/clickhouse"],
25
- tags: ["database", "table", "view", "dictionary", "func", "user", "role", "policy", "grant"],
25
+ tags: ["database", "table", "view", "dictionary", "func", "user", "role", "policy", "quota", "settingsProfile", "grant"],
26
26
  };
27
27
 
28
28
  export type FoundTemplate = SqlFoundTemplate<SqlTag>;
@@ -138,6 +138,20 @@ export function postgresDeclarations(content: string, fileName = "file.ts"): Pos
138
138
  sqlName = text(node.name.span);
139
139
  kind = "enum type";
140
140
  break;
141
+ case "composite":
142
+ case "range":
143
+ sqlName = text(node.name.span);
144
+ kind = `${node.statement} type`;
145
+ break;
146
+ case "aggregate":
147
+ case "collation":
148
+ sqlName = text(node.name.span);
149
+ kind = node.statement;
150
+ break;
151
+ case "eventTrigger":
152
+ sqlName = text(node.name.span);
153
+ kind = "event trigger";
154
+ break;
141
155
  case "index":
142
156
  sqlName = node.name ? text(node.name.span) : undefined;
143
157
  break;
package/src/mcp/index.ts CHANGED
@@ -10,7 +10,7 @@ import type { McpResourceContribution, McpToolContribution } from "@intentius/ch
10
10
  import { createCatalogResource, createDiffTool } from "@intentius/chant/lexicon-plugin-helpers";
11
11
  import { sqlSerializer } from "../serializer";
12
12
  import { catalogIndex } from "../lsp/catalog";
13
- import { database, dictionary, func, grant, policy, role, table, user, view } from "../clickhouse/entities";
13
+ import { database, dictionary, func, grant, policy, quota, role, settingsProfile, table, user, view } from "../clickhouse/entities";
14
14
  import { SqlSyntaxError } from "../clickhouse/tokens";
15
15
  import { existsSync, readFileSync } from "fs";
16
16
  import { diffSchemas } from "../clickhouse/plan/diff";
@@ -122,12 +122,12 @@ const searchTool: McpToolContribution = {
122
122
  const parseTool: McpToolContribution = {
123
123
  name: "parse-ddl",
124
124
  description:
125
- "Parse one CREATE statement the way its tag does (dialect clickhouse, the default: table, view, database, dictionary, func, user, role, policy, grant; dialect postgres: schema, table, index, view, sequence, type, domain, extension), and return the entity it builds (columns, engine, keys, settings, and for a view its lineage). A statement that does not parse comes back as the error with its line and column, the same one SQLCH001 (ClickHouse) or SQLPG001 (Postgres) reports. The statement is plain DDL: there are no interpolations.",
125
+ "Parse one CREATE statement the way its tag does (dialect clickhouse, the default: table, view, database, dictionary, func, user, role, policy, quota, settingsProfile, grant; dialect postgres: schema, table, index, view, sequence, type, domain, extension), and return the entity it builds (columns, engine, keys, settings, and for a view its lineage). A statement that does not parse comes back as the error with its line and column, the same one SQLCH001 (ClickHouse) or SQLPG001 (Postgres) reports. The statement is plain DDL: there are no interpolations.",
126
126
  inputSchema: {
127
127
  type: "object",
128
128
  properties: {
129
129
  dialect: dialectProp,
130
- tag: { type: "string", enum: [...new Set(["table", "view", "database", "dictionary", "func", "user", "role", "policy", "grant", ...POSTGRES_TAGS])], description: "The tag the statement belongs in (ClickHouse: table, view, database, dictionary, func, user, role, policy, grant; Postgres: " + POSTGRES_TAGS.join(", ") + ")" },
130
+ tag: { type: "string", enum: [...new Set(["table", "view", "database", "dictionary", "func", "user", "role", "policy", "quota", "settingsProfile", "grant", ...POSTGRES_TAGS])], description: "The tag the statement belongs in (ClickHouse: table, view, database, dictionary, func, user, role, policy, quota, settingsProfile, grant; Postgres: " + POSTGRES_TAGS.join(", ") + ")" },
131
131
  ddl: { type: "string", description: "The CREATE statement" },
132
132
  },
133
133
  required: ["tag", "ddl"],
@@ -136,8 +136,8 @@ const parseTool: McpToolContribution = {
136
136
  const ddl = String(params.ddl ?? "");
137
137
  const tag = String(params.tag);
138
138
  if (asDialect(params.dialect) === "postgres") return postgresParse(tag, ddl, position);
139
- const build = tag === "table" ? table : tag === "view" ? view : tag === "database" ? database : tag === "dictionary" ? dictionary : tag === "func" ? func : tag === "user" ? user : tag === "role" ? role : tag === "policy" ? policy : tag === "grant" ? grant : undefined;
140
- if (!build) throw new Error("tag must be table, view, database, dictionary, func, user, role, policy or grant");
139
+ const build = tag === "table" ? table : tag === "view" ? view : tag === "database" ? database : tag === "dictionary" ? dictionary : tag === "func" ? func : tag === "user" ? user : tag === "role" ? role : tag === "policy" ? policy : tag === "quota" ? quota : tag === "settingsProfile" ? settingsProfile : tag === "grant" ? grant : undefined;
140
+ if (!build) throw new Error("tag must be table, view, database, dictionary, func, user, role, policy, quota, settingsProfile or grant");
141
141
  const strings = Object.assign([ddl], { raw: [ddl] }) as unknown as TemplateStringsArray;
142
142
  try {
143
143
  const entity = build(strings);
@@ -81,7 +81,7 @@ export function postgresSearch(params: Record<string, unknown>): unknown {
81
81
  };
82
82
  }
83
83
 
84
- const TAGS = { schema: pg.schema, table: pg.table, index: pg.index, view: pg.view, sequence: pg.sequence, type: pg.type, domain: pg.domain, extension: pg.extension, func: pg.func, procedure: pg.procedure, trigger: pg.trigger, policy: pg.policy, role: pg.role, grant: pg.grant } as const;
84
+ const TAGS = { schema: pg.schema, table: pg.table, index: pg.index, view: pg.view, sequence: pg.sequence, type: pg.type, domain: pg.domain, extension: pg.extension, func: pg.func, procedure: pg.procedure, aggregate: pg.aggregate, trigger: pg.trigger, eventTrigger: pg.eventTrigger, collation: pg.collation, cast: pg.cast, policy: pg.policy, role: pg.role, grant: pg.grant } as const;
85
85
  export const POSTGRES_TAGS = Object.keys(TAGS);
86
86
 
87
87
  /** Parse one statement with the Postgres tag; an error comes back as SQLPG001 with its line and column. */
package/src/plugin.ts CHANGED
@@ -99,15 +99,21 @@ export const sqlPlugin: LexiconPlugin = {
99
99
  },
100
100
  { name: "dictionary", isTag: true, description: "A ClickHouse CREATE DICTIONARY, parsed into a dictionary entity with its attributes, key, source and layout" },
101
101
  { name: "user", isTag: true, description: "A ClickHouse CREATE USER, parsed into a user entity; its password is the environment's" },
102
+ { name: "quota", isTag: true, description: "A ClickHouse CREATE QUOTA, parsed into a quota entity with its key, its intervals' limits and whom it applies to" },
103
+ { name: "settingsProfile", isTag: true, description: "A ClickHouse CREATE SETTINGS PROFILE, parsed into a settings profile entity with its settings, constraints and inherited profiles" },
102
104
  { name: "schema", isTag: true, description: "A Postgres CREATE SCHEMA, parsed into a schema entity" },
103
105
  { name: "index", isTag: true, description: "A Postgres CREATE INDEX, parsed into an index entity" },
104
106
  { name: "sequence", isTag: true, description: "A Postgres CREATE SEQUENCE, parsed into a sequence entity" },
105
- { name: "type", isTag: true, description: "A Postgres CREATE TYPE ... AS ENUM, parsed into an enum entity" },
107
+ { name: "type", isTag: true, description: "A Postgres CREATE TYPE ... AS ENUM, AS (...) or AS RANGE, parsed into an enum, composite or range type entity" },
106
108
  { name: "domain", isTag: true, description: "A Postgres CREATE DOMAIN, parsed into a domain entity" },
107
109
  { name: "extension", isTag: true, description: "A Postgres CREATE EXTENSION, parsed into an extension entity" },
108
110
  { name: "func", isTag: true, description: "A Postgres CREATE FUNCTION, parsed into a function entity with its body kept verbatim, or a ClickHouse CREATE FUNCTION (a named lambda)" },
109
111
  { name: "procedure", isTag: true, description: "A Postgres CREATE PROCEDURE, parsed into a procedure entity with its body kept verbatim" },
112
+ { name: "aggregate", isTag: true, description: "A Postgres CREATE AGGREGATE, parsed into an aggregate entity identified by its argument types" },
110
113
  { name: "trigger", isTag: true, description: "A Postgres CREATE TRIGGER, parsed into a trigger entity on its table" },
114
+ { name: "eventTrigger", isTag: true, description: "A Postgres CREATE EVENT TRIGGER, parsed into the database's event trigger entity" },
115
+ { name: "collation", isTag: true, description: "A Postgres CREATE COLLATION, parsed into a collation entity" },
116
+ { name: "cast", isTag: true, description: "A Postgres CREATE CAST, parsed into a cast entity named by its two types" },
111
117
  { name: "policy", isTag: true, description: "A Postgres CREATE POLICY, parsed into a row-level security policy on its table, or a ClickHouse CREATE ROW POLICY" },
112
118
  { name: "role", isTag: true, description: "A Postgres CREATE ROLE without a password or memberships, parsed into a role entity, or a ClickHouse CREATE ROLE" },
113
119
  { name: "grant", isTag: true, description: "A Postgres GRANT, REVOKE or ALTER DEFAULT PRIVILEGES, parsed into the privileges it declares, or a ClickHouse GRANT of privileges or roles" },
@@ -159,7 +165,7 @@ export const sqlPlugin: LexiconPlugin = {
159
165
 
160
166
  /** `chant import schema.sql`: a file of Postgres or ClickHouse DDL (`./import-parser.ts`). */
161
167
  templateParser(options) {
162
- return new SqlFileParser(dialectOption(options));
168
+ return new SqlFileParser(dialectOption(options), { skipUndeclarable: options?.skipUndeclarable === true });
163
169
  },
164
170
 
165
171
  parserOptions() {
@@ -169,6 +175,11 @@ export const sqlPlugin: LexiconPlugin = {
169
175
  type: "string" as const,
170
176
  description: "the DDL's dialect, clickhouse or postgres; read off its statements when omitted",
171
177
  },
178
+ {
179
+ name: "skipUndeclarable",
180
+ type: "boolean" as const,
181
+ description: "import the rest of the file, and list each CREATE of a kind chant cannot declare (an aggregate, a cast, a quota) as a warning instead of failing",
182
+ },
172
183
  ];
173
184
  },
174
185
 
@@ -151,7 +151,7 @@ export const refName = (o: { kind: CanonicalKind; schema?: string; name: string;
151
151
  ? `${o.name}${o.signature ?? ""}`
152
152
  : o.kind === "schema" || o.kind === "extension" || o.kind === "role" || o.schema === undefined
153
153
  ? o.name
154
- : `${o.schema}.${o.name}${o.kind === "function" || o.kind === "procedure" ? (o.signature ?? "") : ""}`;
154
+ : `${o.schema}.${o.name}${o.kind === "function" || o.kind === "procedure" || o.kind === "aggregate" ? (o.signature ?? "") : ""}`;
155
155
 
156
156
  /**
157
157
  * The objects a Postgres `chant build` output declares, in creation order,
@@ -564,7 +564,13 @@ function liveCanonicalAddress(o: LivePgObject): { kind: CanonicalKind; schema?:
564
564
  "Postgres::MaterializedView": "materializedView",
565
565
  "Postgres::Sequence": "sequence",
566
566
  "Postgres::Enum": "enum",
567
+ "Postgres::CompositeType": "composite",
568
+ "Postgres::RangeType": "range",
567
569
  "Postgres::Domain": "domain",
570
+ "Postgres::Aggregate": "aggregate",
571
+ "Postgres::Collation": "collation",
572
+ "Postgres::Cast": "cast",
573
+ "Postgres::EventTrigger": "eventTrigger",
568
574
  "Postgres::Extension": "extension",
569
575
  "Postgres::Function": "function",
570
576
  "Postgres::Procedure": "procedure",
@@ -577,7 +583,7 @@ function liveCanonicalAddress(o: LivePgObject): { kind: CanonicalKind; schema?:
577
583
  )[o.type];
578
584
  return {
579
585
  kind,
580
- ...(o.schema !== undefined && kind !== "schema" && kind !== "extension" && kind !== "role" ? { schema: o.schema } : {}),
586
+ ...(o.schema !== undefined && kind !== "schema" && kind !== "extension" && kind !== "role" && kind !== "cast" && kind !== "eventTrigger" ? { schema: o.schema } : {}),
581
587
  name: o.name,
582
588
  ...(o.signature !== undefined ? { signature: o.signature } : {}),
583
589
  };
@@ -100,12 +100,15 @@ export const pgString = (value: string): string => `'${value.replace(/'/g, "''")
100
100
  export const pgQualified = (schema: string | undefined, name: string): string => (schema ? `${quoteIdent(schema)}.${quoteIdent(name)}` : quoteIdent(name));
101
101
 
102
102
  /**
103
- * An object's name in a statement. A schema and an extension have no schema;
104
- * a routine is named with its parameter types (`app.f(integer,text)`), a
105
- * trigger with its table (`touch ON app.users`).
103
+ * An object's name in a statement. A schema, an extension and an event
104
+ * trigger have no schema; a routine or an aggregate is named with its
105
+ * parameter types (`app.f(integer,text)`), a trigger with its table
106
+ * (`touch ON app.users`), a cast by its types (`(app.money AS numeric)`).
106
107
  */
107
108
  export const objectIdent = (o: { kind: CanonicalKind; schema?: string; name: string; signature?: string }): string =>
108
- o.kind === "schema" || o.kind === "extension" || o.kind === "role"
109
+ o.kind === "cast"
110
+ ? o.name
111
+ : o.kind === "schema" || o.kind === "extension" || o.kind === "role" || o.kind === "eventTrigger"
109
112
  ? quoteIdent(o.name)
110
113
  : o.kind === "trigger" || o.kind === "policy"
111
114
  ? `${quoteIdent(o.name)}${o.signature ?? ""}`
@@ -120,10 +123,16 @@ export const KIND_WORDS: Readonly<Record<CanonicalKind, string>> = {
120
123
  materializedView: "MATERIALIZED VIEW",
121
124
  sequence: "SEQUENCE",
122
125
  enum: "TYPE",
126
+ composite: "TYPE",
127
+ range: "TYPE",
123
128
  domain: "DOMAIN",
124
129
  extension: "EXTENSION",
125
130
  function: "FUNCTION",
126
131
  procedure: "PROCEDURE",
132
+ aggregate: "AGGREGATE",
133
+ collation: "COLLATION",
134
+ cast: "CAST",
135
+ eventTrigger: "EVENT TRIGGER",
127
136
  trigger: "TRIGGER",
128
137
  policy: "POLICY",
129
138
  role: "ROLE",
@@ -254,7 +263,12 @@ export function createSteps(
254
263
  // A table's row-level security is access, which only a profile that manages access applies.
255
264
  if (i > 0 && opts.access === false && firstNode(s)?.statement === "rowSecurity") return;
256
265
  let sql = s.text;
257
- const replaceable = obj.canonical.kind === "view" || obj.canonical.kind === "function" || obj.canonical.kind === "procedure" || (obj.canonical.kind === "trigger" && obj.canonical.fields.constraint !== true);
266
+ const replaceable =
267
+ obj.canonical.kind === "view" ||
268
+ obj.canonical.kind === "function" ||
269
+ obj.canonical.kind === "procedure" ||
270
+ obj.canonical.kind === "aggregate" ||
271
+ (obj.canonical.kind === "trigger" && obj.canonical.fields.constraint !== true);
258
272
  if (i === 0 && opts.orReplace && replaceable && !/^\s*CREATE\s+OR\s+REPLACE\b/i.test(sql)) sql = sql.replace(/\bCREATE\s+/i, "CREATE OR REPLACE ");
259
273
  const concurrently = i === 0 && obj.canonical.kind === "index" && obj.props.concurrently === true;
260
274
  out.push({ ...step(sql, cls, opts.rule, !concurrently), ...(concurrently ? { buildsIndex: objectIdent(obj.canonical) } : {}) });
@@ -528,7 +542,7 @@ export const isRefused = (c: PgChange): boolean => c.class === "expand";
528
542
  export const isDestructive = (c: PgChange): boolean => c.rule === "SQLPG204";
529
543
 
530
544
  /** Changes that replace the object: drop it and create it again from its declaration. */
531
- export const isRecreate = (c: PgChange): boolean => c.rule === "SQLPG252" || c.rule === "SQLPG243" || c.rule === "SQLPG281";
545
+ export const isRecreate = (c: PgChange): boolean => c.rule === "SQLPG252" || c.rule === "SQLPG243" || c.rule === "SQLPG281" || c.rule === "SQLPG300";
532
546
 
533
547
  /**
534
548
  * SET NOT NULL on a table that exists, without reading the table under
@@ -692,6 +706,7 @@ export function alterSteps(
692
706
  break;
693
707
  case "SQLPG250":
694
708
  case "SQLPG280":
709
+ case "SQLPG302":
695
710
  steps.push(...createSteps(obj, opts.marker, { orReplace: true, cls: c.class, rule: c.rule }).slice(0, 1));
696
711
  break;
697
712
  case "SQLPG291": {
@@ -872,11 +887,19 @@ const DROP_ORDER: Record<CanonicalKind, number> = {
872
887
  index: 3,
873
888
  table: 4,
874
889
  sequence: 5,
890
+ // An event trigger and a cast before the function they call; an aggregate before its functions.
891
+ eventTrigger: 5.5,
892
+ cast: 5.5,
893
+ aggregate: 5.5,
875
894
  // A routine after what calls it (a view, a trigger, a default), before the types its parameters use.
876
895
  function: 6,
877
896
  procedure: 6,
878
897
  domain: 7,
898
+ composite: 7.5,
899
+ range: 7.5,
879
900
  enum: 8,
901
+ // A collation after the types and columns that use it.
902
+ collation: 8.5,
880
903
  extension: 9,
881
904
  schema: 10,
882
905
  // A role is the cluster's: never dropped by a prune (`../live/catalog.ts` reads only the declared ones).
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
  import { isDeclarable } from "@intentius/chant/declarable";
3
3
  import { getInterpolationFields } from "@intentius/chant/provenance";
4
- import { domain, extension, index, isColumnRef, literal, schema, sequence, SqlTemplateError, table, type, view } from "./entities";
4
+ import { domain, extension, index, isColumnRef, literal, schema, sequence, SqlTemplateError, table, type, view, type EnumProps } from "./entities";
5
5
 
6
6
  const app = schema`CREATE SCHEMA app`;
7
7
  const orderStatus = type`CREATE TYPE ${app}.order_status AS ENUM ('placed', 'paid')`;
@@ -121,7 +121,7 @@ describe("indexes, views and the rest", () => {
121
121
  const e = extension`CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA ${app} VERSION '1.6'`;
122
122
  expect(e.props).toMatchObject({ name: "citext", schema: "app", version: "1.6", ifNotExists: true });
123
123
  expect(e.dependsOn).toEqual([app]);
124
- expect(orderStatus.props.labels).toEqual(["placed", "paid"]);
124
+ expect((orderStatus.props as EnumProps).labels).toEqual(["placed", "paid"]);
125
125
  });
126
126
  });
127
127