@intentius/chant-lexicon-sql 0.124.31 → 0.124.33
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/change-set.js +250 -0
- package/dist/clickhouse/access.js +722 -0
- package/dist/clickhouse/apply/apply.js +243 -0
- package/dist/clickhouse/apply/mutations.js +66 -0
- package/dist/clickhouse/apply/statements.js +545 -0
- package/dist/clickhouse/catalog-types.js +7 -0
- package/dist/clickhouse/container.js +152 -0
- package/dist/clickhouse/engine-syntax.js +159 -0
- package/dist/clickhouse/entities.js +593 -0
- package/dist/clickhouse/expressions.js +107 -0
- package/dist/clickhouse/http.js +55 -0
- package/dist/clickhouse/import/generator.js +192 -0
- package/dist/clickhouse/import/inlined.js +207 -0
- package/dist/clickhouse/import/ir.js +160 -0
- package/dist/clickhouse/import/live-export.js +131 -0
- package/dist/clickhouse/import/parser.js +17 -0
- package/dist/clickhouse/live/bind.js +116 -0
- package/dist/clickhouse/live/catalog.js +223 -0
- package/dist/clickhouse/live/describe-resources.js +91 -0
- package/dist/clickhouse/live/undeclarable.js +59 -0
- package/dist/clickhouse/overlays/codecs.js +57 -0
- package/dist/clickhouse/overlays/engines.js +101 -0
- package/dist/clickhouse/overlays/kinds.js +1 -0
- package/dist/clickhouse/overlays/projections.js +26 -0
- package/dist/clickhouse/overlays/skip-indexes.js +40 -0
- package/dist/clickhouse/overlays/ttl.js +34 -0
- package/dist/clickhouse/overlays/types.js +59 -0
- package/dist/clickhouse/ownership.js +83 -0
- package/dist/clickhouse/parser.js +674 -0
- package/dist/clickhouse/plan/commands.js +159 -0
- package/dist/clickhouse/plan/deep.js +345 -0
- package/dist/clickhouse/plan/diff.js +312 -0
- package/dist/clickhouse/plan/disruption.js +73 -0
- package/dist/clickhouse/plan/normalize.js +545 -0
- package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
- package/dist/clickhouse/plan/report.js +26 -0
- package/dist/clickhouse/plan/rules.js +72 -0
- package/dist/clickhouse/plan/schema.js +67 -0
- package/dist/clickhouse/plan/server-format.js +35 -0
- package/dist/clickhouse/rebuild/backfill.js +265 -0
- package/dist/clickhouse/rebuild/engines.js +146 -0
- package/dist/clickhouse/rebuild/observe.js +260 -0
- package/dist/clickhouse/rebuild/op.js +152 -0
- package/dist/clickhouse/rebuild/partitions.js +65 -0
- package/dist/clickhouse/rebuild/receipts.js +120 -0
- package/dist/clickhouse/rebuild/replicas.js +121 -0
- package/dist/clickhouse/rebuild/shards.js +76 -0
- package/dist/clickhouse/rebuild/steps.js +383 -0
- package/dist/clickhouse/rebuild/verify.js +279 -0
- package/dist/clickhouse/row-types.js +151 -0
- package/dist/clickhouse/testing/fake-server.js +47 -0
- package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
- package/dist/clickhouse/testing/writable-server.js +123 -0
- package/dist/clickhouse/tokens.js +23 -0
- package/dist/clickhouse/topology.js +463 -0
- package/dist/clickhouse/type-syntax.js +73 -0
- package/dist/clickhouse.js +25 -0
- package/dist/codegen/classifier-page.js +93 -0
- package/dist/codegen/clickhouse-module.js +249 -0
- package/dist/codegen/coverage-page.js +70 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +222 -0
- package/dist/codegen/generate-cli.js +13 -0
- package/dist/codegen/generate.js +97 -0
- package/dist/codegen/package.js +28 -0
- package/dist/codegen/postgres-classifier-page.js +95 -0
- package/dist/codegen/postgres-module.js +418 -0
- package/dist/codegen/registry.js +52 -0
- package/dist/composites/audit-log-table.js +35 -0
- package/dist/composites/catalog.js +524 -0
- package/dist/composites/cdc-mirror.js +32 -0
- package/dist/composites/clickhouse.js +6 -0
- package/dist/composites/events-table.js +26 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/join-table.js +25 -0
- package/dist/composites/postgres.js +6 -0
- package/dist/composites/refreshed-view.js +24 -0
- package/dist/composites/replacing-table.js +28 -0
- package/dist/composites/rollup-view.js +29 -0
- package/dist/composites/sharded-table.js +27 -0
- package/dist/composites/soft-delete-table.js +34 -0
- package/dist/composites/tenant-table.js +26 -0
- package/dist/composites/typed.js +8 -0
- package/dist/config.js +206 -0
- package/dist/core/apply.js +93 -0
- package/dist/core/classifier.js +115 -0
- package/dist/core/container.js +69 -0
- package/dist/core/cursor.js +261 -0
- package/dist/core/diff.js +48 -0
- package/dist/core/entity.js +58 -0
- package/dist/core/find-templates.js +118 -0
- package/dist/core/foreign-tables.js +27 -0
- package/dist/core/handoff.js +32 -0
- package/dist/core/index.js +64 -0
- package/dist/core/interpolation.js +191 -0
- package/dist/core/normalize.js +43 -0
- package/dist/core/ownership.js +119 -0
- package/dist/core/receipts.js +22 -0
- package/dist/core/references.js +61 -0
- package/dist/core/statements.js +42 -0
- package/dist/core/template.js +79 -0
- package/dist/core/testing/http-stub.js +21 -0
- package/dist/core/tokens.js +202 -0
- package/dist/core/typed.js +40 -0
- package/dist/core/undeclarable.js +46 -0
- package/dist/coverage.js +161 -0
- package/dist/detect.js +32 -0
- package/dist/dialects.js +18 -0
- package/dist/files/build-root.js +72 -0
- package/dist/files/clickhouse.js +138 -0
- package/dist/files/common.js +58 -0
- package/dist/files/postgres.js +389 -0
- package/dist/generated/clickhouse-types.js +5 -0
- package/dist/generated/clickhouse.js +4361 -0
- package/dist/generated/postgres-types.js +227 -0
- package/dist/generated/postgres.js +3667 -0
- package/dist/import-generator.js +14 -0
- package/dist/import-parser.js +71 -0
- package/dist/index.js +24 -0
- package/dist/init-templates.js +290 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +31 -0
- package/dist/lint/audit-lineage.js +22 -0
- package/dist/lint/post-synth/audit-entries.js +34 -0
- package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
- package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
- package/dist/lint/post-synth/index.js +112 -0
- package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
- package/dist/lint/post-synth/postgres-helpers.js +65 -0
- package/dist/lint/post-synth/postgres-names.js +507 -0
- package/dist/lint/post-synth/sql-helpers.js +23 -0
- package/dist/lint/post-synth/sql101.js +59 -0
- package/dist/lint/post-synth/sql102.js +34 -0
- package/dist/lint/post-synth/sqlch101.js +34 -0
- package/dist/lint/post-synth/sqlch102.js +23 -0
- package/dist/lint/post-synth/sqlch103.js +45 -0
- package/dist/lint/post-synth/sqlch104.js +41 -0
- package/dist/lint/post-synth/sqlch105.js +36 -0
- package/dist/lint/post-synth/sqlch106.js +27 -0
- package/dist/lint/post-synth/sqlch107.js +39 -0
- package/dist/lint/post-synth/sqlch108.js +28 -0
- package/dist/lint/post-synth/sqlch109.js +21 -0
- package/dist/lint/post-synth/sqlch110.js +31 -0
- package/dist/lint/post-synth/sqlch111.js +41 -0
- package/dist/lint/post-synth/sqlch112.js +28 -0
- package/dist/lint/post-synth/sqlch113.js +19 -0
- package/dist/lint/post-synth/sqlch114.js +26 -0
- package/dist/lint/post-synth/sqlch115.js +30 -0
- package/dist/lint/post-synth/sqlch116.js +22 -0
- package/dist/lint/post-synth/sqlch117.js +26 -0
- package/dist/lint/post-synth/sqlch118.js +27 -0
- package/dist/lint/post-synth/sqlch119.js +32 -0
- package/dist/lint/post-synth/sqlch120.js +20 -0
- package/dist/lint/post-synth/sqlch121.js +11 -0
- package/dist/lint/post-synth/sqlch122.js +11 -0
- package/dist/lint/post-synth/sqlch123.js +10 -0
- package/dist/lint/post-synth/sqlch124.js +21 -0
- package/dist/lint/post-synth/sqlch125.js +55 -0
- package/dist/lint/post-synth/sqlch126.js +62 -0
- package/dist/lint/post-synth/sqlch127.js +132 -0
- package/dist/lint/post-synth/sqlpg101.js +25 -0
- package/dist/lint/post-synth/sqlpg102.js +35 -0
- package/dist/lint/post-synth/sqlpg103.js +23 -0
- package/dist/lint/post-synth/sqlpg104.js +23 -0
- package/dist/lint/post-synth/sqlpg105.js +22 -0
- package/dist/lint/post-synth/sqlpg106.js +22 -0
- package/dist/lint/post-synth/sqlpg107.js +22 -0
- package/dist/lint/post-synth/sqlpg108.js +28 -0
- package/dist/lint/post-synth/sqlpg109.js +39 -0
- package/dist/lint/post-synth/sqlpg110.js +38 -0
- package/dist/lint/post-synth/sqlpg111.js +23 -0
- package/dist/lint/post-synth/sqlpg112.js +25 -0
- package/dist/lint/post-synth/sqlpg113.js +29 -0
- package/dist/lint/post-synth/sqlpg114.js +39 -0
- package/dist/lint/post-synth/sqlpg115.js +48 -0
- package/dist/lint/post-synth/sqlpg116.js +26 -0
- package/dist/lint/post-synth/sqlpg117.js +24 -0
- package/dist/lint/post-synth/sqlpg118.js +22 -0
- package/dist/lint/post-synth/sqlpg119.js +41 -0
- package/dist/lint/post-synth/sqlpg120.js +83 -0
- package/dist/lint/post-synth/sqlpg121.js +33 -0
- package/dist/lint/post-synth/sqlpg122.js +27 -0
- package/dist/lint/post-synth/sqlpg123.js +69 -0
- package/dist/lint/post-synth/sqlpg124.js +50 -0
- package/dist/lint/post-synth/sqlpg125.js +41 -0
- package/dist/lint/post-synth/sqlpg126.js +38 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/postgres-templates.js +27 -0
- package/dist/lint/rules/sqlch001.js +79 -0
- package/dist/lint/rules/sqlch002.js +124 -0
- package/dist/lint/rules/sqlch003.js +69 -0
- package/dist/lint/rules/sqlch004.js +83 -0
- package/dist/lint/rules/sqlpg001.js +68 -0
- package/dist/lint/rules/sqlpg002.js +69 -0
- package/dist/lint/rules/sqlpg003.js +58 -0
- package/dist/lint/rules/sqlpg004.js +77 -0
- package/dist/lint/rules/sqlpg005.js +86 -0
- package/dist/lint/rules/templates.js +25 -0
- package/dist/lint/rules/typed-names.js +132 -0
- package/dist/live-dialect.js +32 -0
- package/dist/lsp/catalog.js +44 -0
- package/dist/lsp/completions.js +136 -0
- package/dist/lsp/hover.js +115 -0
- package/dist/lsp/postgres-catalog.js +176 -0
- package/dist/lsp/postgres-template.js +171 -0
- package/dist/lsp/postgres.js +266 -0
- package/dist/lsp/refs.js +60 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +19 -0
- package/dist/lsp/template.js +143 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +293 -0
- package/dist/mcp/postgres.js +117 -0
- package/dist/migration-statements.js +243 -0
- package/dist/op/activities/clickhouse-apply.js +83 -0
- package/dist/op/activities/clickhouse-emulator.js +53 -0
- package/dist/op/activities/clickhouse-rebuild.js +119 -0
- package/dist/op/activities/index.js +26 -0
- package/dist/op/activities/postgres-apply.js +126 -0
- package/dist/op/activities/postgres-emulator.js +70 -0
- package/dist/op/activities/postgres-migration.js +156 -0
- package/dist/op/activities/sql-exec.js +57 -0
- package/dist/op/activity-contracts.js +103 -0
- package/dist/package-cli.js +14 -0
- package/dist/plugin.js +259 -0
- package/dist/postgres/access/acl.js +344 -0
- package/dist/postgres/access/changes.js +30 -0
- package/dist/postgres/access/import.js +61 -0
- package/dist/postgres/access/live.js +96 -0
- package/dist/postgres/apply/apply.js +557 -0
- package/dist/postgres/apply/statements.js +882 -0
- package/dist/postgres/catalog-types.js +18 -0
- package/dist/postgres/container.js +58 -0
- package/dist/postgres/entities.js +1390 -0
- package/dist/postgres/entity-types.js +28 -0
- package/dist/postgres/import/generator.js +283 -0
- package/dist/postgres/import/ir.js +53 -0
- package/dist/postgres/import/live-export.js +83 -0
- package/dist/postgres/keywords.js +32 -0
- package/dist/postgres/live/bind.js +130 -0
- package/dist/postgres/live/catalog-kinds.js +301 -0
- package/dist/postgres/live/catalog.js +644 -0
- package/dist/postgres/live/client.js +151 -0
- package/dist/postgres/live/describe-resources.js +135 -0
- package/dist/postgres/live/undeclarable.js +111 -0
- package/dist/postgres/migrate/backfill.js +170 -0
- package/dist/postgres/migrate/batches.js +122 -0
- package/dist/postgres/migrate/carry.js +518 -0
- package/dist/postgres/migrate/handoff.js +59 -0
- package/dist/postgres/migrate/names.js +61 -0
- package/dist/postgres/migrate/observe.js +360 -0
- package/dist/postgres/migrate/op.js +178 -0
- package/dist/postgres/migrate/receipts.js +117 -0
- package/dist/postgres/migrate/replication.js +106 -0
- package/dist/postgres/migrate/steps.js +514 -0
- package/dist/postgres/overlays/columns.js +31 -0
- package/dist/postgres/overlays/constraints.js +52 -0
- package/dist/postgres/overlays/extension-objects.js +88 -0
- package/dist/postgres/overlays/indexes.js +25 -0
- package/dist/postgres/overlays/kinds.js +2 -0
- package/dist/postgres/overlays/partitioning.js +23 -0
- package/dist/postgres/overlays/relations.js +25 -0
- package/dist/postgres/overlays/storage.js +65 -0
- package/dist/postgres/overlays/type-ddl.js +36 -0
- package/dist/postgres/overlays/types.js +74 -0
- package/dist/postgres/parser.js +1824 -0
- package/dist/postgres/plan/commands.js +137 -0
- package/dist/postgres/plan/deep.js +324 -0
- package/dist/postgres/plan/diff.js +499 -0
- package/dist/postgres/plan/disruption.js +64 -0
- package/dist/postgres/plan/normalize.js +889 -0
- package/dist/postgres/plan/report.js +21 -0
- package/dist/postgres/plan/rules.js +115 -0
- package/dist/postgres/plan/schema.js +105 -0
- package/dist/postgres/plan/server-normalize.js +166 -0
- package/dist/postgres/providers/aurora.js +44 -0
- package/dist/postgres/providers/azure.js +53 -0
- package/dist/postgres/providers/cloud-sql.js +41 -0
- package/dist/postgres/providers/index.js +73 -0
- package/dist/postgres/providers/neon.js +44 -0
- package/dist/postgres/providers/rds.js +47 -0
- package/dist/postgres/providers/supabase.js +45 -0
- package/dist/postgres/providers/types.js +21 -0
- package/dist/postgres/row-types.js +107 -0
- package/dist/postgres/testing/fake-client.js +34 -0
- package/dist/postgres/testing/migration.js +48 -0
- package/dist/postgres/testing/roundtrip-schema.js +64 -0
- package/dist/postgres/testing/server.js +47 -0
- package/dist/postgres/testing/writable-server.js +160 -0
- package/dist/postgres/tokens.js +41 -0
- package/dist/postgres.js +15 -0
- package/dist/read/clickhouse.js +82 -0
- package/dist/read/objects.js +44 -0
- package/dist/read/postgres.js +104 -0
- package/dist/read.js +52 -0
- package/dist/receipts.js +207 -0
- package/dist/serializer.js +172 -0
- package/dist/skill-defs.js +93 -0
- package/dist/spec/catalog.js +191 -0
- package/dist/spec/fetch.d.ts.map +1 -1
- package/dist/spec/fetch.js +90 -0
- package/dist/spec/pin.js +42 -0
- package/dist/spec/postgres-catalog.js +171 -0
- package/dist/spec/postgres-fetch.js +91 -0
- package/dist/spec/postgres-pin.js +82 -0
- package/dist/sql-files.js +206 -0
- package/dist/sql-types-command.js +97 -0
- package/dist/sql-types.js +323 -0
- package/dist/token-source.js +156 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate.js +151 -0
- package/dist/workspace-outputs.js +191 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/lint/rules/typescript-7.test.ts +2 -1
- package/src/lsp/catalog.ts +2 -2
- package/src/lsp/postgres-catalog.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/mcp/index.ts +4 -2
- package/src/spec/fetch.ts +2 -2
- package/src/spec/postgres-fetch.ts +2 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { STORAGE_PARAMETERS } from "../../generated/postgres.js";
|
|
2
|
+
import { checkOf, rangeOf, storageParams, storageTarget, targetMajor } from "./postgres-helpers.js";
|
|
3
|
+
import { postgresObjects } from "./sql-helpers.js";
|
|
4
|
+
/**
|
|
5
|
+
* SQLPG114: a storage parameter the object's kind does not accept at the
|
|
6
|
+
* target major (`postgresMajor` in the output).
|
|
7
|
+
*
|
|
8
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createtable.html#SQL-CREATETABLE-STORAGE-PARAMETERS
|
|
9
|
+
* (and the CREATE INDEX, CREATE VIEW and CREATE MATERIALIZED VIEW pages). The
|
|
10
|
+
* server refuses an unrecognized parameter at CREATE. The accepted names and
|
|
11
|
+
* the relation kinds they apply to come from the generated catalog, whose
|
|
12
|
+
* since/until data says which majors have each one: a parameter removed
|
|
13
|
+
* before the target major is reported as removed.
|
|
14
|
+
*/
|
|
15
|
+
export const sqlpg114 = checkOf({ id: "SQLPG114", description: "A storage parameter the object's kind or the target major does not accept" }, (ctx, report) => {
|
|
16
|
+
const major = targetMajor(ctx);
|
|
17
|
+
const specs = STORAGE_PARAMETERS;
|
|
18
|
+
for (const o of postgresObjects(ctx)) {
|
|
19
|
+
const target = storageTarget(o);
|
|
20
|
+
if (!target)
|
|
21
|
+
continue;
|
|
22
|
+
for (const raw of storageParams(o.with).keys()) {
|
|
23
|
+
if (raw.startsWith("toast."))
|
|
24
|
+
continue;
|
|
25
|
+
const spec = specs[raw];
|
|
26
|
+
if (!spec) {
|
|
27
|
+
report({ severity: "error", message: `${o.export} (${o.sqlName}) sets ${raw}, which is not a Postgres ${major} storage parameter`, entity: o.export });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const range = rangeOf("storageParameters", `${target}.${raw}`);
|
|
31
|
+
if (!spec.targets.includes(target)) {
|
|
32
|
+
report({ severity: "error", message: `${o.export} (${o.sqlName}) sets ${raw}, which applies to ${spec.targets.join(", ")} and not to a ${target}`, entity: o.export });
|
|
33
|
+
}
|
|
34
|
+
else if (range?.until !== undefined && range.until < major) {
|
|
35
|
+
report({ severity: "error", message: `${o.export} (${o.sqlName}) sets ${raw}, which Postgres removed after ${range.until}`, entity: o.export });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { checkOf, rangeOf, storageParams, storageTarget, tablesOf, targetMajor } from "./postgres-helpers.js";
|
|
2
|
+
import { indexesOf } from "./postgres-helpers.js";
|
|
3
|
+
import { postgresObjects } from "./sql-helpers.js";
|
|
4
|
+
/**
|
|
5
|
+
* SQLPG115: a feature newer than the target major.
|
|
6
|
+
*
|
|
7
|
+
* Doc: https://www.postgresql.org/docs/18/release-18.html (NOT ENFORCED
|
|
8
|
+
* constraints and virtual generated columns arrive in 18),
|
|
9
|
+
* https://www.postgresql.org/docs/15/release-15.html (NULLS NOT DISTINCT). Storage parameters read the generated catalog's
|
|
10
|
+
* `since` data. The target is `sql.postgresMajor`
|
|
11
|
+
* (recorded as `postgresMajor` in the output), else the newest pinned major;
|
|
12
|
+
* a feature is reported as a warning only when it is newer than the target,
|
|
13
|
+
* naming the first major that has it.
|
|
14
|
+
*/
|
|
15
|
+
export const sqlpg115 = checkOf({ id: "SQLPG115", description: "A feature newer than the target major" }, (ctx, report) => {
|
|
16
|
+
const major = targetMajor(ctx);
|
|
17
|
+
const note = (entity, what, since) => report({ severity: "warning", message: `${entity} uses ${what}, which needs Postgres ${since}; Postgres ${major} refuses it`, entity });
|
|
18
|
+
for (const o of postgresObjects(ctx)) {
|
|
19
|
+
const target = storageTarget(o);
|
|
20
|
+
if (!target)
|
|
21
|
+
continue;
|
|
22
|
+
for (const name of storageParams(o.with).keys()) {
|
|
23
|
+
// SQLPG117 asks every view for security_invoker (15), so it is not also a portability finding.
|
|
24
|
+
if (name === "security_invoker")
|
|
25
|
+
continue;
|
|
26
|
+
const since = rangeOf("storageParameters", `${target}.${name}`)?.since;
|
|
27
|
+
if (since !== undefined && since > major)
|
|
28
|
+
note(o.export, `the storage parameter ${name}`, since);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const t of tablesOf(ctx)) {
|
|
32
|
+
for (const c of t.checks)
|
|
33
|
+
if (c.notEnforced !== undefined && major < 18)
|
|
34
|
+
note(t.export, `a NOT ENFORCED / ENFORCED constraint${c.name ? ` ${c.name}` : ""}`, 18);
|
|
35
|
+
for (const fk of t.foreignKeys)
|
|
36
|
+
if (fk.notEnforced !== undefined && major < 18)
|
|
37
|
+
note(t.export, `a NOT ENFORCED / ENFORCED foreign key${fk.name ? ` ${fk.name}` : ""}`, 18);
|
|
38
|
+
for (const c of t.columns)
|
|
39
|
+
if (c.generated?.kind === "virtual" && major < 18)
|
|
40
|
+
note(t.export, `the virtual generated column ${c.name}`, 18);
|
|
41
|
+
const nulls = [...t.uniques, ...(t.primaryKey ? [t.primaryKey] : [])].some((k) => k.nullsNotDistinct);
|
|
42
|
+
if (nulls && major < 15)
|
|
43
|
+
note(t.export, "NULLS NOT DISTINCT", 15);
|
|
44
|
+
}
|
|
45
|
+
for (const i of indexesOf(ctx))
|
|
46
|
+
if (i.nullsNotDistinct && major < 15)
|
|
47
|
+
note(i.export, "NULLS NOT DISTINCT", 15);
|
|
48
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { checkOf, rangeOf, targetMajor } from "./postgres-helpers.js";
|
|
2
|
+
import { postgresObjects } from "./sql-helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG116: an extension the target major no longer ships.
|
|
5
|
+
*
|
|
6
|
+
* Doc: https://www.postgresql.org/docs/18/contrib.html and the release notes
|
|
7
|
+
* of the major that dropped it (adminpack and old_snapshot are gone after 16,
|
|
8
|
+
* https://www.postgresql.org/docs/17/release-17.html). The target is `postgresMajor` in the output. The extension names
|
|
9
|
+
* and their since/until majors come from the generated catalog, read from
|
|
10
|
+
* pg_available_extensions of each major's server image.
|
|
11
|
+
*/
|
|
12
|
+
export const sqlpg116 = checkOf({ id: "SQLPG116", description: "An extension the target major no longer ships" }, (ctx, report) => {
|
|
13
|
+
const major = targetMajor(ctx);
|
|
14
|
+
for (const o of postgresObjects(ctx)) {
|
|
15
|
+
if (o.type !== "Postgres::Extension")
|
|
16
|
+
continue;
|
|
17
|
+
const until = rangeOf("extensions", o.name)?.until;
|
|
18
|
+
if (until === undefined || until >= major)
|
|
19
|
+
continue;
|
|
20
|
+
report({
|
|
21
|
+
severity: "error",
|
|
22
|
+
message: `${o.export} installs the ${o.name} extension, which Postgres ${major} does not ship (last in ${until})`,
|
|
23
|
+
entity: o.export,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { checkOf, isView, storageParams } from "./postgres-helpers.js";
|
|
2
|
+
import { postgresObjects } from "./sql-helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG117: a view that runs with its owner's privileges.
|
|
5
|
+
*
|
|
6
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createview.html ("by default,
|
|
7
|
+
* access to the underlying base relations referenced in the view is determined
|
|
8
|
+
* by the permissions of the view owner"; with security_invoker = true it is the
|
|
9
|
+
* caller's, and the base tables' row-level security applies to the caller).
|
|
10
|
+
* A view over tables with RLS otherwise hands the owner's view of the rows to
|
|
11
|
+
* every role that can select from it.
|
|
12
|
+
*/
|
|
13
|
+
export const sqlpg117 = checkOf({ id: "SQLPG117", description: "A view without security_invoker runs with its owner's privileges" }, (ctx, report) => {
|
|
14
|
+
for (const v of postgresObjects(ctx).filter(isView)) {
|
|
15
|
+
const flag = storageParams(v.with).get("security_invoker");
|
|
16
|
+
if (flag === "true" || flag === "on" || flag === "1" || flag === "'true'" || flag === "'on'")
|
|
17
|
+
continue;
|
|
18
|
+
report({
|
|
19
|
+
severity: "warning",
|
|
20
|
+
message: `${v.export} (${v.sqlName}) does not set security_invoker = true, so it reads its tables as its owner and skips the caller's row-level security`,
|
|
21
|
+
entity: v.export,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
/**
|
|
3
|
+
* SQLPG118: a table that uses INHERITS.
|
|
4
|
+
*
|
|
5
|
+
* Doc: https://www.postgresql.org/docs/18/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-BEST-PRACTICES
|
|
6
|
+
* and https://www.postgresql.org/docs/18/ddl-inherit.html#DDL-INHERIT-CAVEATS
|
|
7
|
+
* (indexes, unique constraints and foreign keys are not inherited, so a
|
|
8
|
+
* constraint on the parent does not cover the children). Declarative
|
|
9
|
+
* partitioning (PARTITION BY ... / PARTITION OF) replaced inheritance for
|
|
10
|
+
* splitting a table; INHERITS is kept for its other uses.
|
|
11
|
+
*/
|
|
12
|
+
export const sqlpg118 = checkOf({ id: "SQLPG118", description: "A table uses INHERITS where declarative partitioning replaced it" }, (ctx, report) => {
|
|
13
|
+
for (const t of tablesOf(ctx)) {
|
|
14
|
+
if (t.inherits.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
report({
|
|
17
|
+
severity: "warning",
|
|
18
|
+
message: `${t.export} (${t.sqlName}) uses INHERITS; its parent's unique and foreign keys do not cover it, so use PARTITION OF for a split table`,
|
|
19
|
+
entity: t.export,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { parseType, projectOf, resolveType, typeLabel } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG119: a column, array element, domain base or sequence `AS` type the
|
|
5
|
+
* target major does not have.
|
|
6
|
+
*
|
|
7
|
+
* Doc: https://www.postgresql.org/docs/18/datatype.html. The server refuses
|
|
8
|
+
* `type "numerik" does not exist` at CREATE. A built-in type counts in every
|
|
9
|
+
* spelling Postgres accepts: the catalog's `pg_type` name and `format_type()`
|
|
10
|
+
* name (`int4`, `integer`), and the grammar's own spellings (`int`, `decimal`,
|
|
11
|
+
* `double precision`, `time with time zone`), an overlay beside the catalog.
|
|
12
|
+
* So does a type the project declares (`type`, `domain`, a table's row type),
|
|
13
|
+
* by `${}` or by name. A type qualified with a schema the project does not
|
|
14
|
+
* declare is a plain-text reference and left to the server. An unknown name is
|
|
15
|
+
* a warning when the project declares an extension, since extension types are
|
|
16
|
+
* not in the core catalog; the overlay of common extension types answers for
|
|
17
|
+
* the ones it knows.
|
|
18
|
+
*/
|
|
19
|
+
export const sqlpg119 = checkOf({ id: "SQLPG119", description: "A column, domain or sequence type the target major does not have" }, (ctx, report) => {
|
|
20
|
+
const p = projectOf(ctx);
|
|
21
|
+
const check = (entity, sqlName, what, text, serial) => {
|
|
22
|
+
const t = parseType(text);
|
|
23
|
+
if (!t)
|
|
24
|
+
return;
|
|
25
|
+
const r = resolveType(p, t, { serial });
|
|
26
|
+
if (r.kind !== "missing")
|
|
27
|
+
return;
|
|
28
|
+
const kind = t.array ? "array element type" : "type";
|
|
29
|
+
report({ severity: r.miss.severity, message: `${entity} (${sqlName}) ${what} ${kind} ${typeLabel(t)}, which Postgres ${p.major} does not have${r.miss.note}`, entity });
|
|
30
|
+
};
|
|
31
|
+
for (const t of tablesOf(ctx)) {
|
|
32
|
+
for (const c of t.columns)
|
|
33
|
+
check(t.export, t.sqlName, `column ${c.name} has`, c.type, true);
|
|
34
|
+
}
|
|
35
|
+
for (const o of p.objects) {
|
|
36
|
+
if (o.type === "Postgres::Domain")
|
|
37
|
+
check(o.export, o.sqlName, "is a domain over", o.dataType, false);
|
|
38
|
+
if (o.type === "Postgres::Sequence")
|
|
39
|
+
check(o.export, o.sqlName, "is a sequence of", o.dataType, false);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { parametersOf, parseType, projectOf, resolveType, typeLabel } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG120: a type modifier that does not fit the type.
|
|
5
|
+
*
|
|
6
|
+
* Doc: https://www.postgresql.org/docs/18/datatype-numeric.html,
|
|
7
|
+
* https://www.postgresql.org/docs/18/datatype-character.html,
|
|
8
|
+
* https://www.postgresql.org/docs/18/datatype-datetime.html,
|
|
9
|
+
* https://www.postgresql.org/docs/18/datatype-bit.html. The server refuses a
|
|
10
|
+
* modifier on a type that takes none (`bigint(8)`), `numeric` precision
|
|
11
|
+
* outside 1 to 1000 or scale outside -1000 to 1000 (0 to the precision before
|
|
12
|
+
* 15), `varchar(n)` or `char(n)` outside 1 to 10485760, `timestamp`, `time`
|
|
13
|
+
* or `interval` precision outside 0 to 6, `bit(n)` below 1, and `float(p)`
|
|
14
|
+
* outside 1 to 53. The parameters and ranges are the lexicon's type overlay.
|
|
15
|
+
* Only built-in types are checked: a declared or extension type's modifiers
|
|
16
|
+
* are its own (`geometry(Point, 4326)`).
|
|
17
|
+
*/
|
|
18
|
+
export const sqlpg120 = checkOf({ id: "SQLPG120", description: "A type modifier that does not fit the type" }, (ctx, report) => {
|
|
19
|
+
const p = projectOf(ctx);
|
|
20
|
+
const check = (entity, sqlName, what, text, serial) => {
|
|
21
|
+
const t = parseType(text);
|
|
22
|
+
if (!t)
|
|
23
|
+
return;
|
|
24
|
+
const problem = modifierProblem(p, t, serial);
|
|
25
|
+
if (problem)
|
|
26
|
+
report({ severity: "error", message: `${entity} (${sqlName}) ${what} ${text.trim()}: ${problem}`, entity });
|
|
27
|
+
};
|
|
28
|
+
for (const t of tablesOf(ctx))
|
|
29
|
+
for (const c of t.columns)
|
|
30
|
+
check(t.export, t.sqlName, `column ${c.name} has type`, c.type, true);
|
|
31
|
+
for (const o of p.objects) {
|
|
32
|
+
if (o.type === "Postgres::Domain")
|
|
33
|
+
check(o.export, o.sqlName, "is a domain over", o.dataType, false);
|
|
34
|
+
if (o.type === "Postgres::Sequence")
|
|
35
|
+
check(o.export, o.sqlName, "is a sequence of", o.dataType, false);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const int = (s) => (/^[+-]?\d+$/.test(s.trim()) ? Number(s.trim()) : undefined);
|
|
39
|
+
function modifierProblem(p, t, serial) {
|
|
40
|
+
if (t.schema !== undefined || t.quoted)
|
|
41
|
+
return undefined;
|
|
42
|
+
const mods = t.modifiers;
|
|
43
|
+
if (t.name === "float" && mods) {
|
|
44
|
+
const n = mods.length === 1 ? int(mods[0]) : undefined;
|
|
45
|
+
if (mods.length !== 1)
|
|
46
|
+
return "float takes one precision";
|
|
47
|
+
if (n !== undefined && (n < 1 || n > 53))
|
|
48
|
+
return `float precision ${n} is outside 1 to 53`;
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const r = resolveType(p, t, { serial });
|
|
52
|
+
if (r.kind !== "builtin")
|
|
53
|
+
return undefined;
|
|
54
|
+
const params = parametersOf(r.canonical);
|
|
55
|
+
if (t.fields !== undefined) {
|
|
56
|
+
const fields = params.find((a) => a.kind === "keyword")?.values ?? [];
|
|
57
|
+
if (!fields.includes(t.fields))
|
|
58
|
+
return `${t.fields} is not an interval field (${fields.join(", ")})`;
|
|
59
|
+
}
|
|
60
|
+
if (!mods)
|
|
61
|
+
return undefined;
|
|
62
|
+
const numbers = params.filter((a) => a.kind === "number");
|
|
63
|
+
if (numbers.length === 0)
|
|
64
|
+
return `${typeLabel(t)} takes no type modifier`;
|
|
65
|
+
if (mods.length > numbers.length)
|
|
66
|
+
return `${typeLabel(t)} takes at most ${numbers.length} modifier${numbers.length === 1 ? "" : "s"}`;
|
|
67
|
+
const values = mods.map(int);
|
|
68
|
+
for (let i = 0; i < mods.length; i++) {
|
|
69
|
+
const v = values[i];
|
|
70
|
+
const spec = numbers[i];
|
|
71
|
+
if (v === undefined || !spec.range)
|
|
72
|
+
continue;
|
|
73
|
+
let [lo, hi] = spec.range;
|
|
74
|
+
// numeric's scale: 0 to the precision before 15, -1000 to 1000 since.
|
|
75
|
+
if (r.canonical === "numeric" && spec.name === "scale" && p.major < 15) {
|
|
76
|
+
lo = 0;
|
|
77
|
+
hi = values[0] ?? hi;
|
|
78
|
+
}
|
|
79
|
+
if (v < lo || v > hi)
|
|
80
|
+
return `${spec.name} ${v} is outside ${lo} to ${hi}${r.canonical === "numeric" && spec.name === "scale" && p.major < 15 ? ` at Postgres ${p.major}` : ""}`;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { parseType, projectOf, resolveType } from "./postgres-names.js";
|
|
3
|
+
const IDENTITY_TYPES = new Set(["smallint", "integer", "bigint"]);
|
|
4
|
+
/**
|
|
5
|
+
* SQLPG121: an identity column whose type is not `smallint`, `integer` or
|
|
6
|
+
* `bigint`.
|
|
7
|
+
*
|
|
8
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createtable.html ("GENERATED {
|
|
9
|
+
* ALWAYS | BY DEFAULT } AS IDENTITY ... The column ... must be of type
|
|
10
|
+
* smallint, integer or bigint"). The server refuses `identity column type must
|
|
11
|
+
* be smallint, integer, or bigint` at CREATE. A declared type (a domain) is
|
|
12
|
+
* left to the server.
|
|
13
|
+
*/
|
|
14
|
+
export const sqlpg121 = checkOf({ id: "SQLPG121", description: "An identity column whose type is not smallint, integer or bigint" }, (ctx, report) => {
|
|
15
|
+
const p = projectOf(ctx);
|
|
16
|
+
for (const t of tablesOf(ctx)) {
|
|
17
|
+
for (const c of t.columns) {
|
|
18
|
+
if (c.generated?.kind !== "identity")
|
|
19
|
+
continue;
|
|
20
|
+
const parsed = parseType(c.type);
|
|
21
|
+
if (!parsed)
|
|
22
|
+
continue;
|
|
23
|
+
const r = resolveType(p, parsed, { serial: true });
|
|
24
|
+
if (r.kind !== "builtin" || (IDENTITY_TYPES.has(r.canonical) && !parsed.array))
|
|
25
|
+
continue;
|
|
26
|
+
report({
|
|
27
|
+
severity: "error",
|
|
28
|
+
message: `${t.export} (${t.sqlName}) column ${c.name} is an identity column of type ${c.type}; an identity column must be smallint, integer or bigint`,
|
|
29
|
+
entity: t.export,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { postgresObjects } from "./sql-helpers.js";
|
|
3
|
+
const repeated = (xs) => [...new Set(xs.filter((x, i) => xs.indexOf(x) !== i))];
|
|
4
|
+
/**
|
|
5
|
+
* SQLPG122: a table that declares a column twice, or an enum that lists a
|
|
6
|
+
* label twice.
|
|
7
|
+
*
|
|
8
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createtable.html and
|
|
9
|
+
* https://www.postgresql.org/docs/18/sql-createtype.html. The server refuses
|
|
10
|
+
* `column "id" specified more than once` and `enum labels must be unique`.
|
|
11
|
+
* Column names are compared as Postgres folds them; labels as written, since
|
|
12
|
+
* they are case-sensitive strings.
|
|
13
|
+
*/
|
|
14
|
+
export const sqlpg122 = checkOf({ id: "SQLPG122", description: "A table declares a column twice, or an enum lists a label twice" }, (ctx, report) => {
|
|
15
|
+
for (const t of tablesOf(ctx)) {
|
|
16
|
+
for (const name of repeated(t.columns.map((c) => c.name))) {
|
|
17
|
+
report({ severity: "error", message: `${t.export} (${t.sqlName}) declares the column ${name} more than once`, entity: t.export });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const o of postgresObjects(ctx)) {
|
|
21
|
+
if (o.type !== "Postgres::Enum")
|
|
22
|
+
continue;
|
|
23
|
+
for (const label of repeated(o.labels ?? [])) {
|
|
24
|
+
report({ severity: "error", message: `${o.export} (${o.sqlName}) lists the enum label '${label}' more than once`, entity: o.export });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { checkOf, indexesOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { columnsOf, includeColumns, interpolatedTable, projectOf } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG123: a bare column name in a key, index or grant column list that the
|
|
5
|
+
* table does not declare.
|
|
6
|
+
*
|
|
7
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createtable.html,
|
|
8
|
+
* https://www.postgresql.org/docs/18/sql-createindex.html,
|
|
9
|
+
* https://www.postgresql.org/docs/18/sql-grant.html. The server refuses
|
|
10
|
+
* `column "idd" named in key does not exist` (or `column "emial" does not
|
|
11
|
+
* exist`) at CREATE or GRANT. Checked: `PRIMARY KEY`, `UNIQUE` and their
|
|
12
|
+
* `INCLUDE`, a foreign key's own columns, an index's bare columns and
|
|
13
|
+
* `INCLUDE` on a `${}` table, and a `GRANT`/`REVOKE` column list on a `${}`
|
|
14
|
+
* table. An expression (an index expression, a CHECK, a generated column) is
|
|
15
|
+
* left to the server, as is a table whose columns come from somewhere the
|
|
16
|
+
* build cannot see (`LIKE`, `OF type`, a plain-text parent).
|
|
17
|
+
*/
|
|
18
|
+
export const sqlpg123 = checkOf({ id: "SQLPG123", description: "A key, index or grant column list names a column the table does not declare" }, (ctx, report) => {
|
|
19
|
+
const p = projectOf(ctx);
|
|
20
|
+
const verify = (entity, sqlName, where, tableName, names, cols) => {
|
|
21
|
+
for (const n of new Set(names)) {
|
|
22
|
+
if (cols.has(n))
|
|
23
|
+
continue;
|
|
24
|
+
report({ severity: "error", message: `${entity} (${sqlName}) ${where} names the column ${n}, which ${tableName} does not declare`, entity });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
for (const t of tablesOf(ctx)) {
|
|
28
|
+
const cols = columnsOf(p, t);
|
|
29
|
+
if (!cols)
|
|
30
|
+
continue;
|
|
31
|
+
if (t.primaryKey) {
|
|
32
|
+
verify(t.export, t.sqlName, "PRIMARY KEY", t.sqlName, t.primaryKey.columns, cols);
|
|
33
|
+
verify(t.export, t.sqlName, "PRIMARY KEY ... INCLUDE", t.sqlName, includeColumns(t.primaryKey.include), cols);
|
|
34
|
+
}
|
|
35
|
+
for (const u of t.uniques) {
|
|
36
|
+
verify(t.export, t.sqlName, "UNIQUE", t.sqlName, u.columns, cols);
|
|
37
|
+
verify(t.export, t.sqlName, "UNIQUE ... INCLUDE", t.sqlName, includeColumns(u.include), cols);
|
|
38
|
+
}
|
|
39
|
+
for (const fk of t.foreignKeys)
|
|
40
|
+
verify(t.export, t.sqlName, `FOREIGN KEY to ${fk.refTable}`, t.sqlName, fk.columns, cols);
|
|
41
|
+
}
|
|
42
|
+
for (const i of indexesOf(ctx)) {
|
|
43
|
+
const t = interpolatedTable(p, i.table, i.tableName);
|
|
44
|
+
const cols = t && columnsOf(p, t);
|
|
45
|
+
if (!cols)
|
|
46
|
+
continue;
|
|
47
|
+
verify(i.export, i.sqlName, `index on ${i.tableName}`, i.tableName, i.elements.flatMap((e) => (e.column ? [e.column] : [])), cols);
|
|
48
|
+
verify(i.export, i.sqlName, `index on ${i.tableName} INCLUDE`, i.tableName, includeColumns(i.include), cols);
|
|
49
|
+
}
|
|
50
|
+
for (const g of p.objects) {
|
|
51
|
+
if (g.type !== "Postgres::Grant" || g.on !== "table")
|
|
52
|
+
continue;
|
|
53
|
+
const objects = g.objects ?? [];
|
|
54
|
+
const names = g.objectNames ?? [];
|
|
55
|
+
const privileges = g.privileges ?? [];
|
|
56
|
+
objects.forEach((ref, n) => {
|
|
57
|
+
const t = interpolatedTable(p, ref, names[n]);
|
|
58
|
+
const cols = t && columnsOf(p, t);
|
|
59
|
+
if (!cols)
|
|
60
|
+
return;
|
|
61
|
+
for (const priv of privileges) {
|
|
62
|
+
if (!priv.columns?.length)
|
|
63
|
+
continue;
|
|
64
|
+
const action = g.action === "revoke" ? "REVOKE" : "GRANT";
|
|
65
|
+
verify(g.export, String(g.name), `${action} ${priv.privilege.toUpperCase()} on ${t.sqlName}`, t.sqlName, priv.columns, cols);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { checkOf, tablesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { columnsOf, interpolatedTable, projectOf, typeIdentity } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG124: a foreign key to a `${}` table that names a column the table does
|
|
5
|
+
* not declare, or pairs columns whose types do not compare.
|
|
6
|
+
*
|
|
7
|
+
* Doc: https://www.postgresql.org/docs/18/ddl-constraints.html#DDL-CONSTRAINTS-FK
|
|
8
|
+
* and https://www.postgresql.org/docs/18/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE.
|
|
9
|
+
* The server refuses `column "idd" referenced in foreign key constraint does
|
|
10
|
+
* not exist` and `Key columns "customer_id" and "id" are of incompatible
|
|
11
|
+
* types: text and bigint`. Types compare by their catalog category: `integer`
|
|
12
|
+
* to `bigint` (both numeric) passes, `text` to `bigint` fails. The
|
|
13
|
+
* user-defined category (`uuid`, `bytea`, `jsonb`...) compares by type; a
|
|
14
|
+
* domain by its base type; an enum by itself. A key to a table named in plain
|
|
15
|
+
* text, or a type outside the project, is left to the server.
|
|
16
|
+
*/
|
|
17
|
+
export const sqlpg124 = checkOf({ id: "SQLPG124", description: "A foreign key names a column the referenced table lacks, or one of another type category" }, (ctx, report) => {
|
|
18
|
+
const p = projectOf(ctx);
|
|
19
|
+
for (const t of tablesOf(ctx)) {
|
|
20
|
+
const own = columnsOf(p, t);
|
|
21
|
+
for (const fk of t.foreignKeys) {
|
|
22
|
+
const ref = interpolatedTable(p, fk.references, fk.refTable);
|
|
23
|
+
const refCols = ref && columnsOf(p, ref);
|
|
24
|
+
if (!ref || !refCols)
|
|
25
|
+
continue;
|
|
26
|
+
const targets = fk.refColumns.length > 0 ? fk.refColumns : (ref.primaryKey?.columns ?? []);
|
|
27
|
+
const missing = targets.filter((c) => !refCols.has(c));
|
|
28
|
+
for (const c of missing) {
|
|
29
|
+
report({ severity: "error", message: `${t.export} (${t.sqlName}) has a foreign key on (${fk.columns.join(", ")}) to ${ref.sqlName} (${c}), which ${ref.sqlName} does not declare`, entity: t.export });
|
|
30
|
+
}
|
|
31
|
+
if (missing.length > 0 || !own || targets.length !== fk.columns.length)
|
|
32
|
+
continue;
|
|
33
|
+
fk.columns.forEach((c, n) => {
|
|
34
|
+
const mine = own.get(c);
|
|
35
|
+
const theirs = refCols.get(targets[n]);
|
|
36
|
+
if (!mine || !theirs)
|
|
37
|
+
return;
|
|
38
|
+
const a = typeIdentity(p, mine.type);
|
|
39
|
+
const b = typeIdentity(p, theirs.type);
|
|
40
|
+
if (a === undefined || b === undefined || a === b)
|
|
41
|
+
return;
|
|
42
|
+
report({
|
|
43
|
+
severity: "error",
|
|
44
|
+
message: `${t.export} (${t.sqlName}) has a foreign key from ${c} (${mine.type}) to ${ref.sqlName} (${targets[n]}) (${theirs.type}); the types do not compare, so Postgres refuses the key`,
|
|
45
|
+
entity: t.export,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { checkOf, indexesOf } from "./postgres-helpers.js";
|
|
2
|
+
import { parseIndexElement, projectOf, resolveMethod, resolveOpClass } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG125: an index access method or operator class the target major does
|
|
5
|
+
* not have, or an operator class that is not for the index's method.
|
|
6
|
+
*
|
|
7
|
+
* Doc: https://www.postgresql.org/docs/18/sql-createindex.html and
|
|
8
|
+
* https://www.postgresql.org/docs/18/indexes-opclass.html. The server refuses
|
|
9
|
+
* `access method "btreee" does not exist` and `operator class "numeric_opz"
|
|
10
|
+
* does not exist for access method "btree"` at CREATE. Methods and operator
|
|
11
|
+
* classes come from the target major's catalog (`pg_am`, `pg_opclass`). The
|
|
12
|
+
* ones an extension adds (`gin_trgm_ops` from pg_trgm, `hnsw` from vector)
|
|
13
|
+
* pass when the project declares that extension and are an error naming it
|
|
14
|
+
* when it does not; one neither the catalog nor the extension overlay knows is
|
|
15
|
+
* a warning when the project declares any extension. An operator class
|
|
16
|
+
* qualified with a schema the project does not declare is left alone.
|
|
17
|
+
*/
|
|
18
|
+
export const sqlpg125 = checkOf({ id: "SQLPG125", description: "An index access method or operator class the target major does not have, or not for that method" }, (ctx, report) => {
|
|
19
|
+
const p = projectOf(ctx);
|
|
20
|
+
for (const i of indexesOf(ctx)) {
|
|
21
|
+
const method = i.method ?? "btree";
|
|
22
|
+
const m = resolveMethod(p, method);
|
|
23
|
+
if (!m.ok) {
|
|
24
|
+
report({ severity: m.miss.severity, message: `${i.export} (${i.sqlName}) uses the index access method ${method}, which Postgres ${p.major} does not have${m.miss.note}`, entity: i.export });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
for (const e of i.elements) {
|
|
28
|
+
const opclass = parseIndexElement(e.expr)?.opclass;
|
|
29
|
+
if (!opclass)
|
|
30
|
+
continue;
|
|
31
|
+
const r = resolveOpClass(p, method, opclass);
|
|
32
|
+
if (r.ok)
|
|
33
|
+
continue;
|
|
34
|
+
const label = `${opclass.schema ? `${opclass.schema}.` : ""}${opclass.name}`;
|
|
35
|
+
const message = r.otherMethods
|
|
36
|
+
? `${i.export} (${i.sqlName}) uses the operator class ${label}, which is for ${r.otherMethods.join(", ")}, not ${method}`
|
|
37
|
+
: `${i.export} (${i.sqlName}) uses the operator class ${label}, which Postgres ${p.major} does not have for ${method}${r.miss.note}`;
|
|
38
|
+
report({ severity: r.miss.severity, message, entity: i.export });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { checkOf } from "./postgres-helpers.js";
|
|
2
|
+
import { callsIn, expressionsOf, projectOf, resolveCall } from "./postgres-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQLPG126: a call to a function the target major does not have, in a column
|
|
5
|
+
* `DEFAULT`, a generated column, a `CHECK` (a table's or a domain's), or an
|
|
6
|
+
* index expression.
|
|
7
|
+
*
|
|
8
|
+
* Doc: https://www.postgresql.org/docs/18/functions.html. The server refuses
|
|
9
|
+
* `function noww() does not exist` at CREATE. A name counts when it is in
|
|
10
|
+
* the target major's `pg_proc`, a function or procedure the project declares,
|
|
11
|
+
* a type name used as a cast (`int4(x)`), or qualified with a schema the
|
|
12
|
+
* project does not declare. Only a name directly followed by `(`, outside
|
|
13
|
+
* string literals, is a call; the grammar's own constructs that read like
|
|
14
|
+
* calls (`COALESCE`, `NULLIF`, `GREATEST`, `LEAST`, `CAST`, `EXTRACT`, `ROW`,
|
|
15
|
+
* `ARRAY`, `EXISTS`, `IN`, `ANY`, `ALL`, `SOME`, and the key word forms of
|
|
16
|
+
* `OVERLAY`, `POSITION`, `SUBSTRING` and `TRIM`) are reserved or column-name
|
|
17
|
+
* key words, not `pg_proc` names, and are skipped by their key word category.
|
|
18
|
+
* An error, or a warning when the project declares an extension.
|
|
19
|
+
*/
|
|
20
|
+
export const sqlpg126 = checkOf({ id: "SQLPG126", description: "An expression calls a function the target major does not have" }, (ctx, report) => {
|
|
21
|
+
const p = projectOf(ctx);
|
|
22
|
+
for (const o of p.objects) {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
for (const { where, expr } of expressionsOf(o)) {
|
|
25
|
+
for (const c of callsIn(expr)) {
|
|
26
|
+
const label = `${c.schema ? `${c.schema}.` : ""}${c.name}`;
|
|
27
|
+
if (seen.has(`${where}\0${label}`))
|
|
28
|
+
continue;
|
|
29
|
+
seen.add(`${where}\0${label}`);
|
|
30
|
+
const r = resolveCall(p, c);
|
|
31
|
+
if (r.ok)
|
|
32
|
+
continue;
|
|
33
|
+
const why = r.since !== undefined ? `, which needs Postgres ${r.since}; Postgres ${p.major} does not have it` : r.until !== undefined ? `, which Postgres removed after ${r.until}` : `, which Postgres ${p.major} does not have`;
|
|
34
|
+
report({ severity: r.miss.severity, message: `${o.export} (${o.sqlName}) ${where} calls ${label}()${why}${r.miss.note}`, entity: o.export });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { sqlch001 } from "./sqlch001.js";
|
|
2
|
+
import { sqlch002 } from "./sqlch002.js";
|
|
3
|
+
import { sqlch003 } from "./sqlch003.js";
|
|
4
|
+
import { sqlch004 } from "./sqlch004.js";
|
|
5
|
+
import { sqlpg001 } from "./sqlpg001.js";
|
|
6
|
+
import { sqlpg002 } from "./sqlpg002.js";
|
|
7
|
+
import { sqlpg003 } from "./sqlpg003.js";
|
|
8
|
+
import { sqlpg004 } from "./sqlpg004.js";
|
|
9
|
+
import { sqlpg005 } from "./sqlpg005.js";
|
|
10
|
+
export { sqlch001, sqlch002, sqlch003, sqlch004, sqlpg001, sqlpg002, sqlpg003, sqlpg004, sqlpg005 };
|
|
11
|
+
/** The lexicon's source-level lint rules, returned by `lintRules()`. */
|
|
12
|
+
export const rules = [sqlch001, sqlch002, sqlch003, sqlch004, sqlpg001, sqlpg002, sqlpg003, sqlpg004, sqlpg005];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding the Postgres tags in a source file, for the source-level rules. A
|
|
3
|
+
* tag is Postgres's because of the module it is imported from: the
|
|
4
|
+
* `/postgres` subpath, or the package root for the tags whose names
|
|
5
|
+
* ClickHouse does not export (the root's `table` and `view` are
|
|
6
|
+
* ClickHouse's).
|
|
7
|
+
*/
|
|
8
|
+
import { tokenize } from "../../postgres/tokens.js";
|
|
9
|
+
import { findSqlTemplates } from "../../core/find-templates.js";
|
|
10
|
+
export { templatePosition, tokenPosition } from "../../core/find-templates.js";
|
|
11
|
+
export const POSTGRES_TEMPLATE_SOURCES = [
|
|
12
|
+
{
|
|
13
|
+
dialect: "postgres",
|
|
14
|
+
modules: ["@intentius/chant-lexicon-sql/postgres"],
|
|
15
|
+
tags: ["schema", "table", "index", "view", "sequence", "type", "domain", "extension", "func", "procedure", "aggregate", "trigger", "eventTrigger", "collation", "cast", "policy", "role", "grant"],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
dialect: "postgres",
|
|
19
|
+
modules: ["@intentius/chant-lexicon-sql"],
|
|
20
|
+
tags: ["schema", "index", "sequence", "type", "domain", "extension", "func", "procedure", "aggregate", "trigger", "eventTrigger", "collation", "cast", "policy", "role", "grant"],
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
/** Every Postgres template in the file. */
|
|
24
|
+
export function findPostgresTemplates(source) {
|
|
25
|
+
return findSqlTemplates(source, POSTGRES_TEMPLATE_SOURCES);
|
|
26
|
+
}
|
|
27
|
+
export const postgresTokensOf = (found) => tokenize(found.parts);
|