@intentius/chant-lexicon-sql 0.124.19 → 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 (156) 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/live/catalog.d.ts +6 -5
  9. package/dist/clickhouse/live/catalog.d.ts.map +1 -1
  10. package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
  11. package/dist/clickhouse/parser.d.ts +16 -0
  12. package/dist/clickhouse/parser.d.ts.map +1 -1
  13. package/dist/clickhouse/plan/deep.d.ts.map +1 -1
  14. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  15. package/dist/clickhouse/plan/normalize.d.ts +6 -3
  16. package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
  17. package/dist/clickhouse/plan/rules.d.ts +3 -0
  18. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  19. package/dist/clickhouse/topology.d.ts.map +1 -1
  20. package/dist/clickhouse.d.ts +1 -1
  21. package/dist/clickhouse.d.ts.map +1 -1
  22. package/dist/codegen/registry.d.ts.map +1 -1
  23. package/dist/coverage/clickhouse.json +8 -8
  24. package/dist/coverage/postgres.json +6 -6
  25. package/dist/files/clickhouse.d.ts +2 -1
  26. package/dist/files/clickhouse.d.ts.map +1 -1
  27. package/dist/files/postgres.d.ts +1 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/integrity.json +10 -10
  31. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  32. package/dist/lint/post-synth/sql101.d.ts.map +1 -1
  33. package/dist/lint/rules/sqlch001.d.ts +1 -1
  34. package/dist/lint/rules/sqlch001.d.ts.map +1 -1
  35. package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
  36. package/dist/lint/rules/templates.d.ts +1 -1
  37. package/dist/lint/rules/templates.d.ts.map +1 -1
  38. package/dist/lsp/postgres-template.d.ts.map +1 -1
  39. package/dist/manifest.json +1 -1
  40. package/dist/meta.json +40 -0
  41. package/dist/okf/index.md +9 -1
  42. package/dist/okf/rules/SQL101.md +1 -0
  43. package/dist/okf/rules/SQLCH001.md +2 -2
  44. package/dist/okf/rules/SQLPG113.md +2 -0
  45. package/dist/okf/types/PostgresAggregate.md +13 -0
  46. package/dist/okf/types/PostgresCast.md +9 -0
  47. package/dist/okf/types/PostgresCollation.md +9 -0
  48. package/dist/okf/types/PostgresCompositeType.md +13 -0
  49. package/dist/okf/types/PostgresEventTrigger.md +9 -0
  50. package/dist/okf/types/PostgresRangeType.md +13 -0
  51. package/dist/okf/types/Quota.md +9 -0
  52. package/dist/okf/types/SettingsProfile.md +9 -0
  53. package/dist/plugin.d.ts.map +1 -1
  54. package/dist/postgres/apply/apply.d.ts.map +1 -1
  55. package/dist/postgres/apply/statements.d.ts +4 -3
  56. package/dist/postgres/apply/statements.d.ts.map +1 -1
  57. package/dist/postgres/entities.d.ts +129 -6
  58. package/dist/postgres/entities.d.ts.map +1 -1
  59. package/dist/postgres/entity-types.d.ts +6 -0
  60. package/dist/postgres/entity-types.d.ts.map +1 -1
  61. package/dist/postgres/import/generator.d.ts +3 -2
  62. package/dist/postgres/import/generator.d.ts.map +1 -1
  63. package/dist/postgres/live/catalog-kinds.d.ts +28 -0
  64. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -0
  65. package/dist/postgres/live/catalog.d.ts +15 -11
  66. package/dist/postgres/live/catalog.d.ts.map +1 -1
  67. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  68. package/dist/postgres/parser.d.ts +88 -5
  69. package/dist/postgres/parser.d.ts.map +1 -1
  70. package/dist/postgres/plan/deep.d.ts.map +1 -1
  71. package/dist/postgres/plan/diff.d.ts.map +1 -1
  72. package/dist/postgres/plan/normalize.d.ts +17 -2
  73. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  74. package/dist/postgres/plan/rules.d.ts +3 -0
  75. package/dist/postgres/plan/rules.d.ts.map +1 -1
  76. package/dist/postgres/plan/schema.d.ts +3 -2
  77. package/dist/postgres/plan/schema.d.ts.map +1 -1
  78. package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
  79. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  80. package/dist/postgres.d.ts +1 -1
  81. package/dist/postgres.d.ts.map +1 -1
  82. package/dist/rules/postgres-names.ts +15 -2
  83. package/dist/rules/postgres-templates.ts +2 -2
  84. package/dist/rules/sql101.ts +1 -0
  85. package/dist/rules/sqlch001.ts +10 -3
  86. package/dist/rules/sqlpg001.ts +6 -21
  87. package/dist/rules/sqlpg113.ts +1 -1
  88. package/dist/rules/templates.ts +2 -2
  89. package/dist/validate.d.ts.map +1 -1
  90. package/package.json +2 -2
  91. package/src/clickhouse/access.ts +197 -22
  92. package/src/clickhouse/apply/statements.ts +17 -5
  93. package/src/clickhouse/entities.ts +78 -2
  94. package/src/clickhouse/import/generator.ts +16 -3
  95. package/src/clickhouse/live/catalog.ts +57 -10
  96. package/src/clickhouse/live/describe-resources.ts +3 -1
  97. package/src/clickhouse/live/kinds.e2e.test.ts +179 -0
  98. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -4
  99. package/src/clickhouse/parser.test.ts +45 -2
  100. package/src/clickhouse/parser.ts +111 -3
  101. package/src/clickhouse/plan/deep.ts +25 -6
  102. package/src/clickhouse/plan/diff.ts +41 -1
  103. package/src/clickhouse/plan/normalize.ts +12 -4
  104. package/src/clickhouse/plan/rules.ts +22 -1
  105. package/src/clickhouse/quota-profile.test.ts +186 -0
  106. package/src/clickhouse/table-function.test.ts +54 -0
  107. package/src/clickhouse/testdata/server-ddl-baseline.json +17 -14
  108. package/src/clickhouse/topology.ts +4 -2
  109. package/src/clickhouse.ts +6 -0
  110. package/src/codegen/postgres-classifier-page.ts +1 -1
  111. package/src/codegen/registry.ts +8 -0
  112. package/src/core/undeclarable.test.ts +22 -19
  113. package/src/coverage/clickhouse.json +8 -8
  114. package/src/coverage/postgres.json +6 -6
  115. package/src/files/clickhouse.ts +14 -6
  116. package/src/files/postgres.ts +19 -5
  117. package/src/generated/lexicon-sql.json +40 -0
  118. package/src/index.ts +2 -2
  119. package/src/lint/post-synth/postgres-names.ts +15 -2
  120. package/src/lint/post-synth/sql101.ts +1 -0
  121. package/src/lint/post-synth/sqlpg113.ts +1 -1
  122. package/src/lint/rules/postgres-templates.ts +2 -2
  123. package/src/lint/rules/sqlch001.ts +10 -3
  124. package/src/lint/rules/sqlpg001.ts +6 -21
  125. package/src/lint/rules/templates.ts +2 -2
  126. package/src/lsp/postgres-template.ts +14 -0
  127. package/src/mcp/index.ts +5 -5
  128. package/src/mcp/postgres.ts +1 -1
  129. package/src/plugin.ts +7 -1
  130. package/src/postgres/apply/apply.ts +8 -2
  131. package/src/postgres/apply/statements.ts +29 -6
  132. package/src/postgres/entities.test.ts +2 -2
  133. package/src/postgres/entities.ts +316 -28
  134. package/src/postgres/entity-types.ts +6 -0
  135. package/src/postgres/import/generator.ts +31 -4
  136. package/src/postgres/kinds.test.ts +140 -0
  137. package/src/postgres/live/catalog-kinds.ts +312 -0
  138. package/src/postgres/live/catalog.ts +30 -12
  139. package/src/postgres/live/describe-resources.ts +14 -5
  140. package/src/postgres/live/kinds.e2e.test.ts +182 -0
  141. package/src/postgres/live/roundtrip.e2e.test.ts +9 -3
  142. package/src/postgres/live/undeclarable.e2e.test.ts +15 -13
  143. package/src/postgres/parser.test.ts +2 -1
  144. package/src/postgres/parser.ts +307 -17
  145. package/src/postgres/plan/deep.ts +33 -0
  146. package/src/postgres/plan/diff.ts +45 -0
  147. package/src/postgres/plan/normalize.ts +153 -2
  148. package/src/postgres/plan/rules.ts +21 -0
  149. package/src/postgres/plan/schema.ts +6 -4
  150. package/src/postgres/testing/roundtrip-schema.ts +19 -1
  151. package/src/postgres.ts +19 -0
  152. package/src/skill-defs.ts +1 -1
  153. package/src/skills/chant-sql-postgres.md +7 -3
  154. package/src/skills/chant-sql.md +3 -1
  155. package/src/unknown-clauses.test.ts +2 -1
  156. package/src/validate.ts +8 -0
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * A hand-written, lossless parser for the Postgres statements the dialect
3
3
  * declares (chant #3278, #3279): `CREATE SCHEMA`, `TABLE`, `INDEX`, `VIEW`,
4
- * `MATERIALIZED VIEW`, `SEQUENCE`, `TYPE ... AS ENUM`, `DOMAIN`, `EXTENSION`,
5
- * `FUNCTION`, `PROCEDURE` and `TRIGGER` (#3680), `POLICY` and `ROLE`, `GRANT`,
4
+ * `MATERIALIZED VIEW`, `SEQUENCE`, `TYPE ... AS ENUM`, `TYPE ... AS (...)` and
5
+ * `TYPE ... AS RANGE`, `DOMAIN`, `EXTENSION`, `FUNCTION`, `PROCEDURE` and
6
+ * `TRIGGER` (#3680), `AGGREGATE`, `COLLATION`, `CAST` and `EVENT TRIGGER`
7
+ * (#3911), `POLICY` and `ROLE`, `GRANT`,
6
8
  * `REVOKE` and `ALTER DEFAULT PRIVILEGES` on schemas, tables, columns,
7
9
  * sequences and routines, `ALTER TABLE ... ROW LEVEL SECURITY` (#3681), and
8
10
  * `COMMENT ON` for those objects.
@@ -31,7 +33,8 @@
31
33
  * (sql-createtable, sql-createindex, sql-createview,
32
34
  * sql-creatematerializedview, sql-createsequence, sql-createtype,
33
35
  * sql-createdomain, sql-createextension, sql-createschema, sql-createfunction,
34
- * sql-createprocedure, sql-createtrigger, sql-comment) and
36
+ * sql-createprocedure, sql-createtrigger, sql-createaggregate,
37
+ * sql-createcollation, sql-createcast, sql-createeventtrigger, sql-comment) and
35
38
  * `src/backend/parser/gram.y` at `REL_18_6` where a page is silent.
36
39
  */
37
40
 
@@ -203,6 +206,71 @@ export interface EnumNode {
203
206
  labels: Span[];
204
207
  }
205
208
 
209
+ /** One `name [= value]` of a definition list (`CREATE AGGREGATE`, `COLLATION`, `TYPE ... AS RANGE`): the name folded, the value as written. */
210
+ export interface DefElem {
211
+ name: string;
212
+ nameSpan: Span;
213
+ value?: Span;
214
+ }
215
+
216
+ /** `CREATE TYPE name AS (attribute type [COLLATE collation], ...)`. */
217
+ export interface CompositeNode {
218
+ statement: "composite";
219
+ name: NameNode;
220
+ attributes: Array<{ name: string; nameSpan: Span; type: Span; collate?: Span }>;
221
+ }
222
+
223
+ /** `CREATE TYPE name AS RANGE (SUBTYPE = type [, ...])`. */
224
+ export interface RangeNode {
225
+ statement: "range";
226
+ name: NameNode;
227
+ options: DefElem[];
228
+ }
229
+
230
+ /** `CREATE [OR REPLACE] AGGREGATE name (args [ORDER BY args]) (SFUNC = ..., STYPE = ...)`. */
231
+ export interface AggregateNode {
232
+ statement: "aggregate";
233
+ orReplace: boolean;
234
+ name: NameNode;
235
+ /** The arguments; empty for `(*)`. For an ordered-set aggregate, the direct ones. */
236
+ args: RoutineArgNode[];
237
+ /** `(*)`: an aggregate with no arguments. */
238
+ star: boolean;
239
+ /** An ordered-set aggregate's aggregated arguments, after `ORDER BY`. */
240
+ orderBy?: RoutineArgNode[];
241
+ options: DefElem[];
242
+ }
243
+
244
+ /** `CREATE COLLATION [IF NOT EXISTS] name (option = value, ...)`. */
245
+ export interface CollationNode {
246
+ statement: "collation";
247
+ ifNotExists: boolean;
248
+ name: NameNode;
249
+ options: DefElem[];
250
+ }
251
+
252
+ /** `CREATE CAST (source AS target) WITH FUNCTION f[(args)] | WITHOUT FUNCTION | WITH INOUT [AS ASSIGNMENT | AS IMPLICIT]`. */
253
+ export interface CastNode {
254
+ statement: "cast";
255
+ source: Span;
256
+ target: Span;
257
+ method: "function" | "binary" | "inout";
258
+ function?: NameNode;
259
+ /** The function's parameter types as written, parentheses excluded. */
260
+ functionArgs?: Span;
261
+ context: "explicit" | "assignment" | "implicit";
262
+ }
263
+
264
+ /** `CREATE EVENT TRIGGER name ON event [WHEN TAG IN ('...') [AND ...]] EXECUTE FUNCTION f()`. */
265
+ export interface EventTriggerNode {
266
+ statement: "eventTrigger";
267
+ name: NameNode;
268
+ event: { name: string; span: Span };
269
+ /** `WHEN variable IN (values)`: the variable folded, each value a string. */
270
+ filters: Array<{ variable: string; values: Span[] }>;
271
+ function: NameNode;
272
+ }
273
+
206
274
  export interface DomainNode {
207
275
  statement: "domain";
208
276
  name: NameNode;
@@ -329,8 +397,10 @@ export interface CommentNode {
329
397
  target: NameNode;
330
398
  /** For `COMMENT ON CONSTRAINT c ON [DOMAIN] t` and `COMMENT ON TRIGGER t ON table`: the table or domain. */
331
399
  on?: NameNode;
332
- /** For `COMMENT ON FUNCTION f(...)`: the parameter list, parentheses excluded. */
400
+ /** For `COMMENT ON FUNCTION f(...)` and `AGGREGATE a(...)`: the parameter list, parentheses excluded. */
333
401
  args?: Span;
402
+ /** For `COMMENT ON CAST (source AS target)`: the two types. */
403
+ cast?: { source: Span; target: Span };
334
404
  /** The comment, a string, or undefined for `IS NULL`. */
335
405
  text?: Span;
336
406
  }
@@ -342,6 +412,12 @@ export type StatementNode =
342
412
  | SequenceNode
343
413
  | SchemaNode
344
414
  | EnumNode
415
+ | CompositeNode
416
+ | RangeNode
417
+ | AggregateNode
418
+ | CollationNode
419
+ | CastNode
420
+ | EventTriggerNode
345
421
  | DomainNode
346
422
  | ExtensionNode
347
423
  | RoutineNode
@@ -396,6 +472,37 @@ const LIKE_OPTIONS = ["COMMENTS", "COMPRESSION", "CONSTRAINTS", "DEFAULTS", "GEN
396
472
  */
397
473
  const NOT_IN_B_EXPR = ["IN", "AND", "OR", "LIKE", "ILIKE", "SIMILAR", "BETWEEN", "ISNULL", "NOTNULL", "AT", "COLLATE", "OVERLAPS"];
398
474
 
475
+ /** `CREATE TYPE ... AS RANGE`'s options (sql-createtype). */
476
+ export const RANGE_OPTIONS = ["subtype", "subtype_opclass", "collation", "canonical", "subtype_diff", "multirange_type_name"];
477
+
478
+ /** `CREATE AGGREGATE`'s options (sql-createaggregate), the old form's `basetype` aside. */
479
+ export const AGGREGATE_OPTIONS = [
480
+ "sfunc",
481
+ "stype",
482
+ "sspace",
483
+ "finalfunc",
484
+ "finalfunc_extra",
485
+ "finalfunc_modify",
486
+ "combinefunc",
487
+ "serialfunc",
488
+ "deserialfunc",
489
+ "initcond",
490
+ "msfunc",
491
+ "minvfunc",
492
+ "mstype",
493
+ "msspace",
494
+ "mfinalfunc",
495
+ "mfinalfunc_extra",
496
+ "mfinalfunc_modify",
497
+ "minitcond",
498
+ "sortop",
499
+ "parallel",
500
+ "hypothetical",
501
+ ];
502
+
503
+ /** `CREATE COLLATION`'s options (sql-createcollation). */
504
+ export const COLLATION_OPTIONS = ["locale", "lc_collate", "lc_ctype", "provider", "deterministic", "rules", "version"];
505
+
399
506
  /** What may follow `IS` inside a `b_expr`. */
400
507
  const B_EXPR_IS = [["DISTINCT", "FROM"], ["NOT", "DISTINCT", "FROM"], ["DOCUMENT"], ["NOT", "DOCUMENT"]];
401
508
 
@@ -894,13 +1001,17 @@ class PgParser extends SqlCursor {
894
1001
  if (!orReplace && kw(this.peek(), "USER", "GROUP")) this.fail("expected ROLE (declare a role with CREATE ROLE; LOGIN is an option of it)");
895
1002
  if (this.accept("FUNCTION")) return this.routine("function", orReplace);
896
1003
  if (this.accept("PROCEDURE")) return this.routine("procedure", orReplace);
1004
+ if (this.accept("AGGREGATE")) return this.aggregate(orReplace);
897
1005
  if (this.accept("TRIGGER")) return this.trigger(orReplace, false);
898
1006
  if (this.acceptSeq("CONSTRAINT", "TRIGGER")) return this.trigger(orReplace, true);
899
1007
  if (!orReplace) {
900
1008
  if (this.accept("SCHEMA")) return this.schema();
901
1009
  if (this.accept("EXTENSION")) return this.extension();
902
1010
  if (this.accept("DOMAIN")) return this.domain();
903
- if (this.accept("TYPE")) return this.enumType();
1011
+ if (this.accept("TYPE")) return this.typeDef();
1012
+ if (this.accept("COLLATION")) return this.collation();
1013
+ if (this.accept("CAST")) return this.cast();
1014
+ if (this.acceptSeq("EVENT", "TRIGGER")) return this.eventTrigger();
904
1015
  if (kw(this.peek(), "UNIQUE", "INDEX")) return this.index();
905
1016
  }
906
1017
  const persistence = this.persistence();
@@ -910,7 +1021,7 @@ class PgParser extends SqlCursor {
910
1021
  const recursive = this.accept("RECURSIVE");
911
1022
  if (!orReplace && !recursive && persistence === undefined && this.acceptSeq("MATERIALIZED", "VIEW")) return this.view(true, false, false, false);
912
1023
  if (this.accept("VIEW")) return this.view(false, orReplace, persistence === "temporary", recursive);
913
- return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, TRIGGER, POLICY or ROLE");
1024
+ return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, TRIGGER, EVENT TRIGGER, COLLATION, CAST, POLICY or ROLE");
914
1025
  }
915
1026
 
916
1027
  private schema(): SchemaNode {
@@ -963,10 +1074,20 @@ class PgParser extends SqlCursor {
963
1074
  }
964
1075
  }
965
1076
 
966
- private enumType(): EnumNode {
1077
+ /** `CREATE TYPE name AS ENUM (...)`, `AS (...)` (a composite type) or `AS RANGE (...)`. */
1078
+ private typeDef(): EnumNode | CompositeNode | RangeNode {
967
1079
  const name = this.qualifiedName("a type name");
968
- this.expect("AS");
969
- if (!this.accept("ENUM")) this.fail("expected ENUM (the type tag declares enum types; a domain has its own tag)");
1080
+ if (!this.accept("AS")) {
1081
+ if (this.isPunct("(")) this.fail("expected AS (a base type, CREATE TYPE name (INPUT = ...), is written in C and is not declared here; declare ENUM, RANGE or composite types, or a domain with its own tag)");
1082
+ this.fail("expected AS (a shell type, CREATE TYPE name with nothing after it, is not declared on its own)");
1083
+ }
1084
+ if (this.accept("RANGE")) {
1085
+ const options = this.definition("a range option");
1086
+ this.checkDefinition(options, RANGE_OPTIONS, ["subtype"], "a range type");
1087
+ return { statement: "range", name, options };
1088
+ }
1089
+ if (this.isPunct("(")) return this.composite(name);
1090
+ if (!this.accept("ENUM")) this.fail("expected ENUM, RANGE or '(' (a domain has its own tag)");
970
1091
  const labels = this.parenList();
971
1092
  for (const l of labels) {
972
1093
  const sig = this.tokens.slice(l.from, l.to).filter((t) => t.kind !== "ws" && t.kind !== "comment");
@@ -975,6 +1096,149 @@ class PgParser extends SqlCursor {
975
1096
  return { statement: "enum", name, labels };
976
1097
  }
977
1098
 
1099
+ /** A composite type's attribute list, after `AS`. */
1100
+ private composite(name: NameNode): CompositeNode {
1101
+ const node: CompositeNode = { statement: "composite", name, attributes: [] };
1102
+ this.expectPunct("(");
1103
+ if (this.acceptPunct(")")) return node;
1104
+ for (;;) {
1105
+ const { text, span } = this.nameTok("an attribute name");
1106
+ const attr: CompositeNode["attributes"][number] = { name: text, nameSpan: span, type: this.typeName() };
1107
+ if (this.accept("COLLATE")) attr.collate = this.qualifiedName("a collation").span;
1108
+ node.attributes.push(attr);
1109
+ if (this.acceptPunct(",")) continue;
1110
+ if (!this.isPunct(")")) this.fail("expected COLLATE, ',' or ')' (an attribute of a composite type has a name, a type and a collation, and no constraints)");
1111
+ this.expectPunct(")");
1112
+ return node;
1113
+ }
1114
+ }
1115
+
1116
+ /**
1117
+ * `( name [= value] [, ...] )`: a definition list, as `CREATE AGGREGATE`,
1118
+ * `CREATE COLLATION` and `CREATE TYPE ... AS RANGE` take it. A value is an
1119
+ * expression span: a type, a function or operator name, a string, a number.
1120
+ */
1121
+ private definition(what: string): DefElem[] {
1122
+ this.expectPunct("(");
1123
+ const out: DefElem[] = [];
1124
+ if (this.acceptPunct(")")) return out;
1125
+ for (;;) {
1126
+ const from = this.idx();
1127
+ const t = this.next();
1128
+ if (t.kind !== "ident" && t.kind !== "qident") this.fail(`expected ${what}`, t);
1129
+ const elem: DefElem = { name: identValue(t), nameSpan: this.span(from, []) };
1130
+ if (this.isOp("=")) {
1131
+ this.p++;
1132
+ elem.value = this.expr();
1133
+ }
1134
+ out.push(elem);
1135
+ if (this.acceptPunct(",")) continue;
1136
+ this.expectPunct(")");
1137
+ return out;
1138
+ }
1139
+ }
1140
+
1141
+ /** A definition list's names against the ones the statement takes: an unknown or repeated one fails at its name, a missing required one at the end. */
1142
+ private checkDefinition(elems: readonly DefElem[], known: readonly string[], required: readonly string[], what: string): void {
1143
+ const seen = new Set<string>();
1144
+ for (const e of elems) {
1145
+ const at = this.tokens[e.nameSpan.from];
1146
+ if (!known.includes(e.name)) this.fail(`expected an option of ${what} (${known.join(", ")})`, at);
1147
+ if (seen.has(e.name)) this.fail(`${e.name} is given twice`, at);
1148
+ seen.add(e.name);
1149
+ }
1150
+ for (const r of required) if (!seen.has(r)) this.fail(`expected ${r.toUpperCase()} (${what} needs it)`, this.lastSeen());
1151
+ }
1152
+
1153
+ private aggregate(orReplace: boolean): AggregateNode {
1154
+ const name = this.qualifiedName("an aggregate name");
1155
+ const node: AggregateNode = { statement: "aggregate", orReplace, name, args: [], star: false, options: [] };
1156
+ this.expectPunct("(");
1157
+ const t = this.peek();
1158
+ if ((t?.kind === "ident" || t?.kind === "qident") && this.isOp("=", 1)) {
1159
+ this.fail("expected the argument types (the old form, CREATE AGGREGATE name (BASETYPE = ..., SFUNC = ...), is not declared; write name(argtype) (SFUNC = ..., STYPE = ...))");
1160
+ }
1161
+ const args = (into: RoutineArgNode[]) => {
1162
+ for (;;) {
1163
+ const at = this.peek();
1164
+ const arg = this.routineArg();
1165
+ if (arg.mode === "OUT" || arg.mode === "INOUT") this.fail("expected IN or VARIADIC (an aggregate's arguments are inputs)", at);
1166
+ if (arg.default) this.fail("expected ',' or ')' (an aggregate's argument takes no default)", this.lastSeen());
1167
+ into.push(arg);
1168
+ if (!this.acceptPunct(",")) return;
1169
+ }
1170
+ };
1171
+ if (this.isOp("*")) {
1172
+ this.p++;
1173
+ node.star = true;
1174
+ } else if (!this.is("ORDER", "BY")) args(node.args);
1175
+ if (!node.star && this.acceptSeq("ORDER", "BY")) {
1176
+ node.orderBy = [];
1177
+ args(node.orderBy);
1178
+ }
1179
+ this.expectPunct(")");
1180
+ node.options = this.definition("an aggregate option");
1181
+ this.checkDefinition(node.options, AGGREGATE_OPTIONS, ["sfunc", "stype"], "an aggregate");
1182
+ return node;
1183
+ }
1184
+
1185
+ private collation(): CollationNode {
1186
+ const ifNotExists = this.ifNotExists();
1187
+ const name = this.qualifiedName("a collation name");
1188
+ if (this.accept("FROM")) {
1189
+ this.fail("expected '(' (CREATE COLLATION ... FROM copies another collation's settings, which the server keeps as the copy's own; write them: (provider = ..., locale = ...))", this.lastSeen());
1190
+ }
1191
+ const options = this.definition("a collation option");
1192
+ this.checkDefinition(options, COLLATION_OPTIONS, [], "a collation");
1193
+ return { statement: "collation", ifNotExists, name, options };
1194
+ }
1195
+
1196
+ private cast(): CastNode {
1197
+ this.expectPunct("(");
1198
+ const source = this.typeName();
1199
+ this.expect("AS");
1200
+ const target = this.typeName();
1201
+ this.expectPunct(")");
1202
+ const node: CastNode = { statement: "cast", source, target, method: "function", context: "explicit" };
1203
+ if (this.acceptSeq("WITH", "FUNCTION")) {
1204
+ node.function = this.qualifiedName("a cast function");
1205
+ if (this.isPunct("(")) node.functionArgs = this.parenthesized();
1206
+ } else if (this.acceptSeq("WITHOUT", "FUNCTION")) node.method = "binary";
1207
+ else if (this.acceptSeq("WITH", "INOUT")) node.method = "inout";
1208
+ else this.fail("expected WITH FUNCTION, WITHOUT FUNCTION or WITH INOUT");
1209
+ if (this.acceptSeq("AS", "ASSIGNMENT")) node.context = "assignment";
1210
+ else if (this.acceptSeq("AS", "IMPLICIT")) node.context = "implicit";
1211
+ return node;
1212
+ }
1213
+
1214
+ private eventTrigger(): EventTriggerNode {
1215
+ const { text, span } = this.nameTok("an event trigger name");
1216
+ if (this.isPunct(".")) this.fail("expected ON (an event trigger's name is not schema-qualified; it is the database's)");
1217
+ this.expect("ON");
1218
+ const event = this.nameTok("an event");
1219
+ const node: EventTriggerNode = { statement: "eventTrigger", name: { pieces: [text], span }, event: { name: event.text, span: event.span }, filters: [], function: { pieces: [], span: { from: 0, to: 0, refs: [] } } };
1220
+ if (this.accept("WHEN")) {
1221
+ do {
1222
+ const variable = this.nameTok("a filter variable");
1223
+ if (variable.text !== "tag") this.fail("expected TAG (the only filter variable an event trigger has)", this.tokens[variable.span.from]);
1224
+ this.expect("IN");
1225
+ const values = this.parenList();
1226
+ for (const v of values) {
1227
+ const sig = this.tokens.slice(v.from, v.to).filter((t) => t.kind !== "ws" && t.kind !== "comment");
1228
+ if (sig.length !== 1 || (sig[0]!.kind !== "string" && sig[0]!.kind !== "ref")) this.fail("expected a command tag as a string ('CREATE TABLE')", sig[0]);
1229
+ }
1230
+ node.filters.push({ variable: variable.text, values });
1231
+ } while (this.accept("AND"));
1232
+ }
1233
+ this.expect("EXECUTE");
1234
+ this.expect("FUNCTION", "PROCEDURE");
1235
+ node.function = this.qualifiedName("an event trigger function");
1236
+ this.expectPunct("(");
1237
+ if (!this.isPunct(")")) this.fail("expected ')' (an event trigger's function takes no arguments)");
1238
+ this.expectPunct(")");
1239
+ return node;
1240
+ }
1241
+
978
1242
  private table(persistence: TableNode["persistence"]): TableNode {
979
1243
  const ifNotExists = this.ifNotExists();
980
1244
  const node: TableNode = { statement: "table", persistence, ifNotExists, name: this.qualifiedName("a table name"), columns: [], constraints: [], like: [] };
@@ -1570,26 +1834,52 @@ class PgParser extends SqlCursor {
1570
1834
  } else if (this.accept("ROLE")) {
1571
1835
  objectType = "ROLE";
1572
1836
  target = this.qualifiedName("a role name");
1837
+ } else if (this.acceptSeq("EVENT", "TRIGGER")) {
1838
+ objectType = "EVENT TRIGGER";
1839
+ target = this.qualifiedName("an event trigger name");
1840
+ } else if (this.accept("CAST")) {
1841
+ objectType = "CAST";
1842
+ const from = this.idx();
1843
+ this.expectPunct("(");
1844
+ const source = this.typeName();
1845
+ this.expect("AS");
1846
+ const castTarget = this.typeName();
1847
+ this.expectPunct(")");
1848
+ target = { pieces: [], span: this.span(from, [...source.refs, ...castTarget.refs]) };
1849
+ this.expect("IS");
1850
+ const node: CommentNode = { statement: "comment", objectType, target, cast: { source, target: castTarget } };
1851
+ if (!this.accept("NULL")) node.text = this.commentText();
1852
+ return node;
1853
+ } else if (this.accept("AGGREGATE", "COLLATION")) {
1854
+ objectType = this.lastSeen()!.text.toUpperCase();
1855
+ target = this.qualifiedName(`an ${objectType.toLowerCase()} name`);
1856
+ if (objectType === "AGGREGATE") {
1857
+ if (!this.isPunct("(")) this.fail("expected '(' and the aggregate's argument types");
1858
+ args = this.parenthesized();
1859
+ }
1573
1860
  } else {
1574
1861
  if (this.acceptSeq("MATERIALIZED", "VIEW")) objectType = "MATERIALIZED VIEW";
1575
1862
  else if (this.accept("TABLE", "COLUMN", "INDEX", "VIEW", "SEQUENCE", "TYPE", "DOMAIN", "SCHEMA", "EXTENSION", "FUNCTION", "PROCEDURE")) {
1576
1863
  objectType = this.tokens[this.idx(-1)]!.text.toUpperCase();
1577
- } else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, TRIGGER, POLICY, ROLE or CONSTRAINT");
1864
+ } else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, COLLATION, CAST, TRIGGER, EVENT TRIGGER, POLICY, ROLE or CONSTRAINT");
1578
1865
  target = this.qualifiedName("an object name");
1579
1866
  if ((objectType === "FUNCTION" || objectType === "PROCEDURE") && this.isPunct("(")) args = this.parenthesized();
1580
1867
  }
1581
1868
  this.expect("IS");
1582
1869
  const node: CommentNode = { statement: "comment", objectType, target, on, ...(args ? { args } : {}) };
1583
- if (!this.accept("NULL")) {
1584
- const t = this.peek();
1585
- if (t?.kind !== "string" && t?.kind !== "ref") this.fail("expected a string or NULL");
1586
- const from = this.idx();
1587
- this.p++;
1588
- node.text = this.span(from, t.kind === "ref" ? [t.part] : []);
1589
- }
1870
+ if (!this.accept("NULL")) node.text = this.commentText();
1590
1871
  return node;
1591
1872
  }
1592
1873
 
1874
+ /** A comment's text: a string, or an interpolation. */
1875
+ private commentText(): Span {
1876
+ const t = this.peek();
1877
+ if (t?.kind !== "string" && t?.kind !== "ref") this.fail("expected a string or NULL");
1878
+ const from = this.idx();
1879
+ this.p++;
1880
+ return this.span(from, t.kind === "ref" ? [t.part] : []);
1881
+ }
1882
+
1593
1883
  /** Statements separated by `;`, to the end. */
1594
1884
  parseMany(): StatementNode[] {
1595
1885
  const out: StatementNode[] = [];
@@ -36,7 +36,13 @@ const TAG: Record<string, (s: TemplateStringsArray) => { props: object }> = {
36
36
  [POSTGRES_ENTITY_TYPES.materializedView]: tags.view,
37
37
  [POSTGRES_ENTITY_TYPES.sequence]: tags.sequence,
38
38
  [POSTGRES_ENTITY_TYPES.enum]: tags.type,
39
+ [POSTGRES_ENTITY_TYPES.composite]: tags.type,
40
+ [POSTGRES_ENTITY_TYPES.range]: tags.type,
39
41
  [POSTGRES_ENTITY_TYPES.domain]: tags.domain,
42
+ [POSTGRES_ENTITY_TYPES.aggregate]: tags.aggregate,
43
+ [POSTGRES_ENTITY_TYPES.collation]: tags.collation,
44
+ [POSTGRES_ENTITY_TYPES.cast]: tags.cast,
45
+ [POSTGRES_ENTITY_TYPES.eventTrigger]: tags.eventTrigger,
40
46
  [POSTGRES_ENTITY_TYPES.extension]: tags.extension,
41
47
  [POSTGRES_ENTITY_TYPES.function]: tags.func,
42
48
  [POSTGRES_ENTITY_TYPES.procedure]: tags.procedure,
@@ -119,6 +125,18 @@ const PROP_OF_FIELD: Record<string, string[]> = {
119
125
  connectionLimit: ["connectionLimit"],
120
126
  rowSecurity: ["rowSecurity"],
121
127
  forceRowSecurity: ["forceRowSecurity"],
128
+ attributeDefs: ["attributes"],
129
+ subtype: ["subtype"],
130
+ subtypeOpclass: ["subtypeOpclass"],
131
+ collation: ["collation"],
132
+ canonical: ["canonical"],
133
+ subtypeDiff: ["subtypeDiff"],
134
+ multirange: ["multirangeTypeName"],
135
+ definition: ["options"],
136
+ castMethod: ["method", "functionArgs"],
137
+ context: ["context"],
138
+ event: ["event"],
139
+ tags: ["tags"],
122
140
  };
123
141
 
124
142
  /** Props keys that hold constraints, by constraint kind. */
@@ -139,6 +157,10 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
139
157
  };
140
158
  if (d.name === l.name) adopt("name");
141
159
  if (d.schema === l.schema) adopt("schema");
160
+ // An aggregate's argument types and a cast's two types are its identity: the same, they are the declaration's.
161
+ if (d.kind === "aggregate" && d.signature === l.signature) for (const key of ["args", "orderBy"]) adopt(key);
162
+ if (d.kind === "cast" && d.name === l.name) for (const key of ["sourceType", "targetType"]) adopt(key);
163
+
142
164
  const fieldKeys = new Set([...Object.keys(d.fields), ...Object.keys(l.fields)]);
143
165
  for (const f of fieldKeys) {
144
166
  if (f === "schema" && d.kind !== "extension") continue;
@@ -170,6 +192,17 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
170
192
  ...read.filter((c) => c.at === undefined),
171
193
  ].map((c) => c.value);
172
194
  }
195
+ // An aggregate's or a collation's `options` is the declaration's only when every field read from it is the same (a role's `options` shares the key).
196
+ const optionFields = d.kind === "aggregate" ? ["definition"] : d.kind === "collation" ? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"] : undefined;
197
+ if (optionFields) {
198
+ if (optionFields.every((f) => JSON.stringify(d.fields[f]) === JSON.stringify(l.fields[f]))) adopt("options");
199
+ else out.options = live.options;
200
+ }
201
+ // A cast's `method` shares its key with an index's.
202
+ if (d.kind === "cast" && JSON.stringify(d.fields.castMethod) !== JSON.stringify(l.fields.castMethod)) {
203
+ out.method = live.method;
204
+ out.functionArgs = live.functionArgs;
205
+ }
173
206
  if (d.kind === "table" || d.kind === "domain") {
174
207
  // A constraint the declaration says the same as the server is the declaration's (its name included or left out).
175
208
  const kinds = d.kind === "domain" ? ["CHECK"] : Object.keys(CONSTRAINT_PROPS);
@@ -214,6 +214,42 @@ function diffUnparsed(key: string, before: PgDiffObject, after: PgDiffObject, ou
214
214
  }
215
215
  }
216
216
 
217
+ /** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
218
+ const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger"]);
219
+
220
+ /** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
221
+ const DEFINITION: Readonly<Record<string, readonly string[]>> = {
222
+ composite: ["attributeDefs"],
223
+ range: ["subtype", "subtypeOpclass", "collation", "canonical", "subtypeDiff", "multirange"],
224
+ aggregate: ["definition"],
225
+ collation: ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"],
226
+ cast: ["castMethod", "function", "context"],
227
+ eventTrigger: ["event", "tags", "function"],
228
+ };
229
+
230
+ /**
231
+ * A type's, aggregate's, collation's, cast's or event trigger's change: its
232
+ * comment in place; its definition by CREATE OR REPLACE AGGREGATE where that
233
+ * keeps the aggregate's result, else dropped and created (SQLPG300), or
234
+ * refused while something depends on it (SQLPG301).
235
+ */
236
+ function diffReplaced(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
237
+ if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
238
+ const changed = DEFINITION[after.kind]!.filter((k) => !same(before.fields[k], after.fields[k]));
239
+ if (before.schema !== after.schema || before.name !== after.name || before.signature !== after.signature) changed.unshift("name");
240
+ if (changed.length === 0) return;
241
+ const was = changed.map((k) => `${k} ${k === "name" ? qualified(before) : (show(before.fields[k]) ?? "-")}`).join("; ");
242
+ const now = changed.map((k) => `${k} ${k === "name" ? qualified(after) : (show(after.fields[k]) ?? "-")}`).join("; ");
243
+ const bo = (before.fields.definition ?? {}) as Record<string, unknown>;
244
+ const ao = (after.fields.definition ?? {}) as Record<string, unknown>;
245
+ const replaceable = after.kind === "aggregate" && !changed.includes("name") && same(bo.stype, ao.stype) && same(bo.finalfunc, ao.finalfunc) && same(bo.hypothetical, ao.hypothetical);
246
+ const what = changed.join(", ");
247
+ if (replaceable) out.push(change(key, what, "SQLPG302", was, now, { note: "CREATE OR REPLACE AGGREGATE" }));
248
+ else if (before.dependents && before.dependents.length > 0) {
249
+ out.push(change(key, what, "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
250
+ } else out.push(change(key, what, "SQLPG300", was, now));
251
+ }
252
+
217
253
  function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[], hints: string[], major: number): void {
218
254
  diffUnparsed(key, before, after, out, hints);
219
255
  if (before.kind !== after.kind) {
@@ -225,6 +261,15 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
225
261
  const [b, a] = f(name);
226
262
  if (!same(b, a)) out.push(change(key, name, rule, show(b), show(a), extra));
227
263
  };
264
+ if (REPLACED_KINDS.has(after.kind)) {
265
+ // A cast is named by its types, and an aggregate's argument types are its identity: either changed, between two builds, is another object.
266
+ if (after.kind === "cast" && before.name !== after.name) {
267
+ out.push(change(key, "name", before.dependents?.length ? "SQLPG301" : "SQLPG300", before.name, after.name));
268
+ return;
269
+ }
270
+ diffReplaced(key, before, after, out);
271
+ return;
272
+ }
228
273
  if (before.schema !== after.schema || before.name !== after.name) {
229
274
  out.push(change(key, "name", after.kind === "index" ? "SQLPG229" : "SQLPG228", qualified(before), qualified(after)));
230
275
  }