@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.
- 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/import/live-export.d.ts +8 -0
- package/dist/clickhouse/import/live-export.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/live/undeclarable.d.ts +32 -0
- package/dist/clickhouse/live/undeclarable.d.ts.map +1 -0
- 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/testing/fake-server.d.ts +6 -0
- package/dist/clickhouse/testing/fake-server.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/coverage-page.d.ts +7 -0
- package/dist/codegen/coverage-page.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/core/undeclarable.d.ts +38 -0
- package/dist/core/undeclarable.d.ts.map +1 -0
- package/dist/coverage/clickhouse-commands.json +24 -0
- package/dist/coverage/clickhouse.json +25 -0
- package/dist/coverage/postgres-commands.json +245 -0
- package/dist/coverage/postgres.json +51 -0
- package/dist/coverage.d.ts +68 -0
- package/dist/coverage.d.ts.map +1 -1
- package/dist/files/clickhouse.d.ts +2 -1
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/common.d.ts +12 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/postgres.d.ts +1 -1
- package/dist/files/postgres.d.ts.map +1 -1
- package/dist/import-parser.d.ts +7 -2
- package/dist/import-parser.d.ts.map +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/import/live-export.d.ts +8 -0
- package/dist/postgres/import/live-export.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 +23 -8
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/undeclarable.d.ts +42 -0
- package/dist/postgres/live/undeclarable.d.ts.map +1 -0
- 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/fake-client.d.ts +6 -0
- package/dist/postgres/testing/fake-client.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/import/live-export.ts +17 -2
- 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 +65 -0
- package/src/clickhouse/live/undeclarable.ts +66 -0
- 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/testing/fake-server.ts +5 -0
- package/src/clickhouse/topology.ts +4 -2
- package/src/clickhouse.ts +6 -0
- package/src/codegen/classifier-page.test.ts +6 -0
- package/src/codegen/coverage-page.ts +77 -0
- package/src/codegen/docs.ts +5 -0
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +8 -0
- package/src/core/undeclarable.test.ts +141 -0
- package/src/core/undeclarable.ts +66 -0
- package/src/coverage/clickhouse-commands.json +24 -0
- package/src/coverage/clickhouse.json +25 -0
- package/src/coverage/postgres-commands.json +245 -0
- package/src/coverage/postgres.json +51 -0
- package/src/coverage.test.ts +78 -0
- package/src/coverage.ts +142 -0
- package/src/files/clickhouse.ts +20 -7
- package/src/files/common.ts +18 -0
- package/src/files/postgres.ts +25 -6
- package/src/generated/lexicon-sql.json +40 -0
- package/src/import-parser.ts +12 -4
- 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 +13 -2
- 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/import/live-export.ts +25 -2
- package/src/postgres/kinds.test.ts +140 -0
- package/src/postgres/live/catalog-kinds.ts +312 -0
- package/src/postgres/live/catalog.ts +33 -11
- 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 +83 -0
- package/src/postgres/live/undeclarable.ts +141 -0
- 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/fake-client.ts +5 -0
- 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/files/postgres.ts
CHANGED
|
@@ -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 (
|
|
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",
|
package/src/import-parser.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
|
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. */
|
|
@@ -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> = {
|
|
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
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
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 (
|
|
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);
|
package/src/mcp/postgres.ts
CHANGED
|
@@ -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
|
|
104
|
-
* a routine is named with its
|
|
105
|
-
*
|
|
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 === "
|
|
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 =
|
|
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
|
|