supaschema 0.1.0-rc.1
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/.agents/skills/supaschema/SKILL.md +61 -0
- package/.claude/hooks/block-generated-migration-edits.mjs +32 -0
- package/.claude/rules/supaschema.md +22 -0
- package/.claude/settings.json +16 -0
- package/.claude/skills/supaschema/SKILL.md +61 -0
- package/.codex/hooks/supaschema-tool-gate.mjs +73 -0
- package/.codex/hooks.json +16 -0
- package/.codex/rules/supaschema.rules +22 -0
- package/AGENTS.md +40 -0
- package/LICENSE +661 -0
- package/LICENSE-COMMERCIAL.md +35 -0
- package/README.md +249 -0
- package/benchmarks/README.md +104 -0
- package/benchmarks/compare.js +489 -0
- package/benchmarks/fixtures/additive/from.sql +8 -0
- package/benchmarks/fixtures/additive/manifest.json +1 -0
- package/benchmarks/fixtures/additive/to.sql +9 -0
- package/benchmarks/fixtures/functions-policies/from.sql +24 -0
- package/benchmarks/fixtures/functions-policies/manifest.json +5 -0
- package/benchmarks/fixtures/functions-policies/to.sql +24 -0
- package/benchmarks/plot-lib.js +234 -0
- package/benchmarks/plot-svg.js +339 -0
- package/benchmarks/plot.js +154 -0
- package/benchmarks/tools/bench-all.sh +49 -0
- package/benchmarks/tools/build-project-fixture.mjs +245 -0
- package/benchmarks/tools/compare-db.mjs +101 -0
- package/benchmarks/tools/compare-fixtures.mjs +84 -0
- package/benchmarks/tools/compare-report.mjs +90 -0
- package/benchmarks/tools/compare-supabase.mjs +67 -0
- package/benchmarks/tools/registry.js +266 -0
- package/benchmarks/tools/run-workflow.mjs +77 -0
- package/bin/postinstall.mjs +26 -0
- package/bin/supaschema +2 -0
- package/config-schema.json +208 -0
- package/corpus/supabase-style/corpus.json +6 -0
- package/corpus/supabase-style/migrations/20260101000000_init.sql +28 -0
- package/corpus/supabase-style/migrations/20260102000000_noise.sql +13 -0
- package/corpus/supabase-style/migrations/20260103000000_churn.sql +4 -0
- package/corpus/supabase-style/migrations/20260104000000_triggers.sql +17 -0
- package/corpus/supabase-style/roles.sql +13 -0
- package/corpus/supabase-style/tree/functions.sql +26 -0
- package/corpus/supabase-style/tree/policies.sql +4 -0
- package/corpus/supabase-style/tree/schema.sql +4 -0
- package/corpus/supabase-style/tree/tables.sql +20 -0
- package/corpus/supabase-style/tree/triggers.sql +3 -0
- package/corpus/supabase-style/tree/types.sql +2 -0
- package/corpus/supabase-style/tree/views.sql +6 -0
- package/dist/audit.d.ts +20 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +68 -0
- package/dist/benchmark-db.d.ts +5 -0
- package/dist/benchmark-db.d.ts.map +1 -0
- package/dist/benchmark-db.js +71 -0
- package/dist/benchmark-fixtures.d.ts +10 -0
- package/dist/benchmark-fixtures.d.ts.map +1 -0
- package/dist/benchmark-fixtures.js +201 -0
- package/dist/benchmark.d.ts +2 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +308 -0
- package/dist/catalog-comments.d.ts +9 -0
- package/dist/catalog-comments.d.ts.map +1 -0
- package/dist/catalog-comments.js +194 -0
- package/dist/catalog-extras.d.ts +12 -0
- package/dist/catalog-extras.d.ts.map +1 -0
- package/dist/catalog-extras.js +408 -0
- package/dist/catalog-foreign.d.ts +15 -0
- package/dist/catalog-foreign.d.ts.map +1 -0
- package/dist/catalog-foreign.js +114 -0
- package/dist/catalog-tables.d.ts +9 -0
- package/dist/catalog-tables.d.ts.map +1 -0
- package/dist/catalog-tables.js +114 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +351 -0
- package/dist/check-hazards.d.ts +7 -0
- package/dist/check-hazards.d.ts.map +1 -0
- package/dist/check-hazards.js +83 -0
- package/dist/check-reporters.d.ts +8 -0
- package/dist/check-reporters.d.ts.map +1 -0
- package/dist/check-reporters.js +76 -0
- package/dist/check.d.ts +3 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +229 -0
- package/dist/cli-defaults.d.ts +24 -0
- package/dist/cli-defaults.d.ts.map +1 -0
- package/dist/cli-defaults.js +65 -0
- package/dist/cli-diff.d.ts +13 -0
- package/dist/cli-diff.d.ts.map +1 -0
- package/dist/cli-diff.js +348 -0
- package/dist/cli-reports.d.ts +9 -0
- package/dist/cli-reports.d.ts.map +1 -0
- package/dist/cli-reports.js +90 -0
- package/dist/cli-tools.d.ts +17 -0
- package/dist/cli-tools.d.ts.map +1 -0
- package/dist/cli-tools.js +136 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +239 -0
- package/dist/config-schema-gen.d.ts +2 -0
- package/dist/config-schema-gen.d.ts.map +1 -0
- package/dist/config-schema-gen.js +11 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/core.d.ts +115 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +1 -0
- package/dist/corpus.d.ts +26 -0
- package/dist/corpus.d.ts.map +1 -0
- package/dist/corpus.js +112 -0
- package/dist/database-url.d.ts +8 -0
- package/dist/database-url.d.ts.map +1 -0
- package/dist/database-url.js +74 -0
- package/dist/db-admin.d.ts +23 -0
- package/dist/db-admin.d.ts.map +1 -0
- package/dist/db-admin.js +147 -0
- package/dist/diagnostics.d.ts +16 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +155 -0
- package/dist/diff-score.d.ts +12 -0
- package/dist/diff-score.d.ts.map +1 -0
- package/dist/diff-score.js +339 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +110 -0
- package/dist/hash.d.ts +7 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +34 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/lineage.d.ts +23 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +61 -0
- package/dist/migrations-status.d.ts +35 -0
- package/dist/migrations-status.d.ts.map +1 -0
- package/dist/migrations-status.js +131 -0
- package/dist/plan-order.d.ts +4 -0
- package/dist/plan-order.d.ts.map +1 -0
- package/dist/plan-order.js +178 -0
- package/dist/planner-replace.d.ts +4 -0
- package/dist/planner-replace.d.ts.map +1 -0
- package/dist/planner-replace.js +76 -0
- package/dist/planner-table.d.ts +3 -0
- package/dist/planner-table.d.ts.map +1 -0
- package/dist/planner-table.js +165 -0
- package/dist/planner.d.ts +5 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +385 -0
- package/dist/render-guards.d.ts +12 -0
- package/dist/render-guards.d.ts.map +1 -0
- package/dist/render-guards.js +159 -0
- package/dist/render.d.ts +7 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +325 -0
- package/dist/selfcheck.d.ts +11 -0
- package/dist/selfcheck.d.ts.map +1 -0
- package/dist/selfcheck.js +43 -0
- package/dist/source-normalize.d.ts +14 -0
- package/dist/source-normalize.d.ts.map +1 -0
- package/dist/source-normalize.js +420 -0
- package/dist/source.d.ts +4 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +233 -0
- package/dist/sql/ast.d.ts +42 -0
- package/dist/sql/ast.d.ts.map +1 -0
- package/dist/sql/ast.js +241 -0
- package/dist/sql/canonical-nodes.d.ts +5 -0
- package/dist/sql/canonical-nodes.d.ts.map +1 -0
- package/dist/sql/canonical-nodes.js +101 -0
- package/dist/sql/extract-helpers.d.ts +18 -0
- package/dist/sql/extract-helpers.d.ts.map +1 -0
- package/dist/sql/extract-helpers.js +127 -0
- package/dist/sql/extract.d.ts +13 -0
- package/dist/sql/extract.d.ts.map +1 -0
- package/dist/sql/extract.js +323 -0
- package/dist/sql/facts.d.ts +34 -0
- package/dist/sql/facts.d.ts.map +1 -0
- package/dist/sql/facts.js +392 -0
- package/dist/sql/identifiers.d.ts +13 -0
- package/dist/sql/identifiers.d.ts.map +1 -0
- package/dist/sql/identifiers.js +83 -0
- package/dist/sql/normalize-deparse.d.ts +25 -0
- package/dist/sql/normalize-deparse.d.ts.map +1 -0
- package/dist/sql/normalize-deparse.js +96 -0
- package/dist/sql/object-hash.d.ts +5 -0
- package/dist/sql/object-hash.d.ts.map +1 -0
- package/dist/sql/object-hash.js +24 -0
- package/dist/sql/parser.d.ts +8 -0
- package/dist/sql/parser.d.ts.map +1 -0
- package/dist/sql/parser.js +89 -0
- package/dist/sql/privileges.d.ts +33 -0
- package/dist/sql/privileges.d.ts.map +1 -0
- package/dist/sql/privileges.js +379 -0
- package/dist/sql/split.d.ts +3 -0
- package/dist/sql/split.d.ts.map +1 -0
- package/dist/sql/split.js +182 -0
- package/dist/sql/statements.d.ts +17 -0
- package/dist/sql/statements.d.ts.map +1 -0
- package/dist/sql/statements.js +284 -0
- package/dist/sql/table-constraints.d.ts +15 -0
- package/dist/sql/table-constraints.d.ts.map +1 -0
- package/dist/sql/table-constraints.js +304 -0
- package/dist/sql/table-shape.d.ts +38 -0
- package/dist/sql/table-shape.d.ts.map +1 -0
- package/dist/sql/table-shape.js +287 -0
- package/dist/sync.d.ts +27 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +86 -0
- package/dist/typegen-model.d.ts +78 -0
- package/dist/typegen-model.d.ts.map +1 -0
- package/dist/typegen-model.js +338 -0
- package/dist/typegen-views.d.ts +7 -0
- package/dist/typegen-views.d.ts.map +1 -0
- package/dist/typegen-views.js +92 -0
- package/dist/typegen-zod.d.ts +3 -0
- package/dist/typegen-zod.d.ts.map +1 -0
- package/dist/typegen-zod.js +149 -0
- package/dist/typegen.d.ts +4 -0
- package/dist/typegen.d.ts.map +1 -0
- package/dist/typegen.js +184 -0
- package/dist/validators.d.ts +3 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +104 -0
- package/dist/verify-environment.d.ts +5 -0
- package/dist/verify-environment.d.ts.map +1 -0
- package/dist/verify-environment.js +92 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +261 -0
- package/docs/benchmarks/additive-correctness.svg +86 -0
- package/docs/benchmarks/additive-latency.svg +60 -0
- package/docs/benchmarks/functions-policies-correctness.svg +86 -0
- package/docs/benchmarks/functions-policies-latency.svg +60 -0
- package/docs/benchmarks/realistic-correctness.svg +86 -0
- package/docs/benchmarks/realistic-latency.svg +60 -0
- package/docs/benchmarks/scaling-latency.svg +106 -0
- package/docs/benchmarks/workflow-latency.svg +98 -0
- package/docs/benchmarks/xl-correctness.svg +86 -0
- package/docs/benchmarks/xl-latency.svg +60 -0
- package/docs/benchmarks/xxl-correctness.svg +86 -0
- package/docs/benchmarks/xxl-latency.svg +66 -0
- package/docs/case-study-anilize.md +51 -0
- package/docs/ci-gate.md +44 -0
- package/docs/ci.md +68 -0
- package/docs/commands.md +35 -0
- package/docs/config.md +72 -0
- package/docs/corpus.md +33 -0
- package/docs/diagnostics.md +77 -0
- package/docs/hints.md +92 -0
- package/docs/release.md +19 -0
- package/docs/support-matrix.md +57 -0
- package/examples/postgres/schemas/001_app.sql +17 -0
- package/examples/supabase/schemas/001_app.sql +13 -0
- package/examples/supabase/schemas-next/001_app.sql +21 -0
- package/examples/supabase/supaschema.config.json +15 -0
- package/package.json +99 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { formatQualifiedName, quoteIdent } from "./sql/identifiers.js";
|
|
2
|
+
import { makeObject } from "./sql/statements.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whole-object FDW tier: servers and foreign tables are modeled by their
|
|
5
|
+
* complete definition (no column-level diffing). Server and column options
|
|
6
|
+
* are reconstructed from catalog option arrays; user mappings are excluded
|
|
7
|
+
* because they carry credentials.
|
|
8
|
+
*/
|
|
9
|
+
export async function collectForeignObjects(pool) {
|
|
10
|
+
const objects = [];
|
|
11
|
+
const wrappers = await pool.query(`
|
|
12
|
+
select w.fdwname as name,
|
|
13
|
+
w.fdwhandler::regproc::text as handler,
|
|
14
|
+
w.fdwvalidator::regproc::text as validator,
|
|
15
|
+
w.fdwoptions as options
|
|
16
|
+
from pg_foreign_data_wrapper w
|
|
17
|
+
where not exists (
|
|
18
|
+
select 1 from pg_depend d where d.objid = w.oid and d.deptype = 'e'
|
|
19
|
+
)
|
|
20
|
+
order by w.fdwname
|
|
21
|
+
`);
|
|
22
|
+
for (const row of wrappers.rows) {
|
|
23
|
+
const clauses = [`CREATE FOREIGN DATA WRAPPER ${quoteIdent(text(row.name))}`];
|
|
24
|
+
if (row.handler && text(row.handler) !== "-") {
|
|
25
|
+
clauses.push(`HANDLER ${text(row.handler)}`);
|
|
26
|
+
}
|
|
27
|
+
if (row.validator && text(row.validator) !== "-") {
|
|
28
|
+
clauses.push(`VALIDATOR ${text(row.validator)}`);
|
|
29
|
+
}
|
|
30
|
+
const options = optionsClause(row.options);
|
|
31
|
+
if (options) {
|
|
32
|
+
clauses.push(options);
|
|
33
|
+
}
|
|
34
|
+
objects.push(makeObject({ kind: "foreign-data-wrapper", name: text(row.name) }, clauses.join(" "), 0));
|
|
35
|
+
}
|
|
36
|
+
const servers = await pool.query(`
|
|
37
|
+
select s.srvname as name, w.fdwname as wrapper, s.srvtype as server_type,
|
|
38
|
+
s.srvversion as server_version, s.srvoptions as options
|
|
39
|
+
from pg_foreign_server s
|
|
40
|
+
join pg_foreign_data_wrapper w on w.oid = s.srvfdw
|
|
41
|
+
order by s.srvname
|
|
42
|
+
`);
|
|
43
|
+
for (const row of servers.rows) {
|
|
44
|
+
const clauses = [`CREATE SERVER ${quoteIdent(text(row.name))}`];
|
|
45
|
+
if (row.server_type) {
|
|
46
|
+
clauses.push(`TYPE '${escapeLiteral(text(row.server_type))}'`);
|
|
47
|
+
}
|
|
48
|
+
if (row.server_version) {
|
|
49
|
+
clauses.push(`VERSION '${escapeLiteral(text(row.server_version))}'`);
|
|
50
|
+
}
|
|
51
|
+
clauses.push(`FOREIGN DATA WRAPPER ${quoteIdent(text(row.wrapper))}`);
|
|
52
|
+
const options = optionsClause(row.options);
|
|
53
|
+
if (options) {
|
|
54
|
+
clauses.push(options);
|
|
55
|
+
}
|
|
56
|
+
objects.push(makeObject({ kind: "foreign-server", name: text(row.name) }, clauses.join(" "), 0));
|
|
57
|
+
}
|
|
58
|
+
const tables = await pool.query(`
|
|
59
|
+
select n.nspname as schema, c.relname as name, s.srvname as server, ft.ftoptions as options,
|
|
60
|
+
array(
|
|
61
|
+
select format('%I %s', a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod))
|
|
62
|
+
from pg_attribute a
|
|
63
|
+
where a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
|
|
64
|
+
order by a.attnum
|
|
65
|
+
) as columns
|
|
66
|
+
from pg_foreign_table ft
|
|
67
|
+
join pg_class c on c.oid = ft.ftrelid
|
|
68
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
69
|
+
join pg_foreign_server s on s.oid = ft.ftserver
|
|
70
|
+
where n.nspname !~ '^pg_' and n.nspname <> 'information_schema'
|
|
71
|
+
order by n.nspname, c.relname
|
|
72
|
+
`);
|
|
73
|
+
for (const row of tables.rows) {
|
|
74
|
+
const schema = text(row.schema);
|
|
75
|
+
const name = text(row.name);
|
|
76
|
+
const columns = Array.isArray(row.columns) ? row.columns.map((item) => ` ${text(item)}`) : [];
|
|
77
|
+
const clauses = [
|
|
78
|
+
`CREATE FOREIGN TABLE ${formatQualifiedName(schema, name)} (\n${columns.join(",\n")}\n)`,
|
|
79
|
+
`SERVER ${quoteIdent(text(row.server))}`,
|
|
80
|
+
];
|
|
81
|
+
const options = optionsClause(row.options);
|
|
82
|
+
if (options) {
|
|
83
|
+
clauses.push(options);
|
|
84
|
+
}
|
|
85
|
+
objects.push(makeObject({ kind: "foreign-table", name, schema }, clauses.join(" "), 0, undefined, {
|
|
86
|
+
server: text(row.server),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
return objects;
|
|
90
|
+
}
|
|
91
|
+
function optionsClause(value) {
|
|
92
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const rendered = value
|
|
96
|
+
.map((item) => {
|
|
97
|
+
const raw = text(item);
|
|
98
|
+
const separator = raw.indexOf("=");
|
|
99
|
+
if (separator === -1) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const key = raw.slice(0, separator);
|
|
103
|
+
const optionValue = raw.slice(separator + 1);
|
|
104
|
+
return `${quoteIdent(key)} '${escapeLiteral(optionValue)}'`;
|
|
105
|
+
})
|
|
106
|
+
.filter((item) => item !== undefined);
|
|
107
|
+
return rendered.length > 0 ? `OPTIONS (${rendered.join(", ")})` : undefined;
|
|
108
|
+
}
|
|
109
|
+
function escapeLiteral(value) {
|
|
110
|
+
return value.replaceAll("'", "''");
|
|
111
|
+
}
|
|
112
|
+
function text(value) {
|
|
113
|
+
return typeof value === "string" ? value : String(value);
|
|
114
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SchemaObject } from "./core.js";
|
|
2
|
+
type CatalogQuery = {
|
|
3
|
+
query: <Row extends Record<string, unknown>>(text: string, values?: unknown[]) => Promise<{
|
|
4
|
+
rows: Row[];
|
|
5
|
+
}>;
|
|
6
|
+
};
|
|
7
|
+
export declare function collectTables(pool: CatalogQuery): Promise<SchemaObject[]>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=catalog-tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-tables.d.ts","sourceRoot":"","sources":["../src/catalog-tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAI3D,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAsB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA+E/E"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { formatQualifiedName, quoteIdent } from "./sql/identifiers.js";
|
|
2
|
+
import { makeObject } from "./sql/statements.js";
|
|
3
|
+
export async function collectTables(pool) {
|
|
4
|
+
const tables = await pool.query(`
|
|
5
|
+
select c.oid::text as oid, n.nspname as schema, c.relname as name
|
|
6
|
+
from pg_class c
|
|
7
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
8
|
+
where c.relkind in ('r', 'p')
|
|
9
|
+
and not c.relispartition
|
|
10
|
+
and n.nspname !~ '^pg_'
|
|
11
|
+
and n.nspname <> 'information_schema'
|
|
12
|
+
order by n.nspname, c.relname
|
|
13
|
+
`);
|
|
14
|
+
if (tables.rows.length === 0) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const oids = tables.rows.map((row) => stringValue(row.oid));
|
|
18
|
+
const [columns, constraints] = await Promise.all([
|
|
19
|
+
pool.query(`
|
|
20
|
+
select
|
|
21
|
+
a.attrelid::text as oid,
|
|
22
|
+
a.attname as name,
|
|
23
|
+
format_type(a.atttypid, a.atttypmod) as type,
|
|
24
|
+
a.attnotnull as not_null,
|
|
25
|
+
a.attidentity as identity,
|
|
26
|
+
a.attgenerated as generated,
|
|
27
|
+
pg_get_expr(d.adbin, d.adrelid) as default_expression
|
|
28
|
+
from pg_attribute a
|
|
29
|
+
left join pg_attrdef d on d.adrelid = a.attrelid and d.adnum = a.attnum
|
|
30
|
+
where a.attrelid = any($1::oid[])
|
|
31
|
+
and a.attnum > 0
|
|
32
|
+
and not a.attisdropped
|
|
33
|
+
order by a.attrelid, a.attnum
|
|
34
|
+
`, [oids]),
|
|
35
|
+
pool.query(`
|
|
36
|
+
select conrelid::text as oid, conname as name, pg_get_constraintdef(oid, true) as definition
|
|
37
|
+
from pg_constraint
|
|
38
|
+
where conrelid = any($1::oid[])
|
|
39
|
+
and contype in ('p', 'u', 'f', 'c', 'x')
|
|
40
|
+
order by conrelid, conname
|
|
41
|
+
`, [oids]),
|
|
42
|
+
]);
|
|
43
|
+
const columnsByOid = groupByOid(columns.rows);
|
|
44
|
+
const constraintsByOid = groupByOid(constraints.rows);
|
|
45
|
+
const objects = [];
|
|
46
|
+
for (const table of tables.rows) {
|
|
47
|
+
const oid = stringValue(table.oid);
|
|
48
|
+
const columnDefinitions = (columnsByOid.get(oid) ?? []).map(columnFromRow);
|
|
49
|
+
const lines = columnDefinitions.map((column) => ` ${quoteIdent(column.name)} ${column.definition}`);
|
|
50
|
+
const schema = stringValue(table.schema);
|
|
51
|
+
const name = stringValue(table.name);
|
|
52
|
+
const sql = `CREATE TABLE ${formatQualifiedName(schema, name)} (\n${lines.join(",\n")}\n)`;
|
|
53
|
+
objects.push(makeObject({ kind: "table", name, schema }, sql, 0, undefined, {
|
|
54
|
+
columns: columnDefinitions,
|
|
55
|
+
}));
|
|
56
|
+
// Constraints are separate identity owners on every lane, so a constraint
|
|
57
|
+
// declared inline in a source tree and the same constraint read from
|
|
58
|
+
// pg_constraint compare as the same object instead of changing the table.
|
|
59
|
+
for (const constraint of constraintsByOid.get(oid) ?? []) {
|
|
60
|
+
const constraintName = stringValue(constraint.name);
|
|
61
|
+
const constraintObject = makeObject({ kind: "constraint", name: constraintName, schema, table: name }, `ALTER TABLE ONLY ${formatQualifiedName(schema, name)} ADD CONSTRAINT ${quoteIdent(constraintName)} ${stringValue(constraint.definition)}`, 0, undefined);
|
|
62
|
+
constraintObject.dependencies.push(`${schema}.${name}`);
|
|
63
|
+
objects.push(constraintObject);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return objects;
|
|
67
|
+
}
|
|
68
|
+
function columnFromRow(column) {
|
|
69
|
+
const identity = stringValue(column.identity);
|
|
70
|
+
const generated = stringValue(column.generated);
|
|
71
|
+
const parts = [stringValue(column.type)];
|
|
72
|
+
if (generated === "s" && column.default_expression) {
|
|
73
|
+
parts.push(`GENERATED ALWAYS AS (${stringValue(column.default_expression)}) STORED`);
|
|
74
|
+
}
|
|
75
|
+
else if (identity === "a") {
|
|
76
|
+
parts.push("GENERATED ALWAYS AS IDENTITY");
|
|
77
|
+
}
|
|
78
|
+
else if (identity === "d") {
|
|
79
|
+
parts.push("GENERATED BY DEFAULT AS IDENTITY");
|
|
80
|
+
}
|
|
81
|
+
else if (column.default_expression) {
|
|
82
|
+
parts.push(`DEFAULT ${stringValue(column.default_expression)}`);
|
|
83
|
+
}
|
|
84
|
+
if (column.not_null) {
|
|
85
|
+
parts.push("NOT NULL");
|
|
86
|
+
}
|
|
87
|
+
const facts = {
|
|
88
|
+
definition: parts.join(" "),
|
|
89
|
+
generated: generated === "s",
|
|
90
|
+
hasDefault: Boolean(column.default_expression) && generated !== "s",
|
|
91
|
+
hasInlineConstraint: false,
|
|
92
|
+
identity: identity === "a" || identity === "d",
|
|
93
|
+
name: stringValue(column.name),
|
|
94
|
+
notNull: column.not_null === true,
|
|
95
|
+
type: stringValue(column.type),
|
|
96
|
+
};
|
|
97
|
+
if (facts.hasDefault && !facts.identity) {
|
|
98
|
+
facts.defaultExpression = stringValue(column.default_expression);
|
|
99
|
+
}
|
|
100
|
+
return facts;
|
|
101
|
+
}
|
|
102
|
+
function groupByOid(rows) {
|
|
103
|
+
const groups = new Map();
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
const oid = stringValue(row.oid);
|
|
106
|
+
const group = groups.get(oid) ?? [];
|
|
107
|
+
group.push(row);
|
|
108
|
+
groups.set(oid, group);
|
|
109
|
+
}
|
|
110
|
+
return groups;
|
|
111
|
+
}
|
|
112
|
+
function stringValue(value) {
|
|
113
|
+
return typeof value === "string" ? value : String(value);
|
|
114
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SchemaModel } from "./core.js";
|
|
2
|
+
export interface ExtractCatalogOptions {
|
|
3
|
+
databaseUrl: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
normalize?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function extractCatalogModel(options: ExtractCatalogOptions): Promise<SchemaModel>;
|
|
8
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,WAAW,CAAC;AAQ3D,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAID,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwD9F"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import { collectComments } from "./catalog-comments.js";
|
|
3
|
+
import { collectDefaultPrivileges, collectGrants, collectSequences, collectTypes, } from "./catalog-extras.js";
|
|
4
|
+
import { collectForeignObjects } from "./catalog-foreign.js";
|
|
5
|
+
import { collectTables } from "./catalog-tables.js";
|
|
6
|
+
import { diagnostic } from "./diagnostics.js";
|
|
7
|
+
import { fingerprintObjects, MODEL_FORMAT_VERSION } from "./hash.js";
|
|
8
|
+
import { suppressDefaultAclImpliedGrants } from "./source-normalize.js";
|
|
9
|
+
import { finalizeObjects } from "./sql/facts.js";
|
|
10
|
+
import { formatQualifiedName, quoteIdent, stripOuterDoubleQuotes } from "./sql/identifiers.js";
|
|
11
|
+
import { makeObject } from "./sql/statements.js";
|
|
12
|
+
export async function extractCatalogModel(options) {
|
|
13
|
+
// Empty search_path (pg_dump's convention) so pg_get_expr renders every
|
|
14
|
+
// reference schema-qualified; otherwise the session's search_path decides
|
|
15
|
+
// whether `auth.uid()` reconstructs as `uid()` and the cross-lane hash
|
|
16
|
+
// silently diverges from the declarative spelling.
|
|
17
|
+
const pool = new Pool({
|
|
18
|
+
connectionString: options.databaseUrl,
|
|
19
|
+
max: 4,
|
|
20
|
+
options: "-c search_path=",
|
|
21
|
+
});
|
|
22
|
+
pool.on("error", () => { });
|
|
23
|
+
try {
|
|
24
|
+
const sections = await Promise.all([
|
|
25
|
+
collectSection((objects) => appendSchemas(pool, objects, 0)),
|
|
26
|
+
collectSection((objects) => appendExtensions(pool, objects, 0)),
|
|
27
|
+
collectTypes(pool),
|
|
28
|
+
collectSequences(pool),
|
|
29
|
+
collectTables(pool),
|
|
30
|
+
collectForeignObjects(pool),
|
|
31
|
+
collectSection((objects) => appendFunctions(pool, objects, 0)),
|
|
32
|
+
collectSection((objects) => appendViews(pool, objects, 0)),
|
|
33
|
+
collectSection((objects) => appendIndexes(pool, objects, 0)),
|
|
34
|
+
collectSection((objects) => appendTriggers(pool, objects, 0)),
|
|
35
|
+
collectSection((objects) => appendPoliciesAndRls(pool, objects, 0)),
|
|
36
|
+
collectGrants(pool),
|
|
37
|
+
collectDefaultPrivileges(pool),
|
|
38
|
+
collectComments(pool),
|
|
39
|
+
]);
|
|
40
|
+
const objects = suppressDefaultAclImpliedGrants(sections.flat());
|
|
41
|
+
objects.forEach((object, index) => {
|
|
42
|
+
object.ordinal = index;
|
|
43
|
+
});
|
|
44
|
+
const diagnostics = await finalizeObjects(objects, {
|
|
45
|
+
normalize: options.normalize === true,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
diagnostics,
|
|
49
|
+
fingerprint: fingerprintObjects(objects),
|
|
50
|
+
formatVersion: MODEL_FORMAT_VERSION,
|
|
51
|
+
objects,
|
|
52
|
+
source: options.source ?? "database",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
diagnostics: [
|
|
58
|
+
diagnostic("SUPA_CATALOG_EXTRACT_FAILED", "error", errorMessage(error), {
|
|
59
|
+
hint: "Confirm the database URL is reachable and the role can read pg_catalog.",
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
fingerprint: fingerprintObjects([]),
|
|
63
|
+
objects: [],
|
|
64
|
+
source: options.source ?? "database",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await pool.end();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function collectSection(section) {
|
|
72
|
+
const objects = [];
|
|
73
|
+
await section(objects);
|
|
74
|
+
return objects;
|
|
75
|
+
}
|
|
76
|
+
async function appendSchemas(pool, objects, ordinal) {
|
|
77
|
+
// `public` is created by initdb in every database; modeling it as a
|
|
78
|
+
// droppable object would let a tree that never declares it render
|
|
79
|
+
// DROP SCHEMA public.
|
|
80
|
+
const result = await pool.query(`
|
|
81
|
+
select nspname as name
|
|
82
|
+
from pg_namespace
|
|
83
|
+
where nspname !~ '^pg_'
|
|
84
|
+
and nspname not in ('information_schema', 'public')
|
|
85
|
+
order by nspname
|
|
86
|
+
`);
|
|
87
|
+
for (const row of result.rows) {
|
|
88
|
+
const name = stringValue(row.name);
|
|
89
|
+
objects.push(makeObject({ kind: "schema", name }, `CREATE SCHEMA ${quoteIdent(name)}`, ordinal));
|
|
90
|
+
ordinal += 1;
|
|
91
|
+
}
|
|
92
|
+
return ordinal;
|
|
93
|
+
}
|
|
94
|
+
async function appendExtensions(pool, objects, ordinal) {
|
|
95
|
+
// plpgsql is installed by initdb in every database (same class as the
|
|
96
|
+
// public schema); modeling it would render DROP EXTENSION plpgsql for any
|
|
97
|
+
// tree that never declares it.
|
|
98
|
+
const result = await pool.query(`
|
|
99
|
+
select e.extname as name, n.nspname as schema
|
|
100
|
+
from pg_extension e
|
|
101
|
+
join pg_namespace n on n.oid = e.extnamespace
|
|
102
|
+
where e.extname <> 'plpgsql'
|
|
103
|
+
order by e.extname
|
|
104
|
+
`);
|
|
105
|
+
for (const row of result.rows) {
|
|
106
|
+
const name = stringValue(row.name);
|
|
107
|
+
const schema = stringValue(row.schema);
|
|
108
|
+
objects.push(makeObject({ kind: "extension", name }, `CREATE EXTENSION ${quoteIdent(name)} WITH SCHEMA ${quoteIdent(schema)}`, ordinal, undefined, { schema }));
|
|
109
|
+
ordinal += 1;
|
|
110
|
+
}
|
|
111
|
+
return ordinal;
|
|
112
|
+
}
|
|
113
|
+
async function appendFunctions(pool, objects, ordinal) {
|
|
114
|
+
const result = await pool.query(`
|
|
115
|
+
select
|
|
116
|
+
n.nspname as schema,
|
|
117
|
+
p.proname as name,
|
|
118
|
+
p.prokind as kind,
|
|
119
|
+
oidvectortypes(p.proargtypes) as args,
|
|
120
|
+
p.provariadic <> 0 as variadic,
|
|
121
|
+
pg_get_functiondef(p.oid) as definition
|
|
122
|
+
from pg_proc p
|
|
123
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
124
|
+
where n.nspname !~ '^pg_'
|
|
125
|
+
and n.nspname <> 'information_schema'
|
|
126
|
+
and p.prokind in ('f', 'p')
|
|
127
|
+
order by n.nspname, p.proname, oidvectortypes(p.proargtypes)
|
|
128
|
+
`);
|
|
129
|
+
for (const row of result.rows) {
|
|
130
|
+
const kind = stringValue(row.kind) === "p" ? "procedure" : "function";
|
|
131
|
+
objects.push(makeObject({
|
|
132
|
+
kind,
|
|
133
|
+
name: stringValue(row.name),
|
|
134
|
+
schema: stringValue(row.schema),
|
|
135
|
+
// Routine identity is input argument TYPES only (names are not part
|
|
136
|
+
// of PostgreSQL overload identity), matching the source lane.
|
|
137
|
+
signature: functionSignature(stringValue(row.args), row.variadic === true),
|
|
138
|
+
}, stringValue(row.definition), ordinal));
|
|
139
|
+
ordinal += 1;
|
|
140
|
+
}
|
|
141
|
+
return ordinal;
|
|
142
|
+
}
|
|
143
|
+
// oidvectortypes joins input argument types with ", "; type names contain no
|
|
144
|
+
// commas, so marking the trailing VARIADIC argument by splitting is safe.
|
|
145
|
+
function functionSignature(args, variadic) {
|
|
146
|
+
if (!variadic || args.length === 0) {
|
|
147
|
+
return args;
|
|
148
|
+
}
|
|
149
|
+
const types = args.split(", ");
|
|
150
|
+
types[types.length - 1] = `VARIADIC ${types.at(-1)}`;
|
|
151
|
+
return types.join(", ");
|
|
152
|
+
}
|
|
153
|
+
async function appendViews(pool, objects, ordinal) {
|
|
154
|
+
const result = await pool.query(`
|
|
155
|
+
select
|
|
156
|
+
n.nspname as schema,
|
|
157
|
+
c.relname as name,
|
|
158
|
+
c.relkind as relkind,
|
|
159
|
+
c.reloptions as reloptions,
|
|
160
|
+
pg_get_viewdef(c.oid, true) as definition
|
|
161
|
+
from pg_class c
|
|
162
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
163
|
+
where c.relkind in ('v', 'm')
|
|
164
|
+
and n.nspname !~ '^pg_'
|
|
165
|
+
and n.nspname <> 'information_schema'
|
|
166
|
+
order by n.nspname, c.relname
|
|
167
|
+
`);
|
|
168
|
+
for (const row of result.rows) {
|
|
169
|
+
const kind = stringValue(row.relkind) === "m" ? "materialized-view" : "view";
|
|
170
|
+
const prefix = kind === "materialized-view" ? "CREATE MATERIALIZED VIEW" : "CREATE VIEW";
|
|
171
|
+
const schema = stringValue(row.schema);
|
|
172
|
+
const name = stringValue(row.name);
|
|
173
|
+
const withClause = kind === "view" && reloptionEnabled(row.reloptions, "security_invoker")
|
|
174
|
+
? " WITH (security_invoker = true)"
|
|
175
|
+
: "";
|
|
176
|
+
objects.push(makeObject({ kind, name, schema }, `${prefix} ${formatQualifiedName(schema, name)}${withClause} AS\n${stringValue(row.definition)}`, ordinal));
|
|
177
|
+
ordinal += 1;
|
|
178
|
+
}
|
|
179
|
+
return ordinal;
|
|
180
|
+
}
|
|
181
|
+
function reloptionEnabled(reloptions, option) {
|
|
182
|
+
if (!Array.isArray(reloptions)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
for (const entry of reloptions) {
|
|
186
|
+
const text = String(entry);
|
|
187
|
+
const separator = text.indexOf("=");
|
|
188
|
+
const name = separator === -1 ? text : text.slice(0, separator);
|
|
189
|
+
if (name.trim().toLowerCase() !== option) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const value = separator === -1
|
|
193
|
+
? "true"
|
|
194
|
+
: text
|
|
195
|
+
.slice(separator + 1)
|
|
196
|
+
.trim()
|
|
197
|
+
.toLowerCase();
|
|
198
|
+
return value === "true" || value === "on" || value === "1" || value === "yes";
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
async function appendIndexes(pool, objects, ordinal) {
|
|
203
|
+
// Constraint-backed indexes (PK/UNIQUE/EXCLUDE) are owned by their
|
|
204
|
+
// constraint object; emitting them as index objects would double-own them
|
|
205
|
+
// and plan false index drops against trees that declare the constraint.
|
|
206
|
+
const result = await pool.query(`
|
|
207
|
+
select i.schemaname, i.tablename, i.indexname, i.indexdef
|
|
208
|
+
from pg_indexes i
|
|
209
|
+
where i.schemaname !~ '^pg_'
|
|
210
|
+
and i.schemaname <> 'information_schema'
|
|
211
|
+
and not exists (
|
|
212
|
+
select 1
|
|
213
|
+
from pg_constraint con
|
|
214
|
+
join pg_class ic on ic.oid = con.conindid
|
|
215
|
+
join pg_namespace icn on icn.oid = ic.relnamespace
|
|
216
|
+
where icn.nspname = i.schemaname and ic.relname = i.indexname
|
|
217
|
+
)
|
|
218
|
+
order by i.schemaname, i.indexname
|
|
219
|
+
`);
|
|
220
|
+
for (const row of result.rows) {
|
|
221
|
+
objects.push(makeObject({
|
|
222
|
+
kind: "index",
|
|
223
|
+
name: stringValue(row.indexname),
|
|
224
|
+
schema: stringValue(row.schemaname),
|
|
225
|
+
table: stringValue(row.tablename),
|
|
226
|
+
}, stringValue(row.indexdef), ordinal));
|
|
227
|
+
ordinal += 1;
|
|
228
|
+
}
|
|
229
|
+
return ordinal;
|
|
230
|
+
}
|
|
231
|
+
async function appendTriggers(pool, objects, ordinal) {
|
|
232
|
+
const result = await pool.query(`
|
|
233
|
+
select
|
|
234
|
+
n.nspname as schema,
|
|
235
|
+
c.relname as table_name,
|
|
236
|
+
t.tgname as name,
|
|
237
|
+
pg_get_triggerdef(t.oid, true) as definition
|
|
238
|
+
from pg_trigger t
|
|
239
|
+
join pg_class c on c.oid = t.tgrelid
|
|
240
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
241
|
+
where not t.tgisinternal
|
|
242
|
+
and n.nspname !~ '^pg_'
|
|
243
|
+
and n.nspname <> 'information_schema'
|
|
244
|
+
order by n.nspname, c.relname, t.tgname
|
|
245
|
+
`);
|
|
246
|
+
for (const row of result.rows) {
|
|
247
|
+
objects.push(makeObject({
|
|
248
|
+
kind: "trigger",
|
|
249
|
+
name: stringValue(row.name),
|
|
250
|
+
schema: stringValue(row.schema),
|
|
251
|
+
table: stringValue(row.table_name),
|
|
252
|
+
}, stringValue(row.definition), ordinal));
|
|
253
|
+
ordinal += 1;
|
|
254
|
+
}
|
|
255
|
+
return ordinal;
|
|
256
|
+
}
|
|
257
|
+
async function appendPoliciesAndRls(pool, objects, ordinal) {
|
|
258
|
+
const rls = await pool.query(`
|
|
259
|
+
select n.nspname as schema, c.relname as name, c.relrowsecurity as rls, c.relforcerowsecurity as force
|
|
260
|
+
from pg_class c
|
|
261
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
262
|
+
where c.relkind in ('r', 'p')
|
|
263
|
+
and (c.relrowsecurity or c.relforcerowsecurity)
|
|
264
|
+
and n.nspname !~ '^pg_'
|
|
265
|
+
and n.nspname <> 'information_schema'
|
|
266
|
+
order by n.nspname, c.relname
|
|
267
|
+
`);
|
|
268
|
+
for (const row of rls.rows) {
|
|
269
|
+
const schema = stringValue(row.schema);
|
|
270
|
+
const name = stringValue(row.name);
|
|
271
|
+
// ENABLE and FORCE are independent facets of one table's RLS state and
|
|
272
|
+
// share one rls identity; emit both statements so forced tables render
|
|
273
|
+
// correctly and hash-match a source tree that declares both.
|
|
274
|
+
const statements = [];
|
|
275
|
+
if (row.rls) {
|
|
276
|
+
statements.push(`ALTER TABLE ${formatQualifiedName(schema, name)} ENABLE ROW LEVEL SECURITY`);
|
|
277
|
+
}
|
|
278
|
+
if (row.force) {
|
|
279
|
+
statements.push(`ALTER TABLE ${formatQualifiedName(schema, name)} FORCE ROW LEVEL SECURITY`);
|
|
280
|
+
}
|
|
281
|
+
objects.push(makeObject({ kind: "rls", name, schema, table: name }, statements.join(";\n"), ordinal));
|
|
282
|
+
ordinal += 1;
|
|
283
|
+
}
|
|
284
|
+
const policies = await pool.query(`
|
|
285
|
+
select
|
|
286
|
+
schemaname as schema,
|
|
287
|
+
tablename as table_name,
|
|
288
|
+
policyname as name,
|
|
289
|
+
permissive,
|
|
290
|
+
roles,
|
|
291
|
+
cmd,
|
|
292
|
+
qual,
|
|
293
|
+
with_check
|
|
294
|
+
from pg_policies
|
|
295
|
+
order by schemaname, tablename, policyname
|
|
296
|
+
`);
|
|
297
|
+
for (const row of policies.rows) {
|
|
298
|
+
const schema = stringValue(row.schema);
|
|
299
|
+
const table = stringValue(row.table_name);
|
|
300
|
+
const name = stringValue(row.name);
|
|
301
|
+
const clauses = [
|
|
302
|
+
`CREATE POLICY ${quoteIdent(name)} ON ${formatQualifiedName(schema, table)}`,
|
|
303
|
+
`AS ${stringValue(row.permissive)}`,
|
|
304
|
+
`FOR ${stringValue(row.cmd)}`,
|
|
305
|
+
// pg_policies reports PUBLIC as the role name "public"; quoting it
|
|
306
|
+
// would parse as a named role instead of the ROLESPEC_PUBLIC keyword
|
|
307
|
+
// and break cross-lane hash parity with `TO PUBLIC` trees.
|
|
308
|
+
`TO ${normalizePolicyRoles(row.roles)
|
|
309
|
+
.map((role) => (role === "public" ? "PUBLIC" : quoteIdent(role)))
|
|
310
|
+
.join(", ")}`,
|
|
311
|
+
];
|
|
312
|
+
if (row.qual) {
|
|
313
|
+
clauses.push(`USING (${stringValue(row.qual)})`);
|
|
314
|
+
}
|
|
315
|
+
if (row.with_check) {
|
|
316
|
+
clauses.push(`WITH CHECK (${stringValue(row.with_check)})`);
|
|
317
|
+
}
|
|
318
|
+
objects.push(makeObject({ kind: "policy", name, schema, table }, clauses.join(" "), ordinal));
|
|
319
|
+
ordinal += 1;
|
|
320
|
+
}
|
|
321
|
+
return ordinal;
|
|
322
|
+
}
|
|
323
|
+
function normalizePolicyRoles(value) {
|
|
324
|
+
if (Array.isArray(value)) {
|
|
325
|
+
return value.map(String);
|
|
326
|
+
}
|
|
327
|
+
if (typeof value === "string") {
|
|
328
|
+
const trimmed = value.trim();
|
|
329
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
330
|
+
return trimmed
|
|
331
|
+
.slice(1, -1)
|
|
332
|
+
.split(",")
|
|
333
|
+
.map((role) => stripOuterDoubleQuotes(role.trim()))
|
|
334
|
+
.filter(Boolean);
|
|
335
|
+
}
|
|
336
|
+
return trimmed
|
|
337
|
+
.split(",")
|
|
338
|
+
.map((role) => role.trim())
|
|
339
|
+
.filter(Boolean);
|
|
340
|
+
}
|
|
341
|
+
return [String(value)];
|
|
342
|
+
}
|
|
343
|
+
function stringValue(value) {
|
|
344
|
+
return typeof value === "string" ? value : String(value);
|
|
345
|
+
}
|
|
346
|
+
function errorMessage(error) {
|
|
347
|
+
if (error instanceof Error) {
|
|
348
|
+
return error.message;
|
|
349
|
+
}
|
|
350
|
+
return String(error);
|
|
351
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Diagnostic, SupaschemaConfig } from "./core.js";
|
|
2
|
+
import type { AstStatement } from "./sql/ast.js";
|
|
3
|
+
export declare function newEnumAdditionState(): Map<string, Set<string>>;
|
|
4
|
+
export declare function recordEnumAdditions(statement: AstStatement, state: Map<string, Set<string>>): void;
|
|
5
|
+
export declare function enumValueUseDiagnostics(statement: AstStatement, state: Map<string, Set<string>>, config: SupaschemaConfig): Diagnostic[];
|
|
6
|
+
export declare function escalateNontransactional(diagnostics: Diagnostic[], config: SupaschemaConfig): Diagnostic[];
|
|
7
|
+
//# sourceMappingURL=check-hazards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-hazards.d.ts","sourceRoot":"","sources":["../src/check-hazards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAE/D;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,YAAY,EACvB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC9B,IAAI,CAeN;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,YAAY,EACvB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAC/B,MAAM,EAAE,gBAAgB,GACvB,UAAU,EAAE,CAqBd;AA2CD,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,UAAU,EAAE,EACzB,MAAM,EAAE,gBAAgB,GACvB,UAAU,EAAE,CAcd"}
|