@intentius/chant-lexicon-sql 0.121.1 → 0.123.0
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/README.md +1 -1
- package/dist/clickhouse/catalog-types.d.ts +8 -0
- package/dist/clickhouse/catalog-types.d.ts.map +1 -1
- package/dist/clickhouse/entities.d.ts +37 -11
- package/dist/clickhouse/entities.d.ts.map +1 -1
- package/dist/clickhouse/import/generator.d.ts.map +1 -1
- package/dist/clickhouse/overlays/codecs.d.ts.map +1 -1
- package/dist/clickhouse/overlays/kinds.d.ts +1 -1
- package/dist/clickhouse/overlays/kinds.d.ts.map +1 -1
- package/dist/clickhouse/overlays/types.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/row-types.d.ts +30 -0
- package/dist/clickhouse/row-types.d.ts.map +1 -0
- package/dist/clickhouse/type-syntax.d.ts +33 -0
- package/dist/clickhouse/type-syntax.d.ts.map +1 -0
- package/dist/clickhouse.d.ts +1 -1
- package/dist/clickhouse.d.ts.map +1 -1
- package/dist/codegen/clickhouse-module.d.ts.map +1 -1
- package/dist/codegen/postgres-module.d.ts.map +1 -1
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/typed.d.ts +132 -0
- package/dist/core/typed.d.ts.map +1 -0
- package/dist/generated/clickhouse.d.ts +7 -1
- package/dist/generated/clickhouse.d.ts.map +1 -1
- package/dist/generated/postgres.d.ts +6 -0
- package/dist/generated/postgres.d.ts.map +1 -1
- package/dist/integrity.json +27 -5
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
- package/dist/lint/post-synth/clickhouse-type-check.d.ts +37 -0
- package/dist/lint/post-synth/clickhouse-type-check.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-audit-entries.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-names.d.ts +151 -0
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -0
- package/dist/lint/post-synth/sql101.d.ts +9 -0
- package/dist/lint/post-synth/sql101.d.ts.map +1 -0
- package/dist/lint/post-synth/sql102.d.ts +16 -0
- package/dist/lint/post-synth/sql102.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch121.d.ts +10 -0
- package/dist/lint/post-synth/sqlch121.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch122.d.ts +10 -0
- package/dist/lint/post-synth/sqlch122.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch123.d.ts +9 -0
- package/dist/lint/post-synth/sqlch123.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch124.d.ts +6 -0
- package/dist/lint/post-synth/sqlch124.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch125.d.ts +11 -0
- package/dist/lint/post-synth/sqlch125.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch126.d.ts +12 -0
- package/dist/lint/post-synth/sqlch126.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch127.d.ts +22 -0
- package/dist/lint/post-synth/sqlch127.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg119.d.ts +18 -0
- package/dist/lint/post-synth/sqlpg119.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg120.d.ts +17 -0
- package/dist/lint/post-synth/sqlpg120.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg121.d.ts +12 -0
- package/dist/lint/post-synth/sqlpg121.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg122.d.ts +12 -0
- package/dist/lint/post-synth/sqlpg122.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg123.d.ts +17 -0
- package/dist/lint/post-synth/sqlpg123.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg124.d.ts +16 -0
- package/dist/lint/post-synth/sqlpg124.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg125.d.ts +17 -0
- package/dist/lint/post-synth/sqlpg125.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlpg126.d.ts +19 -0
- package/dist/lint/post-synth/sqlpg126.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +3 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/sqlch004.d.ts +19 -0
- package/dist/lint/rules/sqlch004.d.ts.map +1 -0
- package/dist/lint/rules/sqlpg005.d.ts +18 -0
- package/dist/lint/rules/sqlpg005.d.ts.map +1 -0
- package/dist/lint/rules/typed-names.d.ts +43 -0
- package/dist/lint/rules/typed-names.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +19 -0
- package/dist/okf/rules/SQL101.md +23 -0
- package/dist/okf/rules/SQL102.md +11 -0
- package/dist/okf/rules/SQLCH004.md +11 -0
- package/dist/okf/rules/SQLCH121.md +11 -0
- package/dist/okf/rules/SQLCH122.md +11 -0
- package/dist/okf/rules/SQLCH123.md +11 -0
- package/dist/okf/rules/SQLCH124.md +18 -0
- package/dist/okf/rules/SQLCH125.md +11 -0
- package/dist/okf/rules/SQLCH126.md +15 -0
- package/dist/okf/rules/SQLCH127.md +15 -0
- package/dist/okf/rules/SQLPG005.md +11 -0
- package/dist/okf/rules/SQLPG119.md +16 -0
- package/dist/okf/rules/SQLPG120.md +16 -0
- package/dist/okf/rules/SQLPG121.md +11 -0
- package/dist/okf/rules/SQLPG122.md +15 -0
- package/dist/okf/rules/SQLPG123.md +16 -0
- package/dist/okf/rules/SQLPG124.md +11 -0
- package/dist/okf/rules/SQLPG125.md +11 -0
- package/dist/okf/rules/SQLPG126.md +11 -0
- package/dist/okf/types/Dictionary.md +4 -0
- package/dist/okf/types/Function.md +5 -0
- package/dist/okf/types/Grant.md +6 -0
- package/dist/okf/types/MaterializedView.md +1 -0
- package/dist/okf/types/PostgresDefaultPrivileges.md +4 -0
- package/dist/okf/types/PostgresDomain.md +2 -0
- package/dist/okf/types/PostgresEnum.md +1 -0
- package/dist/okf/types/PostgresFunction.md +4 -0
- package/dist/okf/types/PostgresGrant.md +5 -0
- package/dist/okf/types/PostgresPolicy.md +4 -0
- package/dist/okf/types/PostgresProcedure.md +4 -0
- package/dist/okf/types/PostgresSequence.md +2 -0
- package/dist/okf/types/PostgresTrigger.md +4 -0
- package/dist/okf/types/RowPolicy.md +4 -0
- package/dist/okf/types/Table.md +1 -0
- package/dist/okf/types/View.md +1 -0
- package/dist/postgres/entities.d.ts +38 -9
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/overlays/extension-objects.d.ts +28 -0
- package/dist/postgres/overlays/extension-objects.d.ts.map +1 -0
- package/dist/postgres/overlays/types.d.ts +10 -0
- package/dist/postgres/overlays/types.d.ts.map +1 -1
- package/dist/postgres/row-types.d.ts +17 -0
- package/dist/postgres/row-types.d.ts.map +1 -0
- package/dist/postgres/testing/migration.d.ts +3 -2
- package/dist/postgres/testing/migration.d.ts.map +1 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/rules/audit-entries.ts +7 -0
- package/dist/rules/clickhouse-type-check.ts +173 -0
- package/dist/rules/postgres-audit-entries.ts +8 -0
- package/dist/rules/postgres-names.ts +510 -0
- package/dist/rules/sql101.ts +62 -0
- package/dist/rules/sql102.ts +34 -0
- package/dist/rules/sqlch004.ts +81 -0
- package/dist/rules/sqlch121.ts +12 -0
- package/dist/rules/sqlch122.ts +12 -0
- package/dist/rules/sqlch123.ts +11 -0
- package/dist/rules/sqlch124.ts +21 -0
- package/dist/rules/sqlch125.ts +56 -0
- package/dist/rules/sqlch126.ts +57 -0
- package/dist/rules/sqlch127.ts +128 -0
- package/dist/rules/sqlpg005.ts +73 -0
- package/dist/rules/sqlpg119.ts +37 -0
- package/dist/rules/sqlpg120.ts +70 -0
- package/dist/rules/sqlpg121.ts +32 -0
- package/dist/rules/sqlpg122.ts +28 -0
- package/dist/rules/sqlpg123.ts +63 -0
- package/dist/rules/sqlpg124.ts +47 -0
- package/dist/rules/sqlpg125.ts +40 -0
- package/dist/rules/sqlpg126.ts +37 -0
- package/dist/rules/typed-names.ts +120 -0
- package/dist/sql-types-command.d.ts +19 -0
- package/dist/sql-types-command.d.ts.map +1 -0
- package/dist/sql-types.d.ts +53 -0
- package/dist/sql-types.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/clickhouse/catalog-types.ts +9 -0
- package/src/clickhouse/dictionary.test.ts +1 -1
- package/src/clickhouse/entities.ts +79 -14
- package/src/clickhouse/import/generator.ts +7 -1
- package/src/clickhouse/import/roundtrip.test.ts +1 -1
- package/src/clickhouse/overlays/codecs.ts +6 -2
- package/src/clickhouse/overlays/kinds.ts +1 -1
- package/src/clickhouse/overlays/types.ts +20 -3
- package/src/clickhouse/plan/commands.ts +5 -0
- package/src/clickhouse/row-types.ts +158 -0
- package/src/clickhouse/type-syntax.ts +82 -0
- package/src/clickhouse.ts +10 -0
- package/src/codegen/clickhouse-module.ts +15 -1
- package/src/codegen/postgres-module.ts +43 -0
- package/src/composites/composites.test.ts +7 -6
- package/src/core/index.ts +4 -0
- package/src/core/typed.ts +143 -0
- package/src/generated/clickhouse.ts +1905 -6
- package/src/generated/postgres.ts +2639 -0
- package/src/lint/audit-catalog.ts +28 -0
- package/src/lint/post-synth/audit-entries.ts +7 -0
- package/src/lint/post-synth/clickhouse-catalog-checks.test.ts +315 -0
- package/src/lint/post-synth/clickhouse-type-check.ts +173 -0
- package/src/lint/post-synth/index.ts +34 -0
- package/src/lint/post-synth/postgres-audit-entries.ts +8 -0
- package/src/lint/post-synth/postgres-checks.test.ts +214 -1
- package/src/lint/post-synth/postgres-names.ts +510 -0
- package/src/lint/post-synth/sql101.ts +62 -0
- package/src/lint/post-synth/sql102.ts +34 -0
- package/src/lint/post-synth/sqlch121.ts +12 -0
- package/src/lint/post-synth/sqlch122.ts +12 -0
- package/src/lint/post-synth/sqlch123.ts +11 -0
- package/src/lint/post-synth/sqlch124.ts +21 -0
- package/src/lint/post-synth/sqlch125.ts +56 -0
- package/src/lint/post-synth/sqlch126.ts +57 -0
- package/src/lint/post-synth/sqlch127.ts +128 -0
- package/src/lint/post-synth/sqlpg119.ts +37 -0
- package/src/lint/post-synth/sqlpg120.ts +70 -0
- package/src/lint/post-synth/sqlpg121.ts +32 -0
- package/src/lint/post-synth/sqlpg122.ts +28 -0
- package/src/lint/post-synth/sqlpg123.ts +63 -0
- package/src/lint/post-synth/sqlpg124.ts +47 -0
- package/src/lint/post-synth/sqlpg125.ts +40 -0
- package/src/lint/post-synth/sqlpg126.ts +37 -0
- package/src/lint/rules/index.ts +4 -2
- package/src/lint/rules/sqlch004.ts +81 -0
- package/src/lint/rules/sqlpg005.ts +73 -0
- package/src/lint/rules/typed-names.test.ts +151 -0
- package/src/lint/rules/typed-names.ts +120 -0
- package/src/op/__snapshots__/watch-pipeline.test.ts.snap +3 -3
- package/src/postgres/entities.ts +65 -12
- package/src/postgres/import/generator.ts +7 -1
- package/src/postgres/overlays/extension-objects.ts +100 -0
- package/src/postgres/overlays/types.ts +25 -0
- package/src/postgres/row-types.ts +111 -0
- package/src/postgres.ts +10 -0
- package/src/sql-types-command.ts +99 -0
- package/src/sql-types-staleness.test.ts +110 -0
- package/src/sql-types.test.ts +132 -0
- package/src/sql-types.ts +352 -0
- package/src/typecheck.test.ts +12 -3
- package/src/typed-columns.test.ts +92 -0
|
@@ -4,7 +4,7 @@ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
|
4
4
|
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
5
5
|
import { sqlSerializer } from "../../serializer";
|
|
6
6
|
import { table as chTable } from "../../clickhouse/entities";
|
|
7
|
-
import { extension, index, schema, table, view } from "../../postgres/entities";
|
|
7
|
+
import { domain, extension, func, grant, index, role, schema, sequence, table, type as pgType, view } from "../../postgres/entities";
|
|
8
8
|
import { sqlAuditCatalog } from "../audit-catalog";
|
|
9
9
|
import { postSynthChecks } from "./index";
|
|
10
10
|
|
|
@@ -255,3 +255,216 @@ describe("SQLPG118: INHERITS", () => {
|
|
|
255
255
|
clean("SQLPG118", { p, c: table`CREATE TABLE c PARTITION OF ${p} FOR VALUES FROM (1) TO (10)` });
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
// ── Catalog names (chant #3750) ─────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
/** Every diagnostic of the catalog-name rules over a set of entities, at one major. */
|
|
262
|
+
const CATALOG_RULES = ["SQLPG119", "SQLPG120", "SQLPG121", "SQLPG122", "SQLPG123", "SQLPG124", "SQLPG125", "SQLPG126"];
|
|
263
|
+
const allCatalog = (entities: Entities, major?: number) => CATALOG_RULES.flatMap((id) => run(id, entities, major));
|
|
264
|
+
const MAJORS = [14, 15, 16, 17, 18];
|
|
265
|
+
|
|
266
|
+
describe("catalog names: correct declarations stay clean at every major", () => {
|
|
267
|
+
const app = schema`CREATE SCHEMA app`;
|
|
268
|
+
const mood = pgType`CREATE TYPE ${app}.mood AS ENUM ('ok', 'sad')`;
|
|
269
|
+
const pos = domain`CREATE DOMAIN ${app}.pos AS numeric(12,2) CHECK (VALUE > 0)`;
|
|
270
|
+
const label = domain`CREATE DOMAIN ${app}.label AS text DEFAULT lower('x') CHECK (length(VALUE) < 10)`;
|
|
271
|
+
const customers = table`CREATE TABLE ${app}.customers (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email text NOT NULL)`;
|
|
272
|
+
const spellings = table`CREATE TABLE ${app}.spellings (
|
|
273
|
+
a int, b integer, c int4, d bigint, e smallint, f int2, g int8,
|
|
274
|
+
h smallserial, i serial, j bigserial, k serial8,
|
|
275
|
+
l varchar(255), m character varying, n character varying(10), o text[], p int[][], q int[3], r int ARRAY,
|
|
276
|
+
s timestamptz, t timestamp(3) with time zone, u timestamp without time zone, v time with time zone, w time(3) with time zone, x timetz,
|
|
277
|
+
y interval, z interval(6), aa interval day to second(3), ab interval year to month,
|
|
278
|
+
ac numeric, ad numeric(12,2), ae decimal(5), af dec(4,1), ag double precision, ah float, ai float(24), aj float(53), ak real, al float8,
|
|
279
|
+
am bit varying(8), an bit(3), ao varbit, ap jsonb, aq json, ar uuid, as_ inet, at cidr, au tsvector, av bytea, aw bool, ax boolean,
|
|
280
|
+
ay char, az char(2), ba character(3), bb "char", bc date, bd money, be macaddr, bf int4range, bg nchar(2), bh national character varying(5),
|
|
281
|
+
bi ${mood}, bj app.mood, bk ${pos}, bl app.label, bm billing.money, bn pg_catalog.int4, bo ${customers}, bp _int4
|
|
282
|
+
)`;
|
|
283
|
+
const identities = table`CREATE TABLE ${app}.ids (
|
|
284
|
+
a smallint GENERATED ALWAYS AS IDENTITY, b integer GENERATED BY DEFAULT AS IDENTITY, c bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
285
|
+
cid integer REFERENCES ${customers} (id), cid2 bigint REFERENCES ${customers},
|
|
286
|
+
at timestamptz DEFAULT now(), u uuid DEFAULT gen_random_uuid(), n numeric DEFAULT coalesce(nullif(1, 2), greatest(1, 2), least(3, 4)),
|
|
287
|
+
s text DEFAULT substring('abc' FROM 1 FOR 2), p int DEFAULT position('b' IN 'abc'), tr text DEFAULT trim(both ' ' FROM ' x '),
|
|
288
|
+
ov text DEFAULT overlay('abc' PLACING 'x' FROM 2 FOR 1), ex numeric DEFAULT extract(year FROM now()), ca bigint DEFAULT CAST('1' AS bigint),
|
|
289
|
+
cs numeric DEFAULT '1'::numeric(10,2), ct varchar(3) DEFAULT CAST('a' AS varchar(3)), cu timestamptz DEFAULT current_timestamp,
|
|
290
|
+
cl timestamp DEFAULT localtimestamp(3), q text DEFAULT billing.make_code(), r int DEFAULT int4('3'), arr int[] DEFAULT ARRAY[1, 2],
|
|
291
|
+
g bigint GENERATED ALWAYS AS (c * 2) STORED,
|
|
292
|
+
CHECK (a > 0 AND EXISTS_LIKE_NAME IS NULL OR lower(s) IN ('a', 'b')),
|
|
293
|
+
CONSTRAINT k UNIQUE (a, b) INCLUDE (n)
|
|
294
|
+
)`;
|
|
295
|
+
const t = table`CREATE TABLE ${app}.docs (id bigint PRIMARY KEY, body text, doc jsonb, tags text[], at timestamptz, r int4range, loc point)`;
|
|
296
|
+
const idx = [
|
|
297
|
+
index`CREATE INDEX d_body ON ${t} (${t.columns.body} text_pattern_ops)`,
|
|
298
|
+
index`CREATE INDEX d_lower ON ${t} ((lower(body)) text_pattern_ops DESC NULLS LAST) INCLUDE (at)`,
|
|
299
|
+
index`CREATE INDEX d_doc ON ${t} USING gin (doc jsonb_path_ops)`,
|
|
300
|
+
index`CREATE INDEX d_doc2 ON ${t} USING gin (doc)`,
|
|
301
|
+
index`CREATE INDEX d_tags ON ${t} USING gin (tags array_ops)`,
|
|
302
|
+
index`CREATE INDEX d_r ON ${t} USING gist (r)`,
|
|
303
|
+
index`CREATE INDEX d_loc ON ${t} USING spgist (loc)`,
|
|
304
|
+
index`CREATE INDEX d_at ON ${t} USING brin (at)`,
|
|
305
|
+
index`CREATE INDEX d_id ON ${t} USING hash (id)`,
|
|
306
|
+
index`CREATE INDEX d_fts ON ${t} USING gin (to_tsvector('english', body))`,
|
|
307
|
+
index`CREATE UNIQUE INDEX d_c ON ${t} (body COLLATE "C" ASC)`,
|
|
308
|
+
];
|
|
309
|
+
const r = role`CREATE ROLE reader NOLOGIN`;
|
|
310
|
+
const g = grant`GRANT SELECT (id, body), UPDATE (at) ON ${t} TO ${r}`;
|
|
311
|
+
const seq = sequence`CREATE SEQUENCE ${app}.s AS integer`;
|
|
312
|
+
const fn = func`CREATE FUNCTION ${app}.slug(x text) RETURNS text LANGUAGE sql IMMUTABLE AS $$ select lower(x) $$`;
|
|
313
|
+
const uses = table`CREATE TABLE ${app}.uses (id bigint PRIMARY KEY, name text, slug text DEFAULT ${fn}('a'), s2 text GENERATED ALWAYS AS (slug(name)) STORED)`;
|
|
314
|
+
const entities: Entities = { app, mood, pos, label, customers, spellings, identities, t, r, g, seq, fn, uses, ...Object.fromEntries(idx.map((x, n) => [`i${n}`, x])) };
|
|
315
|
+
for (const major of MAJORS) {
|
|
316
|
+
test(`at Postgres ${major}`, () => expect(allCatalog(entities, major)).toEqual([]));
|
|
317
|
+
}
|
|
318
|
+
test("extension types, functions and operator classes, with the extension declared", () => {
|
|
319
|
+
const ext = [extension`CREATE EXTENSION citext`, extension`CREATE EXTENSION postgis`, extension`CREATE EXTENSION pg_trgm`, extension`CREATE EXTENSION "uuid-ossp"`];
|
|
320
|
+
const u = table`CREATE TABLE u (id uuid DEFAULT uuid_generate_v4() PRIMARY KEY, email citext, at geometry(Point, 4326), name text CHECK (similarity(name, 'x') >= 0 AND ST_IsValid(at)))`;
|
|
321
|
+
const i = index`CREATE INDEX u_name ON ${u} USING gin (name gin_trgm_ops)`;
|
|
322
|
+
const j = index`CREATE INDEX u_at ON ${u} USING gist (at)`;
|
|
323
|
+
expect(allCatalog({ ...Object.fromEntries(ext.map((e, n) => [`e${n}`, e])), u, i, j })).toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
test("a table that inherits, or is a partition of, a declared table reads the parent's columns", () => {
|
|
326
|
+
const parent = table`CREATE TABLE p (id bigint NOT NULL, at date NOT NULL) PARTITION BY RANGE (at)`;
|
|
327
|
+
const part = table`CREATE TABLE p1 PARTITION OF ${parent} (PRIMARY KEY (id, at)) FOR VALUES FROM ('2020-01-01') TO ('2021-01-01')`;
|
|
328
|
+
const i = index`CREATE INDEX p1_at ON ${part} (at)`;
|
|
329
|
+
expect(allCatalog({ parent, part, i })).toEqual([]);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe("SQLPG119: unknown type", () => {
|
|
334
|
+
const app = schema`CREATE SCHEMA app`;
|
|
335
|
+
test("numerik(12,2)", () => flagged("SQLPG119", { app, t: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, amount numerik(12,2))` }, /column amount has type numerik, which Postgres 18 does not have/));
|
|
336
|
+
test("an array of intt", () => flagged("SQLPG119", { app, t: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, tags text[], bad intt[])` }, /column bad has array element type intt/));
|
|
337
|
+
test("a sequence AS numerik", () => flagged("SQLPG119", { app, s: sequence`CREATE SEQUENCE ${app}.s AS numerik` }, /sequence of type numerik/));
|
|
338
|
+
test("a domain over a typo", () => flagged("SQLPG119", { app, d: domain`CREATE DOMAIN ${app}.d AS textt` }, /domain over type textt/));
|
|
339
|
+
test("app.mood when the project declares app and no mood", () => {
|
|
340
|
+
flagged("SQLPG119", { app, t: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, status app.mood)` }, /type app\.mood/);
|
|
341
|
+
});
|
|
342
|
+
test("a declared enum, by ${} or by name, is clean", () => {
|
|
343
|
+
const mood = pgType`CREATE TYPE ${app}.mood AS ENUM ('ok')`;
|
|
344
|
+
clean("SQLPG119", { app, mood, t: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, a ${mood}, b app.mood, c mood)` });
|
|
345
|
+
});
|
|
346
|
+
test("a type in an undeclared schema is left alone", () => clean("SQLPG119", { t: table`CREATE TABLE t (a billing.money, b pg_catalog.int8)` }));
|
|
347
|
+
test("with an extension declared, an unknown name is a warning", () => {
|
|
348
|
+
const d = run("SQLPG119", { e: extension`CREATE EXTENSION hstore`, t: table`CREATE TABLE t (a widget)` });
|
|
349
|
+
expect(d).toHaveLength(1);
|
|
350
|
+
expect(d[0]).toMatchObject({ severity: "warning" });
|
|
351
|
+
});
|
|
352
|
+
test("an extension type without its extension is an error naming it", () => {
|
|
353
|
+
flagged("SQLPG119", { t: table`CREATE TABLE t (email citext)` }, /citext extension, which the project does not declare/);
|
|
354
|
+
});
|
|
355
|
+
test("serial is a column spelling, not a domain base", () => flagged("SQLPG119", { d: domain`CREATE DOMAIN d AS serial` }, /serial/));
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("SQLPG120: type modifiers", () => {
|
|
359
|
+
test("bigint(8)", () => flagged("SQLPG120", { t: table`CREATE TABLE t (id bigint(8) PRIMARY KEY)` }, /bigint takes no type modifier/));
|
|
360
|
+
test("numeric(2000,2)", () => flagged("SQLPG120", { t: table`CREATE TABLE t (a numeric(2000,2))` }, /precision 2000 is outside 1 to 1000/));
|
|
361
|
+
test("varchar(0)", () => flagged("SQLPG120", { t: table`CREATE TABLE t (code varchar(0))` }, /length 0 is outside 1 to 10485760/));
|
|
362
|
+
test("timestamp(7), interval(9), bit(0), float(54)", () => {
|
|
363
|
+
const msgs = run("SQLPG120", { t: table`CREATE TABLE t (a timestamp(7), b interval(9), c bit(0), d float(54), e numeric(1,2,3))` }).map((d) => d.message).join("\n");
|
|
364
|
+
expect(msgs).toMatch(/precision 7 is outside 0 to 6/);
|
|
365
|
+
expect(msgs).toMatch(/column b .*precision 9/);
|
|
366
|
+
expect(msgs).toMatch(/length 0 is outside 1/);
|
|
367
|
+
expect(msgs).toMatch(/float precision 54/);
|
|
368
|
+
expect(msgs).toMatch(/at most 2 modifiers/);
|
|
369
|
+
});
|
|
370
|
+
test("a negative numeric scale is fine at 15 and refused at 14", () => {
|
|
371
|
+
const t = table`CREATE TABLE t (a numeric(5,-2))`;
|
|
372
|
+
expect(run("SQLPG120", { t }, 15)).toEqual([]);
|
|
373
|
+
expect(run("SQLPG120", { t }, 14)[0]!.message).toMatch(/scale -2 is outside 0 to 5 at Postgres 14/);
|
|
374
|
+
});
|
|
375
|
+
test("an extension type's modifiers are its own", () => clean("SQLPG120", { e: extension`CREATE EXTENSION postgis`, t: table`CREATE TABLE t (g geometry(Point, 4326))` }));
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe("SQLPG121: identity type", () => {
|
|
379
|
+
test("text identity", () => flagged("SQLPG121", { t: table`CREATE TABLE t (id text GENERATED ALWAYS AS IDENTITY PRIMARY KEY)` }, /identity column of type text/));
|
|
380
|
+
test("numeric identity", () => flagged("SQLPG121", { t: table`CREATE TABLE t (id numeric GENERATED BY DEFAULT AS IDENTITY)` }, /must be smallint, integer or bigint/));
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe("SQLPG122: duplicates in a table or enum", () => {
|
|
384
|
+
test("(id bigint, id int)", () => flagged("SQLPG122", { t: table`CREATE TABLE t (id bigint PRIMARY KEY, id int)` }, /declares the column id more than once/));
|
|
385
|
+
test("ENUM ('a', 'a')", () => flagged("SQLPG122", { m: pgType`CREATE TYPE m AS ENUM ('a', 'a')` }, /label 'a' more than once/));
|
|
386
|
+
test("labels differing in case are distinct", () => clean("SQLPG122", { m: pgType`CREATE TYPE m AS ENUM ('a', 'A')` }));
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("SQLPG123: column lists", () => {
|
|
390
|
+
const app = schema`CREATE SCHEMA app`;
|
|
391
|
+
const orders = table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, email text NOT NULL, total numeric NOT NULL)`;
|
|
392
|
+
test("PRIMARY KEY (idd)", () => flagged("SQLPG123", { t: table`CREATE TABLE t (id bigint, CONSTRAINT p PRIMARY KEY (idd))` }, /PRIMARY KEY names the column idd, which t does not declare/));
|
|
393
|
+
test("UNIQUE (emial)", () => flagged("SQLPG123", { t: table`CREATE TABLE t (id bigint PRIMARY KEY, email text, UNIQUE (emial))` }, /UNIQUE names the column emial/));
|
|
394
|
+
test("a foreign key's own columns", () => {
|
|
395
|
+
flagged("SQLPG123", { orders, t: table`CREATE TABLE t (id bigint PRIMARY KEY, FOREIGN KEY (order_idd) REFERENCES ${orders} (id))` }, /FOREIGN KEY to app.orders names the column order_idd/);
|
|
396
|
+
});
|
|
397
|
+
test("an index on a declared table", () => flagged("SQLPG123", { app, orders, i: index`CREATE INDEX orders_x ON ${orders} (emial)` }, /index on app.orders names the column emial/));
|
|
398
|
+
test("an index INCLUDE", () => flagged("SQLPG123", { app, orders, i: index`CREATE INDEX orders_x ON ${orders} (email) INCLUDE (totl)` }, /INCLUDE names the column totl/));
|
|
399
|
+
test("GRANT SELECT (totl) ON ${orders}", () => {
|
|
400
|
+
const r = role`CREATE ROLE app_reader NOLOGIN`;
|
|
401
|
+
flagged("SQLPG123", { app, orders, r, g: grant`GRANT SELECT (totl) ON ${orders} TO ${r}` }, /GRANT SELECT on app.orders names the column totl/);
|
|
402
|
+
});
|
|
403
|
+
test("index expressions, CHECKs, plain-text tables and LIKE are left to the server", () => {
|
|
404
|
+
clean("SQLPG123", {
|
|
405
|
+
app,
|
|
406
|
+
orders,
|
|
407
|
+
i: index`CREATE INDEX orders_l ON ${orders} ((lower(emial)))`,
|
|
408
|
+
j: index`CREATE INDEX x ON app.nope (id)`,
|
|
409
|
+
c: table`CREATE TABLE c (id bigint PRIMARY KEY, CHECK (amout > 0))`,
|
|
410
|
+
l: table`CREATE TABLE l (LIKE app.orders, PRIMARY KEY (id))`,
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("SQLPG124: foreign key targets", () => {
|
|
416
|
+
const app = schema`CREATE SCHEMA app`;
|
|
417
|
+
const customers = table`CREATE TABLE ${app}.customers (id bigint PRIMARY KEY, code text UNIQUE)`;
|
|
418
|
+
test("REFERENCES ${customers} (idd)", () => {
|
|
419
|
+
flagged("SQLPG124", { app, customers, o: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, customer_id bigint REFERENCES ${customers} (idd))` }, /to app.customers \(idd\), which app.customers does not declare/);
|
|
420
|
+
});
|
|
421
|
+
test("text to bigint", () => {
|
|
422
|
+
flagged("SQLPG124", { app, customers, o: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, customer_id text REFERENCES ${customers} (id))` }, /from customer_id \(text\) to app.customers \(id\) \(bigint\); the types do not compare/);
|
|
423
|
+
});
|
|
424
|
+
test("the primary key when no columns are named", () => {
|
|
425
|
+
flagged("SQLPG124", { app, customers, o: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, customer_id uuid REFERENCES ${customers})` }, /types do not compare/);
|
|
426
|
+
});
|
|
427
|
+
test("integer to bigint, varchar to text, and a plain-text target are clean", () => {
|
|
428
|
+
clean("SQLPG124", {
|
|
429
|
+
app,
|
|
430
|
+
customers,
|
|
431
|
+
o: table`CREATE TABLE ${app}.orders (id bigint PRIMARY KEY, a integer REFERENCES ${customers} (id), b varchar(10) REFERENCES ${customers} (code), c text REFERENCES app.elsewhere (id))`,
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe("SQLPG125: access methods and operator classes", () => {
|
|
437
|
+
const t = table`CREATE TABLE t (id bigint PRIMARY KEY, email text, total numeric, doc jsonb)`;
|
|
438
|
+
test("USING btreee", () => flagged("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} USING btreee (email)` }, /access method btreee, which Postgres 18 does not have/));
|
|
439
|
+
test("numeric_opz", () => flagged("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} (total numeric_opz)` }, /operator class numeric_opz, which Postgres 18 does not have for btree/));
|
|
440
|
+
test("an operator class of another method", () => flagged("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} (doc jsonb_path_ops)` }, /jsonb_path_ops, which is for gin, not btree/));
|
|
441
|
+
test("gin_trgm_ops without pg_trgm is an error naming it; with it, clean", () => {
|
|
442
|
+
const i = index`CREATE INDEX t_x ON ${t} USING gin (email gin_trgm_ops)`;
|
|
443
|
+
flagged("SQLPG125", { t, i }, /pg_trgm extension, which the project does not declare/);
|
|
444
|
+
clean("SQLPG125", { t, i, e: extension`CREATE EXTENSION pg_trgm` });
|
|
445
|
+
});
|
|
446
|
+
test("an operator class in an undeclared schema is left alone", () => clean("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} (email ext.my_ops)` }));
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe("SQLPG126: functions", () => {
|
|
450
|
+
test("DEFAULT noww()", () => flagged("SQLPG126", { t: table`CREATE TABLE t (id bigint PRIMARY KEY, at timestamptz DEFAULT noww())` }, /column at's DEFAULT calls noww\(\), which Postgres 18 does not have/));
|
|
451
|
+
test("a CHECK, a generated column, an index expression", () => {
|
|
452
|
+
const t = table`CREATE TABLE t (id bigint PRIMARY KEY, a text CHECK (lenght(a) > 0), b text GENERATED ALWAYS AS (lowr(a)) STORED)`;
|
|
453
|
+
const msgs = run("SQLPG126", { t, i: index`CREATE INDEX t_u ON ${t} ((uper(a)))` }).map((d) => d.message).join("\n");
|
|
454
|
+
expect(msgs).toMatch(/a CHECK calls lenght/);
|
|
455
|
+
expect(msgs).toMatch(/generated column b calls lowr/);
|
|
456
|
+
expect(msgs).toMatch(/index expression calls uper/);
|
|
457
|
+
});
|
|
458
|
+
test("a function newer than the target major", () => {
|
|
459
|
+
const t = table`CREATE TABLE t (id uuid DEFAULT uuidv7())`;
|
|
460
|
+
expect(run("SQLPG126", { t }, 17)[0]!.message).toMatch(/uuidv7\(\), which needs Postgres 18; Postgres 17 does not have it/);
|
|
461
|
+
expect(run("SQLPG126", { t }, 18)).toEqual([]);
|
|
462
|
+
});
|
|
463
|
+
test("with an extension declared, an unknown name is a warning", () => {
|
|
464
|
+
const d = run("SQLPG126", { e: extension`CREATE EXTENSION hstore`, t: table`CREATE TABLE t (a text DEFAULT make_widget())` });
|
|
465
|
+
expect(d[0]).toMatchObject({ severity: "warning" });
|
|
466
|
+
});
|
|
467
|
+
test("names in strings and quoted identifiers' casts are not calls", () => {
|
|
468
|
+
clean("SQLPG126", { t: table`CREATE TABLE t (a text DEFAULT 'noww()', b text CHECK (b <> 'x(y)'))` });
|
|
469
|
+
});
|
|
470
|
+
});
|