@intentius/chant-lexicon-sql 0.124.19 → 0.124.22
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.
- package/dist/clickhouse/access.d.ts +36 -7
- package/dist/clickhouse/access.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/entities.d.ts +60 -0
- package/dist/clickhouse/entities.d.ts.map +1 -1
- package/dist/clickhouse/import/generator.d.ts +1 -7
- package/dist/clickhouse/import/generator.d.ts.map +1 -1
- package/dist/clickhouse/live/catalog.d.ts +6 -5
- package/dist/clickhouse/live/catalog.d.ts.map +1 -1
- package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
- package/dist/clickhouse/parser.d.ts +16 -0
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/plan/deep.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/normalize.d.ts +6 -3
- package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.d.ts +3 -0
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/topology.d.ts.map +1 -1
- package/dist/clickhouse.d.ts +1 -1
- package/dist/clickhouse.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/coverage/clickhouse.json +8 -8
- package/dist/coverage/postgres.json +6 -6
- package/dist/files/clickhouse.d.ts +2 -1
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/postgres.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +10 -10
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
- package/dist/lint/post-synth/sql101.d.ts.map +1 -1
- package/dist/lint/rules/sqlch001.d.ts +1 -1
- package/dist/lint/rules/sqlch001.d.ts.map +1 -1
- package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
- package/dist/lint/rules/templates.d.ts +1 -1
- package/dist/lint/rules/templates.d.ts.map +1 -1
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/meta.json +40 -0
- package/dist/okf/index.md +9 -1
- package/dist/okf/rules/SQL101.md +1 -0
- package/dist/okf/rules/SQLCH001.md +2 -2
- package/dist/okf/rules/SQLPG113.md +2 -0
- package/dist/okf/types/PostgresAggregate.md +13 -0
- package/dist/okf/types/PostgresCast.md +9 -0
- package/dist/okf/types/PostgresCollation.md +9 -0
- package/dist/okf/types/PostgresCompositeType.md +13 -0
- package/dist/okf/types/PostgresEventTrigger.md +9 -0
- package/dist/okf/types/PostgresRangeType.md +13 -0
- package/dist/okf/types/Quota.md +9 -0
- package/dist/okf/types/SettingsProfile.md +9 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/postgres/apply/apply.d.ts.map +1 -1
- package/dist/postgres/apply/statements.d.ts +4 -3
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +129 -6
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entity-types.d.ts +6 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/import/generator.d.ts +3 -2
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.d.ts +28 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -0
- package/dist/postgres/live/catalog.d.ts +15 -11
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/parser.d.ts +88 -5
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.d.ts +17 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/rules.d.ts +3 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/schema.d.ts +3 -2
- package/dist/postgres/plan/schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/rules/postgres-names.ts +15 -2
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sql101.ts +1 -0
- package/dist/rules/sqlch001.ts +10 -3
- package/dist/rules/sqlpg001.ts +6 -21
- package/dist/rules/sqlpg113.ts +1 -1
- package/dist/rules/templates.ts +2 -2
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/access.ts +197 -22
- package/src/clickhouse/apply/statements.ts +17 -5
- package/src/clickhouse/entities.ts +78 -2
- package/src/clickhouse/import/generator.ts +16 -3
- package/src/clickhouse/live/catalog.ts +57 -10
- package/src/clickhouse/live/describe-resources.ts +3 -1
- package/src/clickhouse/live/kinds.e2e.test.ts +179 -0
- package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -4
- package/src/clickhouse/parser.test.ts +45 -2
- package/src/clickhouse/parser.ts +111 -3
- package/src/clickhouse/plan/deep.ts +25 -6
- package/src/clickhouse/plan/diff.ts +41 -1
- package/src/clickhouse/plan/normalize.ts +12 -4
- package/src/clickhouse/plan/rules.ts +22 -1
- package/src/clickhouse/quota-profile.test.ts +186 -0
- package/src/clickhouse/table-function.test.ts +54 -0
- package/src/clickhouse/testdata/server-ddl-baseline.json +17 -14
- package/src/clickhouse/topology.ts +4 -2
- package/src/clickhouse.ts +6 -0
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +8 -0
- package/src/core/undeclarable.test.ts +22 -19
- package/src/coverage/clickhouse.json +8 -8
- package/src/coverage/postgres.json +6 -6
- package/src/files/clickhouse.ts +14 -6
- package/src/files/postgres.ts +19 -5
- package/src/generated/lexicon-sql.json +40 -0
- package/src/index.ts +2 -2
- package/src/lint/post-synth/postgres-names.ts +15 -2
- package/src/lint/post-synth/sql101.ts +1 -0
- package/src/lint/post-synth/sqlpg113.ts +1 -1
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lint/rules/sqlch001.ts +10 -3
- package/src/lint/rules/sqlpg001.ts +6 -21
- package/src/lint/rules/templates.ts +2 -2
- package/src/lsp/postgres-template.ts +14 -0
- package/src/mcp/index.ts +5 -5
- package/src/mcp/postgres.ts +1 -1
- package/src/plugin.ts +7 -1
- package/src/postgres/apply/apply.ts +8 -2
- package/src/postgres/apply/statements.ts +29 -6
- package/src/postgres/entities.test.ts +2 -2
- package/src/postgres/entities.ts +316 -28
- package/src/postgres/entity-types.ts +6 -0
- package/src/postgres/import/generator.ts +31 -4
- package/src/postgres/kinds.test.ts +140 -0
- package/src/postgres/live/catalog-kinds.ts +312 -0
- package/src/postgres/live/catalog.ts +30 -12
- package/src/postgres/live/describe-resources.ts +14 -5
- package/src/postgres/live/kinds.e2e.test.ts +182 -0
- package/src/postgres/live/roundtrip.e2e.test.ts +9 -3
- package/src/postgres/live/undeclarable.e2e.test.ts +15 -13
- package/src/postgres/parser.test.ts +2 -1
- package/src/postgres/parser.ts +307 -17
- package/src/postgres/plan/deep.ts +33 -0
- package/src/postgres/plan/diff.ts +45 -0
- package/src/postgres/plan/normalize.ts +153 -2
- package/src/postgres/plan/rules.ts +21 -0
- package/src/postgres/plan/schema.ts +6 -4
- package/src/postgres/testing/roundtrip-schema.ts +19 -1
- package/src/postgres.ts +19 -0
- package/src/skill-defs.ts +1 -1
- package/src/skills/chant-sql-postgres.md +7 -3
- package/src/skills/chant-sql.md +3 -1
- package/src/unknown-clauses.test.ts +2 -1
- package/src/validate.ts +8 -0
package/src/postgres/parser.ts
CHANGED
|
@@ -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`, `
|
|
5
|
-
* `
|
|
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-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
969
|
-
|
|
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
|
}
|