@intentius/chant-lexicon-sql 0.124.18 → 0.124.19
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/import/live-export.d.ts +8 -0
- package/dist/clickhouse/import/live-export.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/testing/fake-server.d.ts +6 -0
- package/dist/clickhouse/testing/fake-server.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/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.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.map +1 -1
- package/dist/import-parser.d.ts +7 -2
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/plugin.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.d.ts +12 -1
- package/dist/postgres/live/catalog.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/testing/fake-client.d.ts +6 -0
- package/dist/postgres/testing/fake-client.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/import/live-export.ts +17 -2
- package/src/clickhouse/live/undeclarable.e2e.test.ts +64 -0
- package/src/clickhouse/live/undeclarable.ts +66 -0
- package/src/clickhouse/testing/fake-server.ts +5 -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/core/undeclarable.test.ts +138 -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 +6 -1
- package/src/files/common.ts +18 -0
- package/src/files/postgres.ts +6 -1
- package/src/import-parser.ts +12 -4
- package/src/plugin.ts +6 -1
- package/src/postgres/import/live-export.ts +25 -2
- package/src/postgres/live/catalog.ts +7 -3
- package/src/postgres/live/undeclarable.e2e.test.ts +81 -0
- package/src/postgres/live/undeclarable.ts +141 -0
- package/src/postgres/testing/fake-client.ts +5 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import never leaves an object out without a word (#3911): an object of a
|
|
3
|
+
* kind no declaration holds fails the import, which names every one, and
|
|
4
|
+
* `skipUndeclarable` turns the failure into a warning per object. Against
|
|
5
|
+
* fakes; the e2e tests (`../postgres/live/undeclarable.e2e.test.ts`,
|
|
6
|
+
* `../clickhouse/live/undeclarable.e2e.test.ts`) run the same against servers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
|
10
|
+
import { fakeClient, type FakeCatalog } from "../postgres/testing/fake-client";
|
|
11
|
+
import { fakeClickHouse, type FakeServer } from "../clickhouse/testing/fake-server";
|
|
12
|
+
import { exportResources as exportClickHouse } from "../clickhouse/import/live-export";
|
|
13
|
+
import { SqlFileParser } from "../import-parser";
|
|
14
|
+
import { UndeclarableObjectsError } from "./undeclarable";
|
|
15
|
+
|
|
16
|
+
const pg: { catalog: FakeCatalog; provider?: string } = { catalog: {} };
|
|
17
|
+
|
|
18
|
+
vi.mock("../postgres/live/bind", async (orig) => ({
|
|
19
|
+
...(await orig<typeof import("../postgres/live/bind")>()),
|
|
20
|
+
bindPostgres: async () => ({
|
|
21
|
+
target: { endpoint: { url: "postgres://x/db" }, source: "test", defaultSchema: "public", ...(pg.provider ? { provider: pg.provider } : {}) },
|
|
22
|
+
client: fakeClient(pg.catalog),
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const { exportResources: exportPostgres } = await import("../postgres/import/live-export");
|
|
27
|
+
|
|
28
|
+
const CATALOG: FakeCatalog = {
|
|
29
|
+
schemas: [{ name: "app" }],
|
|
30
|
+
tables: [{ schema: "app", name: "users", columns: [{ name: "id", type: "bigint", notnull: true }] }],
|
|
31
|
+
undeclarable: [
|
|
32
|
+
{ kind: "aggregate", schema: "app", name: "total(integer)" },
|
|
33
|
+
{ kind: "cast", name: "(app.pair AS text)" },
|
|
34
|
+
{ kind: "composite type", schema: "app", name: "pair" },
|
|
35
|
+
{ kind: "event trigger", name: "audit_ddl" },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("Postgres live import", () => {
|
|
40
|
+
test("fails naming every object of a kind chant cannot declare, and why", async () => {
|
|
41
|
+
pg.catalog = CATALOG;
|
|
42
|
+
const err = await exportPostgres({ environment: "prod" }).then(
|
|
43
|
+
() => undefined,
|
|
44
|
+
(e: unknown) => e,
|
|
45
|
+
);
|
|
46
|
+
expect(err).toBeInstanceOf(UndeclarableObjectsError);
|
|
47
|
+
const message = (err as Error).message;
|
|
48
|
+
for (const line of [" - aggregate app.total(integer)", " - cast (app.pair AS text)", " - composite type app.pair", " - event trigger audit_ddl"]) {
|
|
49
|
+
expect(message).toContain(line);
|
|
50
|
+
}
|
|
51
|
+
expect(message).toContain(" - aggregate: no tag declares an aggregate yet");
|
|
52
|
+
expect(message).toContain("--skip-undeclarable");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("with skipUndeclarable, imports the rest and warns once per object", async () => {
|
|
56
|
+
pg.catalog = CATALOG;
|
|
57
|
+
const ir = await exportPostgres({ environment: "prod", skipUndeclarable: true });
|
|
58
|
+
expect(ir.resources.map((r) => r.properties.name)).toContain("users");
|
|
59
|
+
const left = (ir.warnings ?? []).filter((w) => w.includes("is left out (--skip-undeclarable)"));
|
|
60
|
+
expect(left).toHaveLength(4);
|
|
61
|
+
expect(left[0]).toBe("aggregate app.total(integer) is left out (--skip-undeclarable): no tag declares an aggregate yet");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("a selector or owned import does not look", async () => {
|
|
65
|
+
pg.catalog = CATALOG;
|
|
66
|
+
await expect(exportPostgres({ environment: "prod", selector: { type: "Postgres::Table" } })).resolves.toBeDefined();
|
|
67
|
+
await expect(exportPostgres({ environment: "prod", owned: true })).resolves.toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("a managed provider's own objects are its, with a warning, not a refusal", async () => {
|
|
71
|
+
pg.catalog = { ...CATALOG, undeclarable: [{ kind: "aggregate", schema: "auth", name: "x(integer)" }] };
|
|
72
|
+
pg.provider = "supabase";
|
|
73
|
+
try {
|
|
74
|
+
const ir = await exportPostgres({ environment: "prod" });
|
|
75
|
+
expect(ir.warnings).toContain("aggregate auth.x(integer) is kept by Supabase; left out");
|
|
76
|
+
} finally {
|
|
77
|
+
pg.provider = undefined;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("ClickHouse live import", () => {
|
|
83
|
+
let server: FakeServer;
|
|
84
|
+
beforeAll(async () => {
|
|
85
|
+
server = await fakeClickHouse(
|
|
86
|
+
[
|
|
87
|
+
{ name: "analytics", engine: "Atomic", statement: "CREATE DATABASE analytics\nENGINE = Atomic" },
|
|
88
|
+
{ database: "analytics", name: "events", engine: "MergeTree", statement: "CREATE TABLE analytics.events\n(\n `id` UInt64\n)\nENGINE = MergeTree\nORDER BY id" },
|
|
89
|
+
{ database: "analytics", name: "wv", engine: "WindowView", statement: "CREATE WINDOW VIEW analytics.wv AS SELECT 1" },
|
|
90
|
+
],
|
|
91
|
+
{
|
|
92
|
+
undeclarable: [
|
|
93
|
+
{ kind: "quota", name: "q1" },
|
|
94
|
+
{ kind: "settings profile", name: "sp1" },
|
|
95
|
+
{ kind: "window view", database: "analytics", name: "wv" },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
afterAll(() => server.close());
|
|
101
|
+
const run = (extra: Record<string, unknown> = {}) => exportClickHouse({ environment: "x", config: {}, env: { CLICKHOUSE_URL: server.url }, ...extra });
|
|
102
|
+
|
|
103
|
+
test("fails naming them", async () => {
|
|
104
|
+
await expect(run()).rejects.toThrow(/ - quota q1\n - settings profile sp1\n - window view analytics\.wv\n/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("with skipUndeclarable, warns, and the window view is not imported as a table", async () => {
|
|
108
|
+
const ir = await run({ skipUndeclarable: true });
|
|
109
|
+
expect(ir.resources.map((r) => r.properties.name)).toEqual(["analytics", "events"]);
|
|
110
|
+
expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(3);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("file import", () => {
|
|
115
|
+
const DDL = `CREATE SCHEMA app;
|
|
116
|
+
CREATE TABLE app.t (id int PRIMARY KEY);
|
|
117
|
+
CREATE AGGREGATE app.total(int) (sfunc = int4pl, stype = int);
|
|
118
|
+
CREATE TYPE app.pair AS (a int, b text);
|
|
119
|
+
CREATE COLLATION app.ci (provider = icu, locale = 'und');`;
|
|
120
|
+
|
|
121
|
+
test("a CREATE of a kind chant cannot declare is an error naming it", () => {
|
|
122
|
+
expect(() => new SqlFileParser("postgres").parse(DDL)).toThrow(/aggregate: chant cannot declare it \(no tag declares an aggregate yet\): CREATE AGGREGATE app\.total/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("skipUndeclarable imports the rest and lists them as warnings", () => {
|
|
126
|
+
const ir = new SqlFileParser("postgres", { skipUndeclarable: true }).parse(DDL);
|
|
127
|
+
expect(ir.resources.map((r) => r.properties.name)).toEqual(["app", "t"]);
|
|
128
|
+
expect(ir.warnings).toEqual([
|
|
129
|
+
"left out (skipUndeclarable): aggregate: chant cannot declare it (no tag declares an aggregate yet): CREATE AGGREGATE app.total(int) (sfunc = int4pl, stype = int)",
|
|
130
|
+
"left out (skipUndeclarable): composite type: chant cannot declare it (no tag declares a composite type yet): CREATE TYPE app.pair AS (a int, b text)",
|
|
131
|
+
"left out (skipUndeclarable): collation: chant cannot declare it (no tag declares a collation yet): CREATE COLLATION app.ci (provider = icu, locale = 'und')",
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("ClickHouse: a quota in a file is refused too", () => {
|
|
136
|
+
expect(() => new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE QUOTA q1")).toThrow(/quota: chant cannot declare it/);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objects an import finds that no declaration can hold (#3911).
|
|
3
|
+
*
|
|
4
|
+
* Import reads every kind of object the server has, not only the kinds chant
|
|
5
|
+
* declares, so none is left out without a word. An object of a kind the
|
|
6
|
+
* coverage table marks refused (`../coverage.ts`) is collected here, and the
|
|
7
|
+
* import fails naming every one of them at once: kind, schema and name.
|
|
8
|
+
* `--skip-undeclarable` imports the rest and lists them as warnings instead,
|
|
9
|
+
* so an adopter is not stuck behind an aggregate they mean to keep by hand.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { kindRow } from "../coverage";
|
|
13
|
+
import type { SqlDialect } from "../dialects";
|
|
14
|
+
|
|
15
|
+
/** One object of a kind chant cannot declare. */
|
|
16
|
+
export interface UndeclarableObject {
|
|
17
|
+
/** The coverage table's kind: `aggregate`, `composite type`. */
|
|
18
|
+
kind: string;
|
|
19
|
+
/** The Postgres schema or ClickHouse database it is in; absent for a database-wide or server-wide object. */
|
|
20
|
+
schema?: string;
|
|
21
|
+
/** Its name, with what identifies it besides: an aggregate's argument types, a rule's table, a cast's types. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** The role or user that owns it, where that tells a managed provider's own object from the database owner's. */
|
|
24
|
+
owner?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `aggregate app.total(integer)`. */
|
|
28
|
+
export const describeUndeclarable = (o: UndeclarableObject): string => `${o.kind} ${o.schema ? `${o.schema}.` : ""}${o.name}`;
|
|
29
|
+
|
|
30
|
+
const DOCS = "https://intentius.io/chant/lexicons/sql/object-coverage/";
|
|
31
|
+
|
|
32
|
+
/** The import's refusal: every undeclarable object, then why for each kind among them. */
|
|
33
|
+
export class UndeclarableObjectsError extends Error {
|
|
34
|
+
constructor(
|
|
35
|
+
readonly dialect: SqlDialect,
|
|
36
|
+
readonly objects: readonly UndeclarableObject[],
|
|
37
|
+
) {
|
|
38
|
+
const kinds = [...new Set(objects.map((o) => o.kind))];
|
|
39
|
+
const n = objects.length;
|
|
40
|
+
super(
|
|
41
|
+
[
|
|
42
|
+
`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} server has ${n === 1 ? "an object" : `${n} objects`} of a kind chant cannot declare, so the import would leave ${n === 1 ? "it" : "them"} out:`,
|
|
43
|
+
...objects.map((o) => ` - ${describeUndeclarable(o)}`),
|
|
44
|
+
"Why, by kind:",
|
|
45
|
+
...kinds.map((k) => ` - ${k}: ${kindRow(dialect, k).reason ?? "not declared"}`),
|
|
46
|
+
`To import everything else and list ${n === 1 ? "it" : "these"} as warnings, run the import again with --skip-undeclarable. Which kinds chant declares: ${DOCS}`,
|
|
47
|
+
].join("\n"),
|
|
48
|
+
);
|
|
49
|
+
this.name = "UndeclarableObjectsError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One warning per object, for `--skip-undeclarable`. */
|
|
54
|
+
export function undeclarableWarnings(dialect: SqlDialect, objects: readonly UndeclarableObject[]): string[] {
|
|
55
|
+
return objects.map((o) => `${describeUndeclarable(o)} is left out (--skip-undeclarable): ${kindRow(dialect, o.kind).reason ?? "chant cannot declare it"}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Refuse the import when it found undeclarable objects, or, with `skip`,
|
|
60
|
+
* add a warning per object to `warnings`. Nothing found is nothing to do.
|
|
61
|
+
*/
|
|
62
|
+
export function refuseUndeclarable(dialect: SqlDialect, objects: readonly UndeclarableObject[], skip: boolean | undefined, warnings: string[]): void {
|
|
63
|
+
if (objects.length === 0) return;
|
|
64
|
+
if (!skip) throw new UndeclarableObjectsError(dialect, objects);
|
|
65
|
+
warnings.push(...undeclarableWarnings(dialect, objects));
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "src/Parsers/ParserCreate*.h and src/Parsers/Access/ParserCreate*.h, at the pinned release's tag",
|
|
3
|
+
"version": "26.8.15.10",
|
|
4
|
+
"tag": "v26.8.15.10-lts",
|
|
5
|
+
"commands": [
|
|
6
|
+
"CREATE DATABASE",
|
|
7
|
+
"CREATE DICTIONARY",
|
|
8
|
+
"CREATE FUNCTION",
|
|
9
|
+
"CREATE HANDLER",
|
|
10
|
+
"CREATE INDEX",
|
|
11
|
+
"CREATE MASKING POLICY",
|
|
12
|
+
"CREATE NAMED COLLECTION",
|
|
13
|
+
"CREATE QUOTA",
|
|
14
|
+
"CREATE RESOURCE",
|
|
15
|
+
"CREATE ROLE",
|
|
16
|
+
"CREATE ROW POLICY",
|
|
17
|
+
"CREATE SETTINGS PROFILE",
|
|
18
|
+
"CREATE TABLE",
|
|
19
|
+
"CREATE USER",
|
|
20
|
+
"CREATE VIEW",
|
|
21
|
+
"CREATE WINDOW VIEW",
|
|
22
|
+
"CREATE WORKLOAD"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dialect": "clickhouse",
|
|
3
|
+
"kinds": [
|
|
4
|
+
{ "command": "CREATE DATABASE", "kind": "database", "status": "declared", "entity": "ClickHouse::Database" },
|
|
5
|
+
{ "command": "CREATE DICTIONARY", "kind": "dictionary", "status": "declared", "entity": "ClickHouse::Dictionary" },
|
|
6
|
+
{ "command": "CREATE FUNCTION", "kind": "SQL function", "status": "declared", "entity": "ClickHouse::Function" },
|
|
7
|
+
{ "command": "CREATE FUNCTION", "form": "LANGUAGE WASM, or with a driver", "match": "\\bLANGUAGE\\b|\\bUSING\\s+DRIVER\\b", "kind": "non-SQL function", "status": "refused", "reason": "only a SQL function (CREATE FUNCTION f AS (x) -> ...) is declared" },
|
|
8
|
+
{ "command": "CREATE HANDLER", "kind": "HTTP handler", "status": "refused", "reason": "no declaration holds an HTTP handler yet" },
|
|
9
|
+
{ "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "ClickHouse::Table", "note": "an index is part of its table: SHOW CREATE TABLE prints it, and the table's declaration holds it" },
|
|
10
|
+
{ "command": "CREATE MASKING POLICY", "kind": "masking policy", "status": "refused", "reason": "no declaration holds a masking policy yet" },
|
|
11
|
+
{ "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "refused", "reason": "no declaration holds a named collection yet; one usually carries credentials" },
|
|
12
|
+
{ "command": "CREATE QUOTA", "kind": "quota", "status": "refused", "reason": "no declaration holds a quota yet" },
|
|
13
|
+
{ "command": "CREATE RESOURCE", "kind": "workload resource", "status": "refused", "reason": "no declaration holds a workload scheduling resource yet" },
|
|
14
|
+
{ "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "ClickHouse::Role", "note": "import reads only the access objects a build declares" },
|
|
15
|
+
{ "command": "CREATE ROW POLICY", "kind": "row policy", "status": "declared", "entity": "ClickHouse::RowPolicy", "note": "import reads only the access objects a build declares" },
|
|
16
|
+
{ "command": "CREATE SETTINGS PROFILE", "kind": "settings profile", "status": "refused", "reason": "no declaration holds a settings profile yet" },
|
|
17
|
+
{ "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "ClickHouse::Table" },
|
|
18
|
+
{ "command": "CREATE TABLE", "form": "TEMPORARY", "match": "^CREATE\\s+TEMPORARY\\b", "kind": "temporary table", "status": "out-of-scope", "reason": "a temporary table lives in one session" },
|
|
19
|
+
{ "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "ClickHouse::User", "note": "import reads only the access objects a build declares" },
|
|
20
|
+
{ "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "ClickHouse::View" },
|
|
21
|
+
{ "command": "CREATE VIEW", "form": "MATERIALIZED", "match": "^CREATE\\s+(?:OR\\s+REPLACE\\s+)?MATERIALIZED\\b", "kind": "materialized view", "status": "declared", "entity": "ClickHouse::MaterializedView" },
|
|
22
|
+
{ "command": "CREATE WINDOW VIEW", "kind": "window view", "status": "refused", "reason": "no declaration holds a window view, an experimental feature" },
|
|
23
|
+
{ "command": "CREATE WORKLOAD", "kind": "workload", "status": "refused", "reason": "no declaration holds a workload yet" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "doc/src/sgml/ref/create_*.sgml <refname>, at each pinned major's tag",
|
|
3
|
+
"majors": {
|
|
4
|
+
"14": {
|
|
5
|
+
"version": "14.24",
|
|
6
|
+
"tag": "REL_14_24",
|
|
7
|
+
"commands": [
|
|
8
|
+
"CREATE ACCESS METHOD",
|
|
9
|
+
"CREATE AGGREGATE",
|
|
10
|
+
"CREATE CAST",
|
|
11
|
+
"CREATE COLLATION",
|
|
12
|
+
"CREATE CONVERSION",
|
|
13
|
+
"CREATE DATABASE",
|
|
14
|
+
"CREATE DOMAIN",
|
|
15
|
+
"CREATE EVENT TRIGGER",
|
|
16
|
+
"CREATE EXTENSION",
|
|
17
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
18
|
+
"CREATE FOREIGN TABLE",
|
|
19
|
+
"CREATE FUNCTION",
|
|
20
|
+
"CREATE GROUP",
|
|
21
|
+
"CREATE INDEX",
|
|
22
|
+
"CREATE LANGUAGE",
|
|
23
|
+
"CREATE MATERIALIZED VIEW",
|
|
24
|
+
"CREATE OPERATOR",
|
|
25
|
+
"CREATE OPERATOR CLASS",
|
|
26
|
+
"CREATE OPERATOR FAMILY",
|
|
27
|
+
"CREATE POLICY",
|
|
28
|
+
"CREATE PROCEDURE",
|
|
29
|
+
"CREATE PUBLICATION",
|
|
30
|
+
"CREATE ROLE",
|
|
31
|
+
"CREATE RULE",
|
|
32
|
+
"CREATE SCHEMA",
|
|
33
|
+
"CREATE SEQUENCE",
|
|
34
|
+
"CREATE SERVER",
|
|
35
|
+
"CREATE STATISTICS",
|
|
36
|
+
"CREATE SUBSCRIPTION",
|
|
37
|
+
"CREATE TABLE",
|
|
38
|
+
"CREATE TABLE AS",
|
|
39
|
+
"CREATE TABLESPACE",
|
|
40
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
41
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
42
|
+
"CREATE TEXT SEARCH PARSER",
|
|
43
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
44
|
+
"CREATE TRANSFORM",
|
|
45
|
+
"CREATE TRIGGER",
|
|
46
|
+
"CREATE TYPE",
|
|
47
|
+
"CREATE USER",
|
|
48
|
+
"CREATE USER MAPPING",
|
|
49
|
+
"CREATE VIEW"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"15": {
|
|
53
|
+
"version": "15.19",
|
|
54
|
+
"tag": "REL_15_19",
|
|
55
|
+
"commands": [
|
|
56
|
+
"CREATE ACCESS METHOD",
|
|
57
|
+
"CREATE AGGREGATE",
|
|
58
|
+
"CREATE CAST",
|
|
59
|
+
"CREATE COLLATION",
|
|
60
|
+
"CREATE CONVERSION",
|
|
61
|
+
"CREATE DATABASE",
|
|
62
|
+
"CREATE DOMAIN",
|
|
63
|
+
"CREATE EVENT TRIGGER",
|
|
64
|
+
"CREATE EXTENSION",
|
|
65
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
66
|
+
"CREATE FOREIGN TABLE",
|
|
67
|
+
"CREATE FUNCTION",
|
|
68
|
+
"CREATE GROUP",
|
|
69
|
+
"CREATE INDEX",
|
|
70
|
+
"CREATE LANGUAGE",
|
|
71
|
+
"CREATE MATERIALIZED VIEW",
|
|
72
|
+
"CREATE OPERATOR",
|
|
73
|
+
"CREATE OPERATOR CLASS",
|
|
74
|
+
"CREATE OPERATOR FAMILY",
|
|
75
|
+
"CREATE POLICY",
|
|
76
|
+
"CREATE PROCEDURE",
|
|
77
|
+
"CREATE PUBLICATION",
|
|
78
|
+
"CREATE ROLE",
|
|
79
|
+
"CREATE RULE",
|
|
80
|
+
"CREATE SCHEMA",
|
|
81
|
+
"CREATE SEQUENCE",
|
|
82
|
+
"CREATE SERVER",
|
|
83
|
+
"CREATE STATISTICS",
|
|
84
|
+
"CREATE SUBSCRIPTION",
|
|
85
|
+
"CREATE TABLE",
|
|
86
|
+
"CREATE TABLE AS",
|
|
87
|
+
"CREATE TABLESPACE",
|
|
88
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
89
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
90
|
+
"CREATE TEXT SEARCH PARSER",
|
|
91
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
92
|
+
"CREATE TRANSFORM",
|
|
93
|
+
"CREATE TRIGGER",
|
|
94
|
+
"CREATE TYPE",
|
|
95
|
+
"CREATE USER",
|
|
96
|
+
"CREATE USER MAPPING",
|
|
97
|
+
"CREATE VIEW"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"16": {
|
|
101
|
+
"version": "16.15",
|
|
102
|
+
"tag": "REL_16_15",
|
|
103
|
+
"commands": [
|
|
104
|
+
"CREATE ACCESS METHOD",
|
|
105
|
+
"CREATE AGGREGATE",
|
|
106
|
+
"CREATE CAST",
|
|
107
|
+
"CREATE COLLATION",
|
|
108
|
+
"CREATE CONVERSION",
|
|
109
|
+
"CREATE DATABASE",
|
|
110
|
+
"CREATE DOMAIN",
|
|
111
|
+
"CREATE EVENT TRIGGER",
|
|
112
|
+
"CREATE EXTENSION",
|
|
113
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
114
|
+
"CREATE FOREIGN TABLE",
|
|
115
|
+
"CREATE FUNCTION",
|
|
116
|
+
"CREATE GROUP",
|
|
117
|
+
"CREATE INDEX",
|
|
118
|
+
"CREATE LANGUAGE",
|
|
119
|
+
"CREATE MATERIALIZED VIEW",
|
|
120
|
+
"CREATE OPERATOR",
|
|
121
|
+
"CREATE OPERATOR CLASS",
|
|
122
|
+
"CREATE OPERATOR FAMILY",
|
|
123
|
+
"CREATE POLICY",
|
|
124
|
+
"CREATE PROCEDURE",
|
|
125
|
+
"CREATE PUBLICATION",
|
|
126
|
+
"CREATE ROLE",
|
|
127
|
+
"CREATE RULE",
|
|
128
|
+
"CREATE SCHEMA",
|
|
129
|
+
"CREATE SEQUENCE",
|
|
130
|
+
"CREATE SERVER",
|
|
131
|
+
"CREATE STATISTICS",
|
|
132
|
+
"CREATE SUBSCRIPTION",
|
|
133
|
+
"CREATE TABLE",
|
|
134
|
+
"CREATE TABLE AS",
|
|
135
|
+
"CREATE TABLESPACE",
|
|
136
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
137
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
138
|
+
"CREATE TEXT SEARCH PARSER",
|
|
139
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
140
|
+
"CREATE TRANSFORM",
|
|
141
|
+
"CREATE TRIGGER",
|
|
142
|
+
"CREATE TYPE",
|
|
143
|
+
"CREATE USER",
|
|
144
|
+
"CREATE USER MAPPING",
|
|
145
|
+
"CREATE VIEW"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"17": {
|
|
149
|
+
"version": "17.11",
|
|
150
|
+
"tag": "REL_17_11",
|
|
151
|
+
"commands": [
|
|
152
|
+
"CREATE ACCESS METHOD",
|
|
153
|
+
"CREATE AGGREGATE",
|
|
154
|
+
"CREATE CAST",
|
|
155
|
+
"CREATE COLLATION",
|
|
156
|
+
"CREATE CONVERSION",
|
|
157
|
+
"CREATE DATABASE",
|
|
158
|
+
"CREATE DOMAIN",
|
|
159
|
+
"CREATE EVENT TRIGGER",
|
|
160
|
+
"CREATE EXTENSION",
|
|
161
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
162
|
+
"CREATE FOREIGN TABLE",
|
|
163
|
+
"CREATE FUNCTION",
|
|
164
|
+
"CREATE GROUP",
|
|
165
|
+
"CREATE INDEX",
|
|
166
|
+
"CREATE LANGUAGE",
|
|
167
|
+
"CREATE MATERIALIZED VIEW",
|
|
168
|
+
"CREATE OPERATOR",
|
|
169
|
+
"CREATE OPERATOR CLASS",
|
|
170
|
+
"CREATE OPERATOR FAMILY",
|
|
171
|
+
"CREATE POLICY",
|
|
172
|
+
"CREATE PROCEDURE",
|
|
173
|
+
"CREATE PUBLICATION",
|
|
174
|
+
"CREATE ROLE",
|
|
175
|
+
"CREATE RULE",
|
|
176
|
+
"CREATE SCHEMA",
|
|
177
|
+
"CREATE SEQUENCE",
|
|
178
|
+
"CREATE SERVER",
|
|
179
|
+
"CREATE STATISTICS",
|
|
180
|
+
"CREATE SUBSCRIPTION",
|
|
181
|
+
"CREATE TABLE",
|
|
182
|
+
"CREATE TABLE AS",
|
|
183
|
+
"CREATE TABLESPACE",
|
|
184
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
185
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
186
|
+
"CREATE TEXT SEARCH PARSER",
|
|
187
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
188
|
+
"CREATE TRANSFORM",
|
|
189
|
+
"CREATE TRIGGER",
|
|
190
|
+
"CREATE TYPE",
|
|
191
|
+
"CREATE USER",
|
|
192
|
+
"CREATE USER MAPPING",
|
|
193
|
+
"CREATE VIEW"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"18": {
|
|
197
|
+
"version": "18.6",
|
|
198
|
+
"tag": "REL_18_6",
|
|
199
|
+
"commands": [
|
|
200
|
+
"CREATE ACCESS METHOD",
|
|
201
|
+
"CREATE AGGREGATE",
|
|
202
|
+
"CREATE CAST",
|
|
203
|
+
"CREATE COLLATION",
|
|
204
|
+
"CREATE CONVERSION",
|
|
205
|
+
"CREATE DATABASE",
|
|
206
|
+
"CREATE DOMAIN",
|
|
207
|
+
"CREATE EVENT TRIGGER",
|
|
208
|
+
"CREATE EXTENSION",
|
|
209
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
210
|
+
"CREATE FOREIGN TABLE",
|
|
211
|
+
"CREATE FUNCTION",
|
|
212
|
+
"CREATE GROUP",
|
|
213
|
+
"CREATE INDEX",
|
|
214
|
+
"CREATE LANGUAGE",
|
|
215
|
+
"CREATE MATERIALIZED VIEW",
|
|
216
|
+
"CREATE OPERATOR",
|
|
217
|
+
"CREATE OPERATOR CLASS",
|
|
218
|
+
"CREATE OPERATOR FAMILY",
|
|
219
|
+
"CREATE POLICY",
|
|
220
|
+
"CREATE PROCEDURE",
|
|
221
|
+
"CREATE PUBLICATION",
|
|
222
|
+
"CREATE ROLE",
|
|
223
|
+
"CREATE RULE",
|
|
224
|
+
"CREATE SCHEMA",
|
|
225
|
+
"CREATE SEQUENCE",
|
|
226
|
+
"CREATE SERVER",
|
|
227
|
+
"CREATE STATISTICS",
|
|
228
|
+
"CREATE SUBSCRIPTION",
|
|
229
|
+
"CREATE TABLE",
|
|
230
|
+
"CREATE TABLE AS",
|
|
231
|
+
"CREATE TABLESPACE",
|
|
232
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
233
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
234
|
+
"CREATE TEXT SEARCH PARSER",
|
|
235
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
236
|
+
"CREATE TRANSFORM",
|
|
237
|
+
"CREATE TRIGGER",
|
|
238
|
+
"CREATE TYPE",
|
|
239
|
+
"CREATE USER",
|
|
240
|
+
"CREATE USER MAPPING",
|
|
241
|
+
"CREATE VIEW"
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dialect": "postgres",
|
|
3
|
+
"kinds": [
|
|
4
|
+
{ "command": "CREATE ACCESS METHOD", "kind": "access method", "status": "refused", "reason": "no tag declares an access method; an extension usually installs one, and the extension is declared instead" },
|
|
5
|
+
{ "command": "CREATE AGGREGATE", "kind": "aggregate", "status": "refused", "reason": "no tag declares an aggregate yet" },
|
|
6
|
+
{ "command": "CREATE CAST", "kind": "cast", "status": "refused", "reason": "no tag declares a cast yet" },
|
|
7
|
+
{ "command": "CREATE COLLATION", "kind": "collation", "status": "refused", "reason": "no tag declares a collation yet" },
|
|
8
|
+
{ "command": "CREATE CONVERSION", "kind": "conversion", "status": "refused", "reason": "no tag declares an encoding conversion" },
|
|
9
|
+
{ "command": "CREATE DATABASE", "kind": "database", "status": "out-of-scope", "reason": "a project's schema lives in one database, which the environment provides (sql.profiles.<env>)" },
|
|
10
|
+
{ "command": "CREATE DOMAIN", "kind": "domain", "status": "declared", "entity": "Postgres::Domain" },
|
|
11
|
+
{ "command": "CREATE EVENT TRIGGER", "kind": "event trigger", "status": "refused", "reason": "no tag declares an event trigger yet" },
|
|
12
|
+
{ "command": "CREATE EXTENSION", "kind": "extension", "status": "declared", "entity": "Postgres::Extension" },
|
|
13
|
+
{ "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "refused", "reason": "no tag declares a foreign data wrapper yet; one an extension installs comes with the extension" },
|
|
14
|
+
{ "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "refused", "reason": "no tag declares a foreign table yet" },
|
|
15
|
+
{ "command": "CREATE FUNCTION", "kind": "function", "status": "declared", "entity": "Postgres::Function" },
|
|
16
|
+
{ "command": "CREATE FUNCTION", "form": "RETURN or BEGIN ATOMIC body", "match": "\\b(?:RETURN|BEGIN\\s+ATOMIC)\\b", "kind": "routine with a SQL-standard body", "status": "refused", "reason": "the server stores a SQL-standard body parsed and prints it back rewritten, so a declaration cannot be compared with it; declare the routine with a string body (AS $$ ... $$)" },
|
|
17
|
+
{ "command": "CREATE GROUP", "kind": "group", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE" },
|
|
18
|
+
{ "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "Postgres::Index" },
|
|
19
|
+
{ "command": "CREATE LANGUAGE", "kind": "procedural language", "status": "refused", "reason": "no tag declares a procedural language; an extension installs one, and the extension is declared instead" },
|
|
20
|
+
{ "command": "CREATE MATERIALIZED VIEW", "kind": "materialized view", "status": "declared", "entity": "Postgres::MaterializedView" },
|
|
21
|
+
{ "command": "CREATE OPERATOR", "kind": "operator", "status": "refused", "reason": "no tag declares an operator yet" },
|
|
22
|
+
{ "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "refused", "reason": "no tag declares an operator class yet" },
|
|
23
|
+
{ "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "refused", "reason": "no tag declares an operator family yet" },
|
|
24
|
+
{ "command": "CREATE POLICY", "kind": "policy", "status": "declared", "entity": "Postgres::Policy", "note": "read where the profile manages access (sql.profiles.<env>.access)" },
|
|
25
|
+
{ "command": "CREATE PROCEDURE", "kind": "procedure", "status": "declared", "entity": "Postgres::Procedure" },
|
|
26
|
+
{ "command": "CREATE PUBLICATION", "kind": "publication", "status": "refused", "reason": "no tag declares a publication yet" },
|
|
27
|
+
{ "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "Postgres::Role", "note": "import reads only the roles a build declares, since a role is the cluster's" },
|
|
28
|
+
{ "command": "CREATE RULE", "kind": "rule", "status": "refused", "reason": "no tag declares a rewrite rule; a view's own rule is the view's" },
|
|
29
|
+
{ "command": "CREATE SCHEMA", "kind": "schema", "status": "declared", "entity": "Postgres::Schema" },
|
|
30
|
+
{ "command": "CREATE SEQUENCE", "kind": "sequence", "status": "declared", "entity": "Postgres::Sequence" },
|
|
31
|
+
{ "command": "CREATE SERVER", "kind": "foreign server", "status": "refused", "reason": "no tag declares a foreign server yet" },
|
|
32
|
+
{ "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "refused", "reason": "no tag declares extended statistics yet" },
|
|
33
|
+
{ "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "no tag declares a subscription yet" },
|
|
34
|
+
{ "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "Postgres::Table" },
|
|
35
|
+
{ "command": "CREATE TABLE AS", "kind": "table from a query", "status": "declared", "entity": "Postgres::Table", "note": "the server keeps no query, so the table it made reads as a CREATE TABLE" },
|
|
36
|
+
{ "command": "CREATE TABLESPACE", "kind": "tablespace", "status": "out-of-scope", "reason": "a tablespace is the cluster's, a directory on the server; a table's TABLESPACE clause is kept" },
|
|
37
|
+
{ "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "refused", "reason": "no tag declares a text search configuration yet" },
|
|
38
|
+
{ "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "refused", "reason": "no tag declares a text search dictionary yet" },
|
|
39
|
+
{ "command": "CREATE TEXT SEARCH PARSER", "kind": "text search parser", "status": "refused", "reason": "no tag declares a text search parser; one is C code an extension installs" },
|
|
40
|
+
{ "command": "CREATE TEXT SEARCH TEMPLATE", "kind": "text search template", "status": "refused", "reason": "no tag declares a text search template; one is C code an extension installs" },
|
|
41
|
+
{ "command": "CREATE TRANSFORM", "kind": "transform", "status": "refused", "reason": "no tag declares a transform; an extension installs one" },
|
|
42
|
+
{ "command": "CREATE TRIGGER", "kind": "trigger", "status": "declared", "entity": "Postgres::Trigger" },
|
|
43
|
+
{ "command": "CREATE TYPE", "form": "AS ENUM", "match": "\\bAS\\s+ENUM\\b", "kind": "enum type", "status": "declared", "entity": "Postgres::Enum" },
|
|
44
|
+
{ "command": "CREATE TYPE", "form": "AS (...)", "match": "\\bAS\\s*\\(", "kind": "composite type", "status": "refused", "reason": "no tag declares a composite type yet" },
|
|
45
|
+
{ "command": "CREATE TYPE", "form": "AS RANGE", "match": "\\bAS\\s+RANGE\\b", "kind": "range type", "status": "refused", "reason": "no tag declares a range type yet" },
|
|
46
|
+
{ "command": "CREATE TYPE", "form": "INPUT, OUTPUT, or a shell type", "kind": "base type", "status": "refused", "reason": "no tag declares a base type; its input and output functions are C code an extension installs" },
|
|
47
|
+
{ "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE ... LOGIN" },
|
|
48
|
+
{ "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "no tag declares a user mapping yet" },
|
|
49
|
+
{ "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "Postgres::View" }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { KIND_COVERAGE, PINNED_COMMANDS, kindCoverageProblems, pinnedCommandsCurrent, statementKind } from "./coverage";
|
|
3
|
+
import { POSTGRES_PROBES } from "./postgres/live/undeclarable";
|
|
4
|
+
import { CLICKHOUSE_PROBES } from "./clickhouse/live/undeclarable";
|
|
5
|
+
import { SQL_DIALECTS } from "./dialects";
|
|
6
|
+
|
|
7
|
+
describe("the object coverage table (#3911)", () => {
|
|
8
|
+
test.each(SQL_DIALECTS)("%s: every CREATE command a pinned server has is marked, and nothing else", (dialect) => {
|
|
9
|
+
expect(kindCoverageProblems(dialect)).toEqual([]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test.each(SQL_DIALECTS)("%s: the command lists are the pinned servers' (run scripts/sql-create-commands.ts after a pin move)", (dialect) => {
|
|
13
|
+
expect(pinnedCommandsCurrent(dialect)).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("a pinned server with a command no row covers fails the check", () => {
|
|
17
|
+
const pins = [{ version: "19.0", commands: [...PINNED_COMMANDS.postgres[0]!.commands, "CREATE PROPERTY GRAPH"] }];
|
|
18
|
+
expect(kindCoverageProblems("postgres", { pins })).toContain("postgres 19.0 has CREATE PROPERTY GRAPH, and no row in src/coverage/postgres.json covers it");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a row must say why it is not declared, and name its entity when it is", () => {
|
|
22
|
+
const rows = [
|
|
23
|
+
{ command: "CREATE CAST", kind: "cast", status: "refused" as const },
|
|
24
|
+
{ command: "CREATE TABLE", kind: "table", status: "declared" as const },
|
|
25
|
+
];
|
|
26
|
+
expect(kindCoverageProblems("postgres", { pins: [{ version: "x", commands: ["CREATE CAST", "CREATE TABLE"] }], rows })).toEqual([
|
|
27
|
+
"cast is refused, and gives no reason",
|
|
28
|
+
"table is declared, and names no entity type",
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("every refused kind has the live query that finds it, and every query a refused-or-declared row", () => {
|
|
33
|
+
const probes = { postgres: POSTGRES_PROBES, clickhouse: CLICKHOUSE_PROBES };
|
|
34
|
+
for (const dialect of SQL_DIALECTS) {
|
|
35
|
+
const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused").map((r) => r.kind);
|
|
36
|
+
for (const kind of refused) expect(Object.keys(probes[dialect]), `${dialect} ${kind}`).toContain(kind);
|
|
37
|
+
const kinds = KIND_COVERAGE[dialect].map((r) => r.kind);
|
|
38
|
+
for (const kind of Object.keys(probes[dialect])) expect(kinds, `${dialect} probe ${kind}`).toContain(kind);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("a statement's kind", () => {
|
|
44
|
+
test.each([
|
|
45
|
+
["CREATE TYPE app.pair AS (a int, b text)", "composite type"],
|
|
46
|
+
["CREATE TYPE app.r AS RANGE (subtype = float8)", "range type"],
|
|
47
|
+
["CREATE TYPE app.mood AS ENUM ('ok')", "enum type"],
|
|
48
|
+
["CREATE TYPE app.shell", "base type"],
|
|
49
|
+
["CREATE AGGREGATE app.total(int) (sfunc = int4pl, stype = int)", "aggregate"],
|
|
50
|
+
["CREATE CAST (app.pair AS text) WITH FUNCTION app.f(app.pair)", "cast"],
|
|
51
|
+
["CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE plx", "procedural language"],
|
|
52
|
+
["CREATE DEFAULT CONVERSION c FOR 'LATIN1' TO 'UTF8' FROM f", "conversion"],
|
|
53
|
+
["CREATE OPERATOR CLASS c FOR TYPE int USING btree AS OPERATOR 1 <", "operator class"],
|
|
54
|
+
["CREATE TEXT SEARCH CONFIGURATION app.eng (COPY = english)", "text search configuration"],
|
|
55
|
+
["CREATE USER MAPPING FOR app SERVER s", "user mapping"],
|
|
56
|
+
["CREATE UNIQUE INDEX i ON app.t (a)", "index"],
|
|
57
|
+
// A body's RETURN is inside its string, not a SQL-standard body.
|
|
58
|
+
["CREATE FUNCTION app.f() RETURNS int LANGUAGE plpgsql AS $$ BEGIN RETURN 1; END $$", "function"],
|
|
59
|
+
["CREATE FUNCTION app.f(a int) RETURNS int LANGUAGE sql RETURN a + 1", "routine with a SQL-standard body"],
|
|
60
|
+
])("postgres: %s is a %s", (sql, kind) => {
|
|
61
|
+
expect(statementKind("postgres", sql)?.kind).toBe(kind);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test.each([
|
|
65
|
+
["CREATE TEMPORARY TABLE t (a Int8)", "temporary table"],
|
|
66
|
+
["CREATE TABLE t (a Int8) ENGINE = Memory", "table"],
|
|
67
|
+
["CREATE WINDOW VIEW w AS SELECT 1", "window view"],
|
|
68
|
+
["CREATE SETTINGS PROFILE p SETTINGS max_threads = 1", "settings profile"],
|
|
69
|
+
["CREATE OR REPLACE FUNCTION f AS (x) -> x + 1", "SQL function"],
|
|
70
|
+
["CREATE QUOTA q FOR INTERVAL 1 hour MAX queries = 10", "quota"],
|
|
71
|
+
])("clickhouse: %s is a %s", (sql, kind) => {
|
|
72
|
+
expect(statementKind("clickhouse", sql)?.kind).toBe(kind);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a statement that is not a CREATE has no kind", () => {
|
|
76
|
+
expect(statementKind("postgres", "ALTER TABLE t ADD COLUMN a int")).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
});
|