@supabase/postgrest-typegen 0.0.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/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/generation/constants.d.ts +9 -0
- package/dist/generation/constants.js +10 -0
- package/dist/generation/go.d.ts +6 -0
- package/dist/generation/go.js +233 -0
- package/dist/generation/index.d.ts +5 -0
- package/dist/generation/index.js +5 -0
- package/dist/generation/python.d.ts +6 -0
- package/dist/generation/python.js +339 -0
- package/dist/generation/swift.d.ts +11 -0
- package/dist/generation/swift.js +309 -0
- package/dist/generation/typescript.d.ts +32 -0
- package/dist/generation/typescript.js +796 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/introspection/index.d.ts +27 -0
- package/dist/introspection/index.js +70 -0
- package/dist/introspection/normalize.d.ts +31 -0
- package/dist/introspection/normalize.js +65 -0
- package/dist/introspection/relationships.d.ts +47 -0
- package/dist/introspection/relationships.js +85 -0
- package/dist/introspection/sql/columns.sql.d.ts +7 -0
- package/dist/introspection/sql/columns.sql.js +122 -0
- package/dist/introspection/sql/common.d.ts +16 -0
- package/dist/introspection/sql/common.js +1 -0
- package/dist/introspection/sql/foreign_tables.sql.d.ts +6 -0
- package/dist/introspection/sql/foreign_tables.sql.js +18 -0
- package/dist/introspection/sql/functions.sql.d.ts +5 -0
- package/dist/introspection/sql/functions.sql.js +148 -0
- package/dist/introspection/sql/helpers.d.ts +12 -0
- package/dist/introspection/sql/helpers.js +28 -0
- package/dist/introspection/sql/materialized_views.sql.d.ts +4 -0
- package/dist/introspection/sql/materialized_views.sql.js +19 -0
- package/dist/introspection/sql/primary_keys.sql.d.ts +4 -0
- package/dist/introspection/sql/primary_keys.sql.js +32 -0
- package/dist/introspection/sql/schemas.sql.d.ts +6 -0
- package/dist/introspection/sql/schemas.sql.js +24 -0
- package/dist/introspection/sql/table.sql.d.ts +4 -0
- package/dist/introspection/sql/table.sql.js +41 -0
- package/dist/introspection/sql/table_relationships.sql.d.ts +2 -0
- package/dist/introspection/sql/table_relationships.sql.js +49 -0
- package/dist/introspection/sql/types.sql.d.ts +5 -0
- package/dist/introspection/sql/types.sql.js +65 -0
- package/dist/introspection/sql/views.sql.d.ts +4 -0
- package/dist/introspection/sql/views.sql.js +20 -0
- package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -0
- package/dist/introspection/sql/views_key_dependencies.sql.js +195 -0
- package/dist/sort.d.ts +35 -0
- package/dist/sort.js +48 -0
- package/dist/types.d.ts +372 -0
- package/dist/types.js +207 -0
- package/package.json +87 -0
- package/src/generation/constants.ts +11 -0
- package/src/generation/go.ts +356 -0
- package/src/generation/index.ts +13 -0
- package/src/generation/python.ts +461 -0
- package/src/generation/swift.ts +481 -0
- package/src/generation/typescript.ts +1144 -0
- package/src/index.ts +4 -0
- package/src/introspection/index.ts +124 -0
- package/src/introspection/normalize.ts +71 -0
- package/src/introspection/relationships.ts +173 -0
- package/src/introspection/sql/columns.sql.ts +131 -0
- package/src/introspection/sql/common.ts +18 -0
- package/src/introspection/sql/foreign_tables.sql.ts +26 -0
- package/src/introspection/sql/functions.sql.ts +159 -0
- package/src/introspection/sql/helpers.ts +33 -0
- package/src/introspection/sql/materialized_views.sql.ts +25 -0
- package/src/introspection/sql/primary_keys.sql.ts +38 -0
- package/src/introspection/sql/schemas.sql.ts +32 -0
- package/src/introspection/sql/table.sql.ts +47 -0
- package/src/introspection/sql/table_relationships.sql.ts +53 -0
- package/src/introspection/sql/types.sql.ts +74 -0
- package/src/introspection/sql/views.sql.ts +26 -0
- package/src/introspection/sql/views_key_dependencies.sql.ts +199 -0
- package/src/sort.ts +102 -0
- package/src/types.ts +237 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type GeneratorMetadata } from "../types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural database interface. `pg.Pool` / `pg.Client` satisfy it,
|
|
4
|
+
* as do Bun and other drivers. postgres-meta injects its forked-pg pool here,
|
|
5
|
+
* keeping its own error handling on its side of the boundary. Errors throw;
|
|
6
|
+
* callers adapt.
|
|
7
|
+
*/
|
|
8
|
+
export interface Queryable {
|
|
9
|
+
query(sql: string): Promise<{
|
|
10
|
+
rows: any[];
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface IntrospectOptions {
|
|
14
|
+
includedSchemas?: string[];
|
|
15
|
+
excludedSchemas?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Introspect a database into the `GeneratorMetadata` contract.
|
|
19
|
+
*
|
|
20
|
+
* Port of `getGeneratorMetadata` from `postgres-meta/src/lib/generators.ts`.
|
|
21
|
+
* Instead of going through the `PostgresMeta*` manager classes, it calls the
|
|
22
|
+
* ported SQL builders directly with the single option combination the
|
|
23
|
+
* generator path uses, then applies the int8 `normalize` coercion so output is
|
|
24
|
+
* identical regardless of driver. Connection lifecycle is the caller's
|
|
25
|
+
* responsibility — unlike `getGeneratorMetadata`, this does not end the pool.
|
|
26
|
+
*/
|
|
27
|
+
export declare function introspect(db: Queryable, opts?: IntrospectOptions): Promise<GeneratorMetadata>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { GENERATOR_METADATA_VERSION, } from "../types.js";
|
|
2
|
+
import { normalizeRows } from "./normalize.js";
|
|
3
|
+
import { listRelationships } from "./relationships.js";
|
|
4
|
+
import { COLUMNS_SQL } from "./sql/columns.sql.js";
|
|
5
|
+
import { FOREIGN_TABLES_SQL } from "./sql/foreign_tables.sql.js";
|
|
6
|
+
import { FUNCTIONS_SQL } from "./sql/functions.sql.js";
|
|
7
|
+
import { DEFAULT_SYSTEM_SCHEMAS, filterByList } from "./sql/helpers.js";
|
|
8
|
+
import { MATERIALIZED_VIEWS_SQL } from "./sql/materialized_views.sql.js";
|
|
9
|
+
import { PRIMARY_KEYS_SQL } from "./sql/primary_keys.sql.js";
|
|
10
|
+
import { SCHEMAS_SQL } from "./sql/schemas.sql.js";
|
|
11
|
+
import { TABLES_SQL } from "./sql/table.sql.js";
|
|
12
|
+
import { TYPES_SQL } from "./sql/types.sql.js";
|
|
13
|
+
import { VIEWS_SQL } from "./sql/views.sql.js";
|
|
14
|
+
/**
|
|
15
|
+
* Introspect a database into the `GeneratorMetadata` contract.
|
|
16
|
+
*
|
|
17
|
+
* Port of `getGeneratorMetadata` from `postgres-meta/src/lib/generators.ts`.
|
|
18
|
+
* Instead of going through the `PostgresMeta*` manager classes, it calls the
|
|
19
|
+
* ported SQL builders directly with the single option combination the
|
|
20
|
+
* generator path uses, then applies the int8 `normalize` coercion so output is
|
|
21
|
+
* identical regardless of driver. Connection lifecycle is the caller's
|
|
22
|
+
* responsibility — unlike `getGeneratorMetadata`, this does not end the pool.
|
|
23
|
+
*/
|
|
24
|
+
export async function introspect(db, opts = {}) {
|
|
25
|
+
const includedSchemas = opts.includedSchemas ?? [];
|
|
26
|
+
const excludedSchemas = opts.excludedSchemas ?? [];
|
|
27
|
+
// Managers receive `undefined` (not an empty array) when no filter is set.
|
|
28
|
+
const included = includedSchemas.length > 0 ? includedSchemas : undefined;
|
|
29
|
+
const excluded = excludedSchemas.length > 0 ? excludedSchemas : undefined;
|
|
30
|
+
// Most queries exclude the system schemas by default; foreign tables and
|
|
31
|
+
// materialized views do not (matching the respective manager `list()` calls).
|
|
32
|
+
const systemExcludingFilter = filterByList(included, excluded, DEFAULT_SYSTEM_SCHEMAS);
|
|
33
|
+
const plainFilter = filterByList(included, excluded);
|
|
34
|
+
const queryRows = async (sql) => normalizeRows((await db.query(sql)).rows);
|
|
35
|
+
const schemas = await queryRows(SCHEMAS_SQL({
|
|
36
|
+
includeSystemSchemas: false,
|
|
37
|
+
nameFilter: systemExcludingFilter,
|
|
38
|
+
}));
|
|
39
|
+
const tables = await queryRows(TABLES_SQL({ schemaFilter: systemExcludingFilter }));
|
|
40
|
+
const foreignTables = await queryRows(FOREIGN_TABLES_SQL({ schemaFilter: plainFilter }));
|
|
41
|
+
const views = await queryRows(VIEWS_SQL({ schemaFilter: systemExcludingFilter }));
|
|
42
|
+
const materializedViews = await queryRows(MATERIALIZED_VIEWS_SQL({ schemaFilter: plainFilter }));
|
|
43
|
+
const columns = await queryRows(COLUMNS_SQL({ schemaFilter: systemExcludingFilter }));
|
|
44
|
+
const primaryKeys = await queryRows(PRIMARY_KEYS_SQL({ schemaFilter: systemExcludingFilter }));
|
|
45
|
+
const relationships = await listRelationships(db, {
|
|
46
|
+
includedSchemas: included,
|
|
47
|
+
excludedSchemas: excluded,
|
|
48
|
+
});
|
|
49
|
+
const functions = await queryRows(FUNCTIONS_SQL({ schemaFilter: systemExcludingFilter }));
|
|
50
|
+
// types: includeSystemSchemas true (no schema filter), table + array types included.
|
|
51
|
+
const types = await queryRows(TYPES_SQL({
|
|
52
|
+
schemaFilter: "",
|
|
53
|
+
includeTableTypes: true,
|
|
54
|
+
includeArrayTypes: true,
|
|
55
|
+
}));
|
|
56
|
+
return {
|
|
57
|
+
version: GENERATOR_METADATA_VERSION,
|
|
58
|
+
schemas: schemas.filter(({ name }) => !excludedSchemas.includes(name) &&
|
|
59
|
+
(includedSchemas.length === 0 || includedSchemas.includes(name))),
|
|
60
|
+
tables,
|
|
61
|
+
foreignTables,
|
|
62
|
+
views,
|
|
63
|
+
materializedViews,
|
|
64
|
+
columns,
|
|
65
|
+
primaryKeys,
|
|
66
|
+
relationships,
|
|
67
|
+
functions: functions.filter(({ return_type }) => !["trigger", "event_trigger"].includes(return_type)),
|
|
68
|
+
types,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* int8 → number coercion replicating postgres-meta's global int8 type parser.
|
|
3
|
+
*
|
|
4
|
+
* postgres-meta installs `pg.types.setTypeParser(INT8, ...)` in
|
|
5
|
+
* `src/lib/db.ts`, so every `bigint`/`int8` column comes back as a JS number
|
|
6
|
+
* when it fits in a safe integer, and as the raw string otherwise. Stock `pg`
|
|
7
|
+
* (and other drivers) instead return int8 as a string. To keep
|
|
8
|
+
* `GeneratorMetadata` byte-identical regardless of driver, we re-apply that
|
|
9
|
+
* exact coercion to the known int8 top-level fields after each query.
|
|
10
|
+
*
|
|
11
|
+
* Only top-level int8 columns need this: int8 values embedded inside `jsonb`
|
|
12
|
+
* columns (e.g. `args[].type_id`, composite type attributes)
|
|
13
|
+
* are decoded by the JSON parser as numbers in every driver, matching
|
|
14
|
+
* postgres-meta. Composite text ids like a column's `"<oid>.<attnum>"` are not
|
|
15
|
+
* safe integers, so the `Number.isSafeInteger` guard leaves them untouched —
|
|
16
|
+
* exactly as postgres-meta's parser does, since that column is `text`, not
|
|
17
|
+
* int8.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Coerce a single value the way postgres-meta's `setTypeParser(INT8, ...)`
|
|
21
|
+
* does: parse to a number when it is a safe integer, otherwise keep the raw
|
|
22
|
+
* string. Non-string values (already-numbers, `null`) pass through unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function coerceInt8Value(value: unknown): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Apply {@link coerceInt8Value} to the known int8 fields of a single row,
|
|
27
|
+
* mutating and returning it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeRow<T extends Record<string, unknown>>(row: T): T;
|
|
30
|
+
/** Apply {@link normalizeRow} to every row of a query result. */
|
|
31
|
+
export declare function normalizeRows<T extends Record<string, unknown>>(rows: T[]): T[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* int8 → number coercion replicating postgres-meta's global int8 type parser.
|
|
3
|
+
*
|
|
4
|
+
* postgres-meta installs `pg.types.setTypeParser(INT8, ...)` in
|
|
5
|
+
* `src/lib/db.ts`, so every `bigint`/`int8` column comes back as a JS number
|
|
6
|
+
* when it fits in a safe integer, and as the raw string otherwise. Stock `pg`
|
|
7
|
+
* (and other drivers) instead return int8 as a string. To keep
|
|
8
|
+
* `GeneratorMetadata` byte-identical regardless of driver, we re-apply that
|
|
9
|
+
* exact coercion to the known int8 top-level fields after each query.
|
|
10
|
+
*
|
|
11
|
+
* Only top-level int8 columns need this: int8 values embedded inside `jsonb`
|
|
12
|
+
* columns (e.g. `args[].type_id`, composite type attributes)
|
|
13
|
+
* are decoded by the JSON parser as numbers in every driver, matching
|
|
14
|
+
* postgres-meta. Composite text ids like a column's `"<oid>.<attnum>"` are not
|
|
15
|
+
* safe integers, so the `Number.isSafeInteger` guard leaves them untouched —
|
|
16
|
+
* exactly as postgres-meta's parser does, since that column is `text`, not
|
|
17
|
+
* int8.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Top-level result columns that are `int8` across the typegen introspection
|
|
21
|
+
* queries (schemas/tables/foreign-tables/views/materialized-views/columns/
|
|
22
|
+
* functions/types). Field names are unique enough across these queries that a
|
|
23
|
+
* name-based set faithfully reproduces the OID-based global parser.
|
|
24
|
+
*/
|
|
25
|
+
const INT8_FIELDS = [
|
|
26
|
+
"id",
|
|
27
|
+
"table_id",
|
|
28
|
+
"type_relation_id",
|
|
29
|
+
"return_type_id",
|
|
30
|
+
"return_type_relation_id",
|
|
31
|
+
"bytes",
|
|
32
|
+
"live_rows_estimate",
|
|
33
|
+
"dead_rows_estimate",
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Coerce a single value the way postgres-meta's `setTypeParser(INT8, ...)`
|
|
37
|
+
* does: parse to a number when it is a safe integer, otherwise keep the raw
|
|
38
|
+
* string. Non-string values (already-numbers, `null`) pass through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function coerceInt8Value(value) {
|
|
41
|
+
if (typeof value !== "string") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
const asNumber = Number(value);
|
|
45
|
+
if (Number.isSafeInteger(asNumber)) {
|
|
46
|
+
return asNumber;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply {@link coerceInt8Value} to the known int8 fields of a single row,
|
|
52
|
+
* mutating and returning it.
|
|
53
|
+
*/
|
|
54
|
+
export function normalizeRow(row) {
|
|
55
|
+
for (const field of INT8_FIELDS) {
|
|
56
|
+
if (field in row) {
|
|
57
|
+
row[field] = coerceInt8Value(row[field]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return row;
|
|
61
|
+
}
|
|
62
|
+
/** Apply {@link normalizeRow} to every row of a query result. */
|
|
63
|
+
export function normalizeRows(rows) {
|
|
64
|
+
return rows.map(normalizeRow);
|
|
65
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PostgresRelationship } from "../types.ts";
|
|
2
|
+
import type { IntrospectOptions, Queryable } from "./index.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Port of `postgres-meta/src/lib/PostgresMetaRelationships.ts`.
|
|
5
|
+
*
|
|
6
|
+
* The relationships used for type generation come in two parts:
|
|
7
|
+
* 1. table↔table m2o/o2o relationships, read directly from `pg_constraint`
|
|
8
|
+
* (`TABLE_RELATIONSHIPS_SQL`); and
|
|
9
|
+
* 2. relationships that involve a view on either side, which PostgREST derives
|
|
10
|
+
* by mapping a base table's foreign key columns onto the view columns that
|
|
11
|
+
* select them (`VIEWS_KEY_DEPENDENCIES_SQL` + the cartesian-product
|
|
12
|
+
* expansion below). This second part is pure TypeScript, not SQL — ported
|
|
13
|
+
* verbatim from upstream.
|
|
14
|
+
*
|
|
15
|
+
* Adapted from:
|
|
16
|
+
* https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L392
|
|
17
|
+
*/
|
|
18
|
+
type ColDep = {
|
|
19
|
+
table_column: string;
|
|
20
|
+
view_columns: string[];
|
|
21
|
+
};
|
|
22
|
+
export type ViewKeyDependency = {
|
|
23
|
+
table_schema: string;
|
|
24
|
+
table_name: string;
|
|
25
|
+
view_schema: string;
|
|
26
|
+
view_name: string;
|
|
27
|
+
constraint_name: string;
|
|
28
|
+
constraint_type: "f" | "f_ref" | "p" | "p_ref";
|
|
29
|
+
column_dependencies: ColDep[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Expand the table↔table relationships into the view↔table / table↔view /
|
|
33
|
+
* view↔view relationships implied by the view key dependencies. Pure function
|
|
34
|
+
* (no database) so the cartesian-product expansion can be unit-tested directly.
|
|
35
|
+
*
|
|
36
|
+
* Ported verbatim from `PostgresMetaRelationships.list()` (the body of the
|
|
37
|
+
* `allViewM2oAndO2oRelationships` block).
|
|
38
|
+
*/
|
|
39
|
+
export declare function expandViewRelationships(allTableM2oAndO2oRelationships: PostgresRelationship[], viewsKeyDependencies: ViewKeyDependency[]): PostgresRelationship[];
|
|
40
|
+
/**
|
|
41
|
+
* List all relationships (table↔table plus the view-derived ones) for the
|
|
42
|
+
* given schema filter. Mirrors `PostgresMetaRelationships.list()` with the
|
|
43
|
+
* generator path's defaults (`includeSystemSchemas: false`). Errors from the
|
|
44
|
+
* injected `Queryable` propagate by throwing — callers adapt.
|
|
45
|
+
*/
|
|
46
|
+
export declare function listRelationships(db: Queryable, { includedSchemas, excludedSchemas }?: IntrospectOptions): Promise<PostgresRelationship[]>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DEFAULT_SYSTEM_SCHEMAS, filterByList } from "./sql/helpers.js";
|
|
2
|
+
import { TABLE_RELATIONSHIPS_SQL } from "./sql/table_relationships.sql.js";
|
|
3
|
+
import { VIEWS_KEY_DEPENDENCIES_SQL } from "./sql/views_key_dependencies.sql.js";
|
|
4
|
+
/**
|
|
5
|
+
* Expand the table↔table relationships into the view↔table / table↔view /
|
|
6
|
+
* view↔view relationships implied by the view key dependencies. Pure function
|
|
7
|
+
* (no database) so the cartesian-product expansion can be unit-tested directly.
|
|
8
|
+
*
|
|
9
|
+
* Ported verbatim from `PostgresMetaRelationships.list()` (the body of the
|
|
10
|
+
* `allViewM2oAndO2oRelationships` block).
|
|
11
|
+
*/
|
|
12
|
+
export function expandViewRelationships(allTableM2oAndO2oRelationships, viewsKeyDependencies) {
|
|
13
|
+
return allTableM2oAndO2oRelationships.flatMap((r) => {
|
|
14
|
+
const expandKeyDepCols = (colDeps) => {
|
|
15
|
+
const tableColumns = colDeps.map(({ table_column }) => table_column);
|
|
16
|
+
// https://gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef?permalink_comment_id=3474581#gistcomment-3474581
|
|
17
|
+
const cartesianProduct = (allEntries) => {
|
|
18
|
+
return allEntries.reduce((results, entries) => results
|
|
19
|
+
.map((result) => entries.map((entry) => result.concat(entry)))
|
|
20
|
+
.reduce((subResults, result) => subResults.concat(result), []), [[]]);
|
|
21
|
+
};
|
|
22
|
+
const viewColumnsPermutations = cartesianProduct(colDeps.map((cd) => cd.view_columns));
|
|
23
|
+
return viewColumnsPermutations.map((viewColumns) => ({
|
|
24
|
+
tableColumns,
|
|
25
|
+
viewColumns,
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
const viewToTableKeyDeps = viewsKeyDependencies.filter((vkd) => vkd.table_schema === r.schema &&
|
|
29
|
+
vkd.table_name === r.relation &&
|
|
30
|
+
vkd.constraint_name === r.foreign_key_name &&
|
|
31
|
+
vkd.constraint_type === "f");
|
|
32
|
+
const tableToViewKeyDeps = viewsKeyDependencies.filter((vkd) => vkd.table_schema === r.referenced_schema &&
|
|
33
|
+
vkd.table_name === r.referenced_relation &&
|
|
34
|
+
vkd.constraint_name === r.foreign_key_name &&
|
|
35
|
+
vkd.constraint_type === "f_ref");
|
|
36
|
+
const viewToTableRelationships = viewToTableKeyDeps.flatMap((vtkd) => expandKeyDepCols(vtkd.column_dependencies).map(({ viewColumns }) => ({
|
|
37
|
+
foreign_key_name: r.foreign_key_name,
|
|
38
|
+
schema: vtkd.view_schema,
|
|
39
|
+
relation: vtkd.view_name,
|
|
40
|
+
columns: viewColumns,
|
|
41
|
+
is_one_to_one: r.is_one_to_one,
|
|
42
|
+
referenced_schema: r.referenced_schema,
|
|
43
|
+
referenced_relation: r.referenced_relation,
|
|
44
|
+
referenced_columns: r.referenced_columns,
|
|
45
|
+
})));
|
|
46
|
+
const tableToViewRelationships = tableToViewKeyDeps.flatMap((tvkd) => expandKeyDepCols(tvkd.column_dependencies).map(({ viewColumns }) => ({
|
|
47
|
+
foreign_key_name: r.foreign_key_name,
|
|
48
|
+
schema: r.schema,
|
|
49
|
+
relation: r.relation,
|
|
50
|
+
columns: r.columns,
|
|
51
|
+
is_one_to_one: r.is_one_to_one,
|
|
52
|
+
referenced_schema: tvkd.view_schema,
|
|
53
|
+
referenced_relation: tvkd.view_name,
|
|
54
|
+
referenced_columns: viewColumns,
|
|
55
|
+
})));
|
|
56
|
+
const viewToViewRelationships = viewToTableKeyDeps.flatMap((vtkd) => expandKeyDepCols(vtkd.column_dependencies).flatMap(({ viewColumns }) => tableToViewKeyDeps.flatMap((tvkd) => expandKeyDepCols(tvkd.column_dependencies).map(({ viewColumns: referencedViewColumns }) => ({
|
|
57
|
+
foreign_key_name: r.foreign_key_name,
|
|
58
|
+
schema: vtkd.view_schema,
|
|
59
|
+
relation: vtkd.view_name,
|
|
60
|
+
columns: viewColumns,
|
|
61
|
+
is_one_to_one: r.is_one_to_one,
|
|
62
|
+
referenced_schema: tvkd.view_schema,
|
|
63
|
+
referenced_relation: tvkd.view_name,
|
|
64
|
+
referenced_columns: referencedViewColumns,
|
|
65
|
+
})))));
|
|
66
|
+
return [
|
|
67
|
+
...viewToTableRelationships,
|
|
68
|
+
...tableToViewRelationships,
|
|
69
|
+
...viewToViewRelationships,
|
|
70
|
+
];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* List all relationships (table↔table plus the view-derived ones) for the
|
|
75
|
+
* given schema filter. Mirrors `PostgresMetaRelationships.list()` with the
|
|
76
|
+
* generator path's defaults (`includeSystemSchemas: false`). Errors from the
|
|
77
|
+
* injected `Queryable` propagate by throwing — callers adapt.
|
|
78
|
+
*/
|
|
79
|
+
export async function listRelationships(db, { includedSchemas, excludedSchemas } = {}) {
|
|
80
|
+
const schemaFilter = filterByList(includedSchemas, excludedSchemas, DEFAULT_SYSTEM_SCHEMAS);
|
|
81
|
+
const { rows: allTableM2oAndO2oRelationships } = await db.query(TABLE_RELATIONSHIPS_SQL({ schemaFilter }));
|
|
82
|
+
const { rows: viewsKeyDependencies } = await db.query(VIEWS_KEY_DEPENDENCIES_SQL({ schemaFilter }));
|
|
83
|
+
const allViewM2oAndO2oRelationships = expandViewRelationships(allTableM2oAndO2oRelationships, viewsKeyDependencies);
|
|
84
|
+
return allTableM2oAndO2oRelationships.concat(allViewM2oAndO2oRelationships);
|
|
85
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
|
|
2
|
+
export declare const COLUMNS_SQL: (props: SQLQueryPropsWithSchemaFilter & {
|
|
3
|
+
tableIdFilter?: string;
|
|
4
|
+
tableIdentifierFilter?: string;
|
|
5
|
+
columnNameFilter?: string;
|
|
6
|
+
idsFilter?: string;
|
|
7
|
+
}) => string;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { literal } from "pg-format";
|
|
2
|
+
export const COLUMNS_SQL = (props) => /* SQL */ `
|
|
3
|
+
-- Adapted from information_schema.columns
|
|
4
|
+
|
|
5
|
+
SELECT
|
|
6
|
+
c.oid :: int8 AS table_id,
|
|
7
|
+
nc.nspname AS schema,
|
|
8
|
+
c.relname AS table,
|
|
9
|
+
(c.oid || '.' || a.attnum) AS id,
|
|
10
|
+
a.attnum AS ordinal_position,
|
|
11
|
+
a.attname AS name,
|
|
12
|
+
CASE
|
|
13
|
+
WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid)
|
|
14
|
+
ELSE NULL
|
|
15
|
+
END AS default_value,
|
|
16
|
+
CASE
|
|
17
|
+
WHEN t.typtype = 'd' THEN CASE
|
|
18
|
+
WHEN bt.typelem <> 0 :: oid
|
|
19
|
+
AND bt.typlen = -1 THEN 'ARRAY'
|
|
20
|
+
WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL)
|
|
21
|
+
ELSE 'USER-DEFINED'
|
|
22
|
+
END
|
|
23
|
+
ELSE CASE
|
|
24
|
+
WHEN t.typelem <> 0 :: oid
|
|
25
|
+
AND t.typlen = -1 THEN 'ARRAY'
|
|
26
|
+
WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL)
|
|
27
|
+
ELSE 'USER-DEFINED'
|
|
28
|
+
END
|
|
29
|
+
END AS data_type,
|
|
30
|
+
COALESCE(bt.typname, t.typname) AS format,
|
|
31
|
+
COALESCE(nbt.nspname, nt.nspname) AS type_schema,
|
|
32
|
+
a.attidentity IN ('a', 'd') AS is_identity,
|
|
33
|
+
CASE
|
|
34
|
+
a.attidentity
|
|
35
|
+
WHEN 'a' THEN 'ALWAYS'
|
|
36
|
+
WHEN 'd' THEN 'BY DEFAULT'
|
|
37
|
+
ELSE NULL
|
|
38
|
+
END AS identity_generation,
|
|
39
|
+
a.attgenerated IN ('s') AS is_generated,
|
|
40
|
+
NOT (
|
|
41
|
+
a.attnotnull
|
|
42
|
+
OR t.typtype = 'd' AND t.typnotnull
|
|
43
|
+
) AS is_nullable,
|
|
44
|
+
(
|
|
45
|
+
c.relkind IN ('r', 'p')
|
|
46
|
+
OR c.relkind IN ('v', 'f') AND pg_column_is_updatable(c.oid, a.attnum, FALSE)
|
|
47
|
+
) AS is_updatable,
|
|
48
|
+
uniques.table_id IS NOT NULL AS is_unique,
|
|
49
|
+
check_constraints.definition AS "check",
|
|
50
|
+
array_to_json(
|
|
51
|
+
array(
|
|
52
|
+
SELECT
|
|
53
|
+
enumlabel
|
|
54
|
+
FROM
|
|
55
|
+
pg_catalog.pg_enum enums
|
|
56
|
+
WHERE
|
|
57
|
+
enums.enumtypid = coalesce(bt.oid, t.oid)
|
|
58
|
+
OR enums.enumtypid = coalesce(bt.typelem, t.typelem)
|
|
59
|
+
ORDER BY
|
|
60
|
+
enums.enumsortorder
|
|
61
|
+
)
|
|
62
|
+
) AS enums,
|
|
63
|
+
col_description(c.oid, a.attnum) AS comment
|
|
64
|
+
FROM
|
|
65
|
+
pg_attribute a
|
|
66
|
+
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid
|
|
67
|
+
AND a.attnum = ad.adnum
|
|
68
|
+
JOIN (
|
|
69
|
+
pg_class c
|
|
70
|
+
JOIN pg_namespace nc ON c.relnamespace = nc.oid
|
|
71
|
+
) ON a.attrelid = c.oid
|
|
72
|
+
JOIN (
|
|
73
|
+
pg_type t
|
|
74
|
+
JOIN pg_namespace nt ON t.typnamespace = nt.oid
|
|
75
|
+
) ON a.atttypid = t.oid
|
|
76
|
+
LEFT JOIN (
|
|
77
|
+
pg_type bt
|
|
78
|
+
JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid
|
|
79
|
+
) ON t.typtype = 'd'
|
|
80
|
+
AND t.typbasetype = bt.oid
|
|
81
|
+
LEFT JOIN (
|
|
82
|
+
SELECT DISTINCT ON (table_id, ordinal_position)
|
|
83
|
+
conrelid AS table_id,
|
|
84
|
+
conkey[1] AS ordinal_position
|
|
85
|
+
FROM pg_catalog.pg_constraint
|
|
86
|
+
WHERE contype = 'u' AND cardinality(conkey) = 1
|
|
87
|
+
) AS uniques ON uniques.table_id = c.oid AND uniques.ordinal_position = a.attnum
|
|
88
|
+
LEFT JOIN (
|
|
89
|
+
-- We only select the first column check
|
|
90
|
+
SELECT DISTINCT ON (table_id, ordinal_position)
|
|
91
|
+
conrelid AS table_id,
|
|
92
|
+
conkey[1] AS ordinal_position,
|
|
93
|
+
substring(
|
|
94
|
+
pg_get_constraintdef(pg_constraint.oid, true),
|
|
95
|
+
8,
|
|
96
|
+
length(pg_get_constraintdef(pg_constraint.oid, true)) - 8
|
|
97
|
+
) AS "definition"
|
|
98
|
+
FROM pg_constraint
|
|
99
|
+
WHERE contype = 'c' AND cardinality(conkey) = 1
|
|
100
|
+
ORDER BY table_id, ordinal_position, oid asc
|
|
101
|
+
) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum
|
|
102
|
+
WHERE
|
|
103
|
+
${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ""}
|
|
104
|
+
${props.idsFilter ? `(c.oid || '.' || a.attnum) ${props.idsFilter} AND` : ""}
|
|
105
|
+
${props.columnNameFilter ? `(c.relname || '.' || a.attname) ${props.columnNameFilter} AND` : ""}
|
|
106
|
+
${props.tableIdFilter ? `c.oid ${props.tableIdFilter} AND` : ""}
|
|
107
|
+
${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
|
|
108
|
+
NOT pg_is_other_temp_schema(nc.oid)
|
|
109
|
+
AND a.attnum > 0
|
|
110
|
+
AND NOT a.attisdropped
|
|
111
|
+
AND (c.relkind IN ('r', 'v', 'm', 'f', 'p'))
|
|
112
|
+
AND (
|
|
113
|
+
pg_has_role(c.relowner, 'USAGE')
|
|
114
|
+
OR has_column_privilege(
|
|
115
|
+
c.oid,
|
|
116
|
+
a.attnum,
|
|
117
|
+
'SELECT, INSERT, UPDATE, REFERENCES'
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
121
|
+
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
122
|
+
`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prop shapes for the SQL builders, ported from
|
|
3
|
+
* `postgres-meta/src/lib/sql/common.ts`. Only the variants used by the
|
|
4
|
+
* typegen introspection path are kept.
|
|
5
|
+
*/
|
|
6
|
+
export type SQLQueryProps = {
|
|
7
|
+
limit?: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
};
|
|
10
|
+
export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & {
|
|
11
|
+
schemaFilter?: string;
|
|
12
|
+
};
|
|
13
|
+
export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & {
|
|
14
|
+
schemaFilter?: string;
|
|
15
|
+
idsFilter?: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { literal } from "pg-format";
|
|
2
|
+
export const FOREIGN_TABLES_SQL = (props) => /* SQL */ `
|
|
3
|
+
SELECT
|
|
4
|
+
c.oid :: int8 AS id,
|
|
5
|
+
n.nspname AS schema,
|
|
6
|
+
c.relname AS name,
|
|
7
|
+
obj_description(c.oid) AS comment
|
|
8
|
+
FROM
|
|
9
|
+
pg_class c
|
|
10
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
11
|
+
WHERE
|
|
12
|
+
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
13
|
+
${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
|
|
14
|
+
${props.tableIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.tableIdentifierFilter} AND` : ""}
|
|
15
|
+
c.relkind = 'f'
|
|
16
|
+
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
17
|
+
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
18
|
+
`;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// oxlint-disable typescript/restrict-template-expressions -- verbatim port of postgres-meta's
|
|
2
|
+
// builder, which interpolates the `args` string[] into the SQL (relying on Array#toString). The
|
|
3
|
+
// expression sits inside a template literal where a targeted disable comment would corrupt the SQL.
|
|
4
|
+
import { literal } from "pg-format";
|
|
5
|
+
export const FUNCTIONS_SQL = (props) => /* SQL */ `
|
|
6
|
+
-- CTE with sane arg_modes, arg_names, and arg_types.
|
|
7
|
+
-- All three are always of the same length.
|
|
8
|
+
-- All three include all args, including OUT and TABLE args.
|
|
9
|
+
with functions as (
|
|
10
|
+
select
|
|
11
|
+
p.*,
|
|
12
|
+
-- proargmodes is null when all arg modes are IN
|
|
13
|
+
coalesce(
|
|
14
|
+
p.proargmodes,
|
|
15
|
+
array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
|
|
16
|
+
) as arg_modes,
|
|
17
|
+
-- proargnames is null when all args are unnamed
|
|
18
|
+
coalesce(
|
|
19
|
+
p.proargnames,
|
|
20
|
+
array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
|
|
21
|
+
) as arg_names,
|
|
22
|
+
-- proallargtypes is null when all arg modes are IN
|
|
23
|
+
coalesce(p.proallargtypes, p.proargtypes) as arg_types,
|
|
24
|
+
array_cat(
|
|
25
|
+
array_fill(false, array[pronargs - pronargdefaults]),
|
|
26
|
+
array_fill(true, array[pronargdefaults])) as arg_has_defaults
|
|
27
|
+
from
|
|
28
|
+
pg_proc as p
|
|
29
|
+
${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ""}
|
|
30
|
+
where
|
|
31
|
+
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
32
|
+
${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ""}
|
|
33
|
+
${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ""}
|
|
34
|
+
${props.args === undefined
|
|
35
|
+
? ""
|
|
36
|
+
: props.args.length > 0
|
|
37
|
+
? `p.proargtypes::text = ${props.args.length
|
|
38
|
+
? `(
|
|
39
|
+
SELECT STRING_AGG(type_oid::text, ' ') FROM (
|
|
40
|
+
SELECT (
|
|
41
|
+
split_args.arr[
|
|
42
|
+
array_length(
|
|
43
|
+
split_args.arr,
|
|
44
|
+
1
|
|
45
|
+
)
|
|
46
|
+
]::regtype::oid
|
|
47
|
+
) AS type_oid FROM (
|
|
48
|
+
SELECT STRING_TO_ARRAY(
|
|
49
|
+
UNNEST(
|
|
50
|
+
ARRAY[${props.args}]
|
|
51
|
+
),
|
|
52
|
+
' '
|
|
53
|
+
) AS arr
|
|
54
|
+
) AS split_args
|
|
55
|
+
) args
|
|
56
|
+
)`
|
|
57
|
+
: "''"} AND`
|
|
58
|
+
: ""}
|
|
59
|
+
p.prokind = 'f'
|
|
60
|
+
)
|
|
61
|
+
select
|
|
62
|
+
f.oid::int8 as id,
|
|
63
|
+
n.nspname as schema,
|
|
64
|
+
f.proname as name,
|
|
65
|
+
l.lanname as language,
|
|
66
|
+
case
|
|
67
|
+
when l.lanname = 'internal' then ''
|
|
68
|
+
else f.prosrc
|
|
69
|
+
end as definition,
|
|
70
|
+
case
|
|
71
|
+
when l.lanname = 'internal' then f.prosrc
|
|
72
|
+
else pg_get_functiondef(f.oid)
|
|
73
|
+
end as complete_statement,
|
|
74
|
+
coalesce(f_args.args, '[]') as args,
|
|
75
|
+
pg_get_function_arguments(f.oid) as argument_types,
|
|
76
|
+
pg_get_function_identity_arguments(f.oid) as identity_argument_types,
|
|
77
|
+
f.prorettype::int8 as return_type_id,
|
|
78
|
+
pg_get_function_result(f.oid) as return_type,
|
|
79
|
+
nullif(rt.typrelid::int8, 0) as return_type_relation_id,
|
|
80
|
+
f.proretset as is_set_returning_function,
|
|
81
|
+
case
|
|
82
|
+
when f.proretset then nullif(f.prorows, 0)
|
|
83
|
+
else null
|
|
84
|
+
end as prorows,
|
|
85
|
+
case
|
|
86
|
+
when f.provolatile = 'i' then 'IMMUTABLE'
|
|
87
|
+
when f.provolatile = 's' then 'STABLE'
|
|
88
|
+
when f.provolatile = 'v' then 'VOLATILE'
|
|
89
|
+
end as behavior,
|
|
90
|
+
f.prosecdef as security_definer,
|
|
91
|
+
f_config.config_params as config_params
|
|
92
|
+
from
|
|
93
|
+
functions f
|
|
94
|
+
left join pg_namespace n on f.pronamespace = n.oid
|
|
95
|
+
left join pg_language l on f.prolang = l.oid
|
|
96
|
+
left join pg_type rt on rt.oid = f.prorettype
|
|
97
|
+
left join (
|
|
98
|
+
select
|
|
99
|
+
oid,
|
|
100
|
+
jsonb_object_agg(param, value) filter (where param is not null) as config_params
|
|
101
|
+
from
|
|
102
|
+
(
|
|
103
|
+
select
|
|
104
|
+
oid,
|
|
105
|
+
(string_to_array(unnest(proconfig), '='))[1] as param,
|
|
106
|
+
(string_to_array(unnest(proconfig), '='))[2] as value
|
|
107
|
+
from
|
|
108
|
+
functions
|
|
109
|
+
) as t
|
|
110
|
+
group by
|
|
111
|
+
oid
|
|
112
|
+
) f_config on f_config.oid = f.oid
|
|
113
|
+
left join (
|
|
114
|
+
select
|
|
115
|
+
oid,
|
|
116
|
+
jsonb_agg(jsonb_build_object(
|
|
117
|
+
'mode', t2.mode,
|
|
118
|
+
'name', name,
|
|
119
|
+
'type_id', type_id,
|
|
120
|
+
'has_default', has_default
|
|
121
|
+
)) as args
|
|
122
|
+
from
|
|
123
|
+
(
|
|
124
|
+
select
|
|
125
|
+
oid,
|
|
126
|
+
unnest(arg_modes) as mode,
|
|
127
|
+
unnest(arg_names) as name,
|
|
128
|
+
unnest(arg_types)::int8 as type_id,
|
|
129
|
+
unnest(arg_has_defaults) as has_default
|
|
130
|
+
from
|
|
131
|
+
functions
|
|
132
|
+
) as t1,
|
|
133
|
+
lateral (
|
|
134
|
+
select
|
|
135
|
+
case
|
|
136
|
+
when t1.mode = 'i' then 'in'
|
|
137
|
+
when t1.mode = 'o' then 'out'
|
|
138
|
+
when t1.mode = 'b' then 'inout'
|
|
139
|
+
when t1.mode = 'v' then 'variadic'
|
|
140
|
+
else 'table'
|
|
141
|
+
end as mode
|
|
142
|
+
) as t2
|
|
143
|
+
group by
|
|
144
|
+
t1.oid
|
|
145
|
+
) f_args on f_args.oid = f.oid
|
|
146
|
+
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
147
|
+
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
148
|
+
`;
|