@supabase/postgrest-typegen 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/collation.d.ts +2 -0
- package/dist/collation.js +15 -0
- package/dist/generation/index.d.ts +1 -1
- package/dist/generation/swift.js +9 -2
- package/dist/generation/typescript.d.ts +18 -7
- package/dist/generation/typescript.js +102 -79
- package/dist/introspection/index.js +6 -23
- package/dist/introspection/relationships.d.ts +4 -4
- package/dist/introspection/relationships.js +4 -11
- package/dist/introspection/sql/columns.sql.d.ts +2 -7
- package/dist/introspection/sql/columns.sql.js +23 -17
- package/dist/introspection/sql/common.d.ts +7 -15
- package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
- package/dist/introspection/sql/foreign_tables.sql.js +0 -5
- package/dist/introspection/sql/functions.sql.d.ts +2 -5
- package/dist/introspection/sql/functions.sql.js +0 -33
- package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
- package/dist/introspection/sql/materialized_views.sql.js +0 -5
- package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
- package/dist/introspection/sql/primary_keys.sql.js +0 -4
- package/dist/introspection/sql/schemas.sql.d.ts +2 -6
- package/dist/introspection/sql/schemas.sql.js +2 -6
- package/dist/introspection/sql/table.sql.d.ts +2 -4
- package/dist/introspection/sql/table.sql.js +0 -5
- package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
- package/dist/introspection/sql/types.sql.d.ts +5 -5
- package/dist/introspection/sql/types.sql.js +6 -17
- package/dist/introspection/sql/views.sql.d.ts +2 -4
- package/dist/introspection/sql/views.sql.js +0 -5
- package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
- package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
- package/dist/sort.js +16 -15
- package/dist/types.d.ts +4 -4
- package/dist/types.js +6 -4
- package/package.json +3 -3
- package/src/collation.ts +16 -0
- package/src/generation/index.ts +1 -0
- package/src/generation/swift.ts +9 -2
- package/src/generation/typescript.ts +161 -104
- package/src/introspection/index.ts +7 -32
- package/src/introspection/relationships.ts +10 -19
- package/src/introspection/sql/columns.sql.ts +25 -26
- package/src/introspection/sql/common.ts +7 -17
- package/src/introspection/sql/foreign_tables.sql.ts +2 -13
- package/src/introspection/sql/functions.sql.ts +2 -44
- package/src/introspection/sql/materialized_views.sql.ts +2 -11
- package/src/introspection/sql/primary_keys.sql.ts +2 -10
- package/src/introspection/sql/schemas.sql.ts +4 -14
- package/src/introspection/sql/table.sql.ts +2 -11
- package/src/introspection/sql/table_relationships.sql.ts +2 -4
- package/src/introspection/sql/types.sql.ts +6 -26
- package/src/introspection/sql/views.sql.ts +2 -11
- package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
- package/src/sort.ts +20 -15
- package/src/types.ts +6 -4
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const COLUMNS_SQL = (
|
|
5
|
-
props: SQLQueryPropsWithSchemaFilter & {
|
|
6
|
-
tableIdFilter?: string;
|
|
7
|
-
tableIdentifierFilter?: string;
|
|
8
|
-
columnNameFilter?: string;
|
|
9
|
-
idsFilter?: string;
|
|
10
|
-
},
|
|
11
|
-
) => /* SQL */ `
|
|
3
|
+
export const COLUMNS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
12
4
|
-- Adapted from information_schema.columns
|
|
13
5
|
|
|
14
6
|
SELECT
|
|
@@ -45,30 +37,43 @@ SELECT
|
|
|
45
37
|
WHEN 'd' THEN 'BY DEFAULT'
|
|
46
38
|
ELSE NULL
|
|
47
39
|
END AS identity_generation,
|
|
48
|
-
|
|
40
|
+
-- 's' = stored, 'v' = virtual (PostgreSQL 18+); neither accepts writes.
|
|
41
|
+
a.attgenerated IN ('s', 'v') AS is_generated,
|
|
49
42
|
NOT (
|
|
50
43
|
a.attnotnull
|
|
51
44
|
OR t.typtype = 'd' AND t.typnotnull
|
|
52
45
|
) AS is_nullable,
|
|
53
46
|
(
|
|
54
47
|
c.relkind IN ('r', 'p')
|
|
55
|
-
-- Columns of views made writable by INSTEAD OF triggers
|
|
56
|
-
-- through PostgREST even though the view is
|
|
57
|
-
-- must not degrade to \`?: never\` in generated
|
|
58
|
-
--
|
|
59
|
-
--
|
|
60
|
-
--
|
|
61
|
-
--
|
|
48
|
+
-- Columns of views made writable by INSTEAD OF triggers or unconditional
|
|
49
|
+
-- INSTEAD rules can be written through PostgREST even though the view is
|
|
50
|
+
-- not auto-updatable, so they must not degrade to \`?: never\` in generated
|
|
51
|
+
-- Insert/Update types. pg_column_is_updatable cannot express this: it
|
|
52
|
+
-- requires the relation to support both UPDATE and DELETE, so a view with
|
|
53
|
+
-- only an INSTEAD OF INSERT or only an INSTEAD OF UPDATE trigger reports
|
|
54
|
+
-- every column as non-updatable regardless of include_triggers. Triggers
|
|
55
|
+
-- and rules rewrite whole rows, so their presence makes every column
|
|
56
|
+
-- writable (tgtype bits: 64 = INSTEAD, 4 = INSERT, 16 = UPDATE;
|
|
57
|
+
-- pg_rewrite ev_type: '2' = UPDATE, '3' = INSERT; ev_qual is '<>' for
|
|
58
|
+
-- unconditional rules, and only unconditional INSTEAD rules make a view
|
|
59
|
+
-- writable). Which events a view accepts is gated per view via
|
|
62
60
|
-- is_insert_enabled / is_update_enabled.
|
|
63
61
|
OR c.relkind IN ('v', 'f') AND (
|
|
64
|
-
pg_column_is_updatable(c.oid, a.attnum,
|
|
62
|
+
pg_column_is_updatable(c.oid, a.attnum, FALSE)
|
|
65
63
|
OR EXISTS (
|
|
66
64
|
SELECT 1 FROM pg_trigger tg
|
|
67
65
|
WHERE tg.tgrelid = c.oid
|
|
68
66
|
AND tg.tgtype & 64 <> 0
|
|
69
|
-
AND tg.tgtype &
|
|
67
|
+
AND tg.tgtype & 20 <> 0
|
|
70
68
|
AND NOT tg.tgisinternal
|
|
71
69
|
)
|
|
70
|
+
OR EXISTS (
|
|
71
|
+
SELECT 1 FROM pg_rewrite rw
|
|
72
|
+
WHERE rw.ev_class = c.oid
|
|
73
|
+
AND rw.is_instead
|
|
74
|
+
AND rw.ev_type IN ('2', '3')
|
|
75
|
+
AND rw.ev_qual :: text = '<>'
|
|
76
|
+
)
|
|
72
77
|
)
|
|
73
78
|
) AS is_updatable,
|
|
74
79
|
uniques.table_id IS NOT NULL AS is_unique,
|
|
@@ -127,10 +132,6 @@ FROM
|
|
|
127
132
|
) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum
|
|
128
133
|
WHERE
|
|
129
134
|
${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ""}
|
|
130
|
-
${props.idsFilter ? `(c.oid || '.' || a.attnum) ${props.idsFilter} AND` : ""}
|
|
131
|
-
${props.columnNameFilter ? `(c.relname || '.' || a.attname) ${props.columnNameFilter} AND` : ""}
|
|
132
|
-
${props.tableIdFilter ? `c.oid ${props.tableIdFilter} AND` : ""}
|
|
133
|
-
${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
|
|
134
135
|
NOT pg_is_other_temp_schema(nc.oid)
|
|
135
136
|
AND a.attnum > 0
|
|
136
137
|
AND NOT a.attisdropped
|
|
@@ -143,6 +144,4 @@ WHERE
|
|
|
143
144
|
'SELECT, INSERT, UPDATE, REFERENCES'
|
|
144
145
|
)
|
|
145
146
|
)
|
|
146
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
147
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
148
147
|
`;
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
offset?: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & {
|
|
12
|
-
schemaFilter?: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & {
|
|
16
|
-
schemaFilter?: string;
|
|
17
|
-
idsFilter?: string;
|
|
1
|
+
/** The only option the introspection passes to its SQL builders. */
|
|
2
|
+
export type SchemaFilterProps = {
|
|
3
|
+
/**
|
|
4
|
+
* An `IN (…)` or `NOT IN (…)` fragment from `filterByList`, or the empty
|
|
5
|
+
* string for no filter.
|
|
6
|
+
*/
|
|
7
|
+
schemaFilter: string;
|
|
18
8
|
};
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryProps } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const FOREIGN_TABLES_SQL = (
|
|
5
|
-
props: SQLQueryProps & {
|
|
6
|
-
schemaFilter?: string;
|
|
7
|
-
idsFilter?: string;
|
|
8
|
-
tableIdentifierFilter?: string;
|
|
9
|
-
},
|
|
10
|
-
) => /* SQL */ `
|
|
3
|
+
export const FOREIGN_TABLES_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
11
4
|
SELECT
|
|
12
5
|
c.oid :: int8 AS id,
|
|
13
6
|
n.nspname AS schema,
|
|
@@ -18,9 +11,5 @@ FROM
|
|
|
18
11
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
19
12
|
WHERE
|
|
20
13
|
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
21
|
-
${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
|
|
22
|
-
${props.tableIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.tableIdentifierFilter} AND` : ""}
|
|
23
14
|
c.relkind = 'f'
|
|
24
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
25
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
26
15
|
`;
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
|
|
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.ts";
|
|
5
|
-
import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
6
2
|
|
|
7
|
-
export const FUNCTIONS_SQL = (
|
|
8
|
-
props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
|
|
9
|
-
nameFilter?: string;
|
|
10
|
-
args?: string[];
|
|
11
|
-
},
|
|
12
|
-
) => /* SQL */ `
|
|
3
|
+
export const FUNCTIONS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
13
4
|
-- CTE with sane arg_modes, arg_names, and arg_types.
|
|
14
5
|
-- All three are always of the same length.
|
|
15
6
|
-- All three include all args, including OUT and TABLE args.
|
|
@@ -36,37 +27,6 @@ with functions as (
|
|
|
36
27
|
${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ""}
|
|
37
28
|
where
|
|
38
29
|
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
39
|
-
${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ""}
|
|
40
|
-
${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ""}
|
|
41
|
-
${
|
|
42
|
-
props.args === undefined
|
|
43
|
-
? ""
|
|
44
|
-
: props.args.length > 0
|
|
45
|
-
? `p.proargtypes::text = ${
|
|
46
|
-
props.args.length
|
|
47
|
-
? `(
|
|
48
|
-
SELECT STRING_AGG(type_oid::text, ' ') FROM (
|
|
49
|
-
SELECT (
|
|
50
|
-
split_args.arr[
|
|
51
|
-
array_length(
|
|
52
|
-
split_args.arr,
|
|
53
|
-
1
|
|
54
|
-
)
|
|
55
|
-
]::regtype::oid
|
|
56
|
-
) AS type_oid FROM (
|
|
57
|
-
SELECT STRING_TO_ARRAY(
|
|
58
|
-
UNNEST(
|
|
59
|
-
ARRAY[${props.args}]
|
|
60
|
-
),
|
|
61
|
-
' '
|
|
62
|
-
) AS arr
|
|
63
|
-
) AS split_args
|
|
64
|
-
) args
|
|
65
|
-
)`
|
|
66
|
-
: "''"
|
|
67
|
-
} AND`
|
|
68
|
-
: ""
|
|
69
|
-
}
|
|
70
30
|
p.prokind = 'f'
|
|
71
31
|
)
|
|
72
32
|
select
|
|
@@ -154,6 +114,4 @@ from
|
|
|
154
114
|
group by
|
|
155
115
|
t1.oid
|
|
156
116
|
) f_args on f_args.oid = f.oid
|
|
157
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
158
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
159
117
|
`;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const MATERIALIZED_VIEWS_SQL = (
|
|
5
|
-
props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
|
|
6
|
-
materializedViewIdentifierFilter?: string;
|
|
7
|
-
},
|
|
8
|
-
) => /* SQL */ `
|
|
3
|
+
export const MATERIALIZED_VIEWS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
9
4
|
select
|
|
10
5
|
c.oid::int8 as id,
|
|
11
6
|
n.nspname as schema,
|
|
@@ -17,9 +12,5 @@ from
|
|
|
17
12
|
join pg_namespace n on n.oid = c.relnamespace
|
|
18
13
|
where
|
|
19
14
|
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
20
|
-
${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
|
|
21
|
-
${props.materializedViewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.materializedViewIdentifierFilter} AND` : ""}
|
|
22
15
|
c.relkind = 'm'
|
|
23
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
24
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
25
16
|
`;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const PRIMARY_KEYS_SQL = (
|
|
5
|
-
props: SQLQueryPropsWithSchemaFilter & {
|
|
6
|
-
tableIdentifierFilter?: string;
|
|
7
|
-
},
|
|
8
|
-
) => /* SQL */ `
|
|
3
|
+
export const PRIMARY_KEYS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
9
4
|
SELECT
|
|
10
5
|
c.oid :: int8 AS table_id,
|
|
11
6
|
n.nspname AS schema,
|
|
@@ -18,7 +13,6 @@ FROM
|
|
|
18
13
|
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
|
|
19
14
|
WHERE
|
|
20
15
|
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
21
|
-
${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
|
|
22
16
|
i.indisprimary
|
|
23
17
|
AND c.relkind IN ('r', 'p')
|
|
24
18
|
AND NOT pg_is_other_temp_schema(n.oid)
|
|
@@ -33,6 +27,4 @@ WHERE
|
|
|
33
27
|
ORDER BY
|
|
34
28
|
c.oid,
|
|
35
29
|
array_position(i.indkey, a.attnum)
|
|
36
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
37
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
38
30
|
`;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryProps } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const SCHEMAS_SQL = (
|
|
5
|
-
props: SQLQueryProps & {
|
|
6
|
-
nameFilter?: string;
|
|
7
|
-
idsFilter?: string;
|
|
8
|
-
includeSystemSchemas?: boolean;
|
|
9
|
-
},
|
|
10
|
-
) => /* SQL */ `
|
|
3
|
+
export const SCHEMAS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
11
4
|
-- Adapted from information_schema.schemata
|
|
12
5
|
select
|
|
13
6
|
n.oid::int8 as id,
|
|
@@ -18,15 +11,12 @@ from
|
|
|
18
11
|
pg_roles u
|
|
19
12
|
where
|
|
20
13
|
n.nspowner = u.oid
|
|
21
|
-
${props.
|
|
22
|
-
|
|
23
|
-
${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ""}
|
|
14
|
+
${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
|
|
15
|
+
and not pg_catalog.starts_with(n.nspname, 'pg_')
|
|
24
16
|
and (
|
|
25
17
|
pg_has_role(n.nspowner, 'USAGE')
|
|
26
18
|
or has_schema_privilege(n.oid, 'CREATE, USAGE')
|
|
27
19
|
)
|
|
28
20
|
and not pg_catalog.starts_with(n.nspname, 'pg_temp_')
|
|
29
21
|
and not pg_catalog.starts_with(n.nspname, 'pg_toast_temp_')
|
|
30
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
31
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
32
22
|
`;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const TABLES_SQL = (
|
|
5
|
-
props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
|
|
6
|
-
tableIdentifierFilter?: string;
|
|
7
|
-
},
|
|
8
|
-
) => /* SQL */ `
|
|
3
|
+
export const TABLES_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
9
4
|
SELECT
|
|
10
5
|
c.oid :: int8 AS id,
|
|
11
6
|
nc.nspname AS schema,
|
|
@@ -30,8 +25,6 @@ FROM
|
|
|
30
25
|
JOIN pg_class c ON nc.oid = c.relnamespace
|
|
31
26
|
WHERE
|
|
32
27
|
${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ""}
|
|
33
|
-
${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
|
|
34
|
-
${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
|
|
35
28
|
c.relkind IN ('r', 'p')
|
|
36
29
|
AND NOT pg_is_other_temp_schema(nc.oid)
|
|
37
30
|
AND (
|
|
@@ -42,6 +35,4 @@ WHERE
|
|
|
42
35
|
)
|
|
43
36
|
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
|
44
37
|
)
|
|
45
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
46
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
47
38
|
`;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
2
2
|
|
|
3
|
-
export const TABLE_RELATIONSHIPS_SQL = (
|
|
4
|
-
props: SQLQueryPropsWithSchemaFilter,
|
|
5
|
-
) => /* SQL */ `
|
|
3
|
+
export const TABLE_RELATIONSHIPS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
6
4
|
-- Adapted from
|
|
7
5
|
-- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722
|
|
8
6
|
WITH
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
includeTableTypes?: boolean;
|
|
7
|
-
includeArrayTypes?: boolean;
|
|
8
|
-
},
|
|
9
|
-
) => /* SQL */ `
|
|
1
|
+
/**
|
|
2
|
+
* Every type of every schema, including the row types of tables and views
|
|
3
|
+
* and the array types, since a column of any schema may use them.
|
|
4
|
+
*/
|
|
5
|
+
export const TYPES_SQL = /* SQL */ `
|
|
10
6
|
select
|
|
11
7
|
t.oid::int8 as id,
|
|
12
8
|
t.typname as name,
|
|
@@ -48,27 +44,11 @@ from
|
|
|
48
44
|
t.typrelid = 0
|
|
49
45
|
or (
|
|
50
46
|
select
|
|
51
|
-
c.relkind
|
|
47
|
+
c.relkind in ('c', 'r', 'v', 'm', 'p', 'f')
|
|
52
48
|
from
|
|
53
49
|
pg_class c
|
|
54
50
|
where
|
|
55
51
|
c.oid = t.typrelid
|
|
56
52
|
)
|
|
57
53
|
)
|
|
58
|
-
${
|
|
59
|
-
!props.includeArrayTypes
|
|
60
|
-
? `and not exists (
|
|
61
|
-
select
|
|
62
|
-
from
|
|
63
|
-
pg_type el
|
|
64
|
-
where
|
|
65
|
-
el.oid = t.typelem
|
|
66
|
-
and el.typarray = t.oid
|
|
67
|
-
)`
|
|
68
|
-
: ""
|
|
69
|
-
}
|
|
70
|
-
${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
|
|
71
|
-
${props.idsFilter ? `and t.oid ${props.idsFilter}` : ""}
|
|
72
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
73
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
74
54
|
`;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
3
2
|
|
|
4
|
-
export const VIEWS_SQL = (
|
|
5
|
-
props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
|
|
6
|
-
viewIdentifierFilter?: string;
|
|
7
|
-
},
|
|
8
|
-
) => /* SQL */ `
|
|
3
|
+
export const VIEWS_SQL = (props: SchemaFilterProps) => /* SQL */ `
|
|
9
4
|
SELECT
|
|
10
5
|
c.oid :: int8 AS id,
|
|
11
6
|
n.nspname AS schema,
|
|
@@ -24,9 +19,5 @@ FROM
|
|
|
24
19
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
25
20
|
WHERE
|
|
26
21
|
${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
|
|
27
|
-
${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
|
|
28
|
-
${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ""}
|
|
29
22
|
c.relkind = 'v'
|
|
30
|
-
${props.limit ? `limit ${literal(props.limit)}` : ""}
|
|
31
|
-
${props.offset ? `offset ${literal(props.offset)}` : ""}
|
|
32
23
|
`;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SchemaFilterProps } from "./common.ts";
|
|
2
|
+
import { literal } from "./pg-format.ts";
|
|
2
3
|
|
|
3
4
|
export const VIEWS_KEY_DEPENDENCIES_SQL = (
|
|
4
|
-
props:
|
|
5
|
+
props: SchemaFilterProps,
|
|
5
6
|
) => /* SQL */ `
|
|
6
7
|
-- Adapted from
|
|
7
8
|
-- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820
|
|
@@ -47,81 +48,7 @@ views as (
|
|
|
47
48
|
transform_json as (
|
|
48
49
|
select
|
|
49
50
|
view_id, view_schema, view_name,
|
|
50
|
-
|
|
51
|
-
-- to allow simple diffs, with less whitespace noise
|
|
52
|
-
replace(
|
|
53
|
-
replace(
|
|
54
|
-
replace(
|
|
55
|
-
replace(
|
|
56
|
-
replace(
|
|
57
|
-
replace(
|
|
58
|
-
replace(
|
|
59
|
-
regexp_replace(
|
|
60
|
-
replace(
|
|
61
|
-
replace(
|
|
62
|
-
replace(
|
|
63
|
-
replace(
|
|
64
|
-
replace(
|
|
65
|
-
replace(
|
|
66
|
-
replace(
|
|
67
|
-
replace(
|
|
68
|
-
replace(
|
|
69
|
-
replace(
|
|
70
|
-
replace(
|
|
71
|
-
view_definition::text,
|
|
72
|
-
-- This conversion to json is heavily optimized for performance.
|
|
73
|
-
-- The general idea is to use as few regexp_replace() calls as possible.
|
|
74
|
-
-- Simple replace() is a lot faster, so we jump through some hoops
|
|
75
|
-
-- to be able to use regexp_replace() only once.
|
|
76
|
-
-- This has been tested against a huge schema with 250+ different views.
|
|
77
|
-
-- The unit tests do NOT reflect all possible inputs. Be careful when changing this!
|
|
78
|
-
-- -----------------------------------------------
|
|
79
|
-
-- pattern | replacement | flags
|
|
80
|
-
-- -----------------------------------------------
|
|
81
|
-
-- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof
|
|
82
|
-
-- we need to make this an empty array here to prevent json_array_elements from throwing an error
|
|
83
|
-
-- when the targetList is null.
|
|
84
|
-
-- We'll need to put it first, to make the node protection below work for node lists that start with
|
|
85
|
-
-- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value.
|
|
86
|
-
'<>' , '()'
|
|
87
|
-
-- , is not part of the pg_node_tree format, but used in the regex.
|
|
88
|
-
-- This removes all , that might be part of column names.
|
|
89
|
-
), ',' , ''
|
|
90
|
-
-- The same applies for { and }, although those are used a lot in pg_node_tree.
|
|
91
|
-
-- We remove the escaped ones, which might be part of column names again.
|
|
92
|
-
), E'\\\\{' , ''
|
|
93
|
-
), E'\\\\}' , ''
|
|
94
|
-
-- The fields we need are formatted as json manually to protect them from the regex.
|
|
95
|
-
), ' :targetList ' , ',"targetList":'
|
|
96
|
-
), ' :resno ' , ',"resno":'
|
|
97
|
-
), ' :resorigtbl ' , ',"resorigtbl":'
|
|
98
|
-
), ' :resorigcol ' , ',"resorigcol":'
|
|
99
|
-
-- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass.
|
|
100
|
-
), '{' , '{ :'
|
|
101
|
-
-- Protect node lists, which start with \`({\` or \`((\` from the greedy regex.
|
|
102
|
-
-- The extra \`{\` is removed again later.
|
|
103
|
-
), '((' , '{(('
|
|
104
|
-
), '({' , '{({'
|
|
105
|
-
-- This regex removes all unused fields to avoid the need to format all of them correctly.
|
|
106
|
-
-- This leads to a smaller json result as well.
|
|
107
|
-
-- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node.
|
|
108
|
-
-- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and
|
|
109
|
-
-- add an empty key for the followig node.
|
|
110
|
-
), ' :[^}{,]+' , ',"":' , 'g'
|
|
111
|
-
-- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`.
|
|
112
|
-
-- Those are removed next.
|
|
113
|
-
), ',"":}' , '}'
|
|
114
|
-
), ',"":,' , ','
|
|
115
|
-
-- This reverses the "node list protection" from above.
|
|
116
|
-
), '{(' , '('
|
|
117
|
-
-- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it.
|
|
118
|
-
), '{,' , '{'
|
|
119
|
-
-- pg_node_tree has \`()\` around lists, but JSON uses \`[]\`
|
|
120
|
-
), '(' , '['
|
|
121
|
-
), ')' , ']'
|
|
122
|
-
-- pg_node_tree has \` \` between list items, but JSON uses \`,\`
|
|
123
|
-
), ' ' , ','
|
|
124
|
-
)::json as view_definition
|
|
51
|
+
${nodeTreeToJson("view_definition::text")}::json as view_definition
|
|
125
52
|
from views
|
|
126
53
|
),
|
|
127
54
|
target_entries as(
|
|
@@ -197,3 +124,82 @@ group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conn
|
|
|
197
124
|
-- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs
|
|
198
125
|
having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
|
|
199
126
|
`;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* One text rewrite of the `pg_node_tree` to JSON conversion: a `replace`, or
|
|
130
|
+
* a global `regexp_replace` when `isRegex` is set.
|
|
131
|
+
*/
|
|
132
|
+
type NodeTreeRewrite = {
|
|
133
|
+
pattern: string;
|
|
134
|
+
replacement: string;
|
|
135
|
+
isRegex?: boolean;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The rewrites, innermost first, that turn the `pg_node_tree` text of a view
|
|
140
|
+
* definition into JSON exposing only `targetList`, `resno`, `resorigtbl` and
|
|
141
|
+
* `resorigcol`. Ported from PostgREST's schema cache query, which keeps them
|
|
142
|
+
* as plain `replace` calls with a single `regexp_replace` for speed; the
|
|
143
|
+
* order matters, and the comments carry PostgREST's reasoning.
|
|
144
|
+
*/
|
|
145
|
+
const NODE_TREE_TO_JSON_REWRITES: readonly NodeTreeRewrite[] = [
|
|
146
|
+
// `<>` is pg_node_tree's null. json_typeof is too slow to special-case it,
|
|
147
|
+
// so it becomes an empty list, which also keeps the node protection below
|
|
148
|
+
// working for lists that start with null, such as coldefexprs whose first
|
|
149
|
+
// column has no default.
|
|
150
|
+
{ pattern: "<>", replacement: "()" },
|
|
151
|
+
// `,`, `{` and `}` are used by the JSON conversion; drop the ones that may
|
|
152
|
+
// be part of column names (`{` and `}` are escaped there).
|
|
153
|
+
{ pattern: ",", replacement: "" },
|
|
154
|
+
{ pattern: "\\{", replacement: "" },
|
|
155
|
+
{ pattern: "\\}", replacement: "" },
|
|
156
|
+
// Format the fields we need as JSON keys so the regex below leaves them.
|
|
157
|
+
{ pattern: " :targetList ", replacement: ',"targetList":' },
|
|
158
|
+
{ pattern: " :resno ", replacement: ',"resno":' },
|
|
159
|
+
{ pattern: " :resorigtbl ", replacement: ',"resorigtbl":' },
|
|
160
|
+
{ pattern: " :resorigcol ", replacement: ',"resorigcol":' },
|
|
161
|
+
// Make the regex also match the node type, e.g. `{QUERY ...`.
|
|
162
|
+
{ pattern: "{", replacement: "{ :" },
|
|
163
|
+
// Protect node lists, which start with `({` or `((`, from the greedy regex.
|
|
164
|
+
// The extra `{` is removed again below.
|
|
165
|
+
{ pattern: "((", replacement: "{((" },
|
|
166
|
+
{ pattern: "({", replacement: "{({" },
|
|
167
|
+
// Remove every unused field. Removal stops at `,` for the kept fields, at
|
|
168
|
+
// `}` for the end of the node and at `{` for a nested node, leaving an empty
|
|
169
|
+
// key for the following node.
|
|
170
|
+
{ pattern: " :[^}{,]+", replacement: ',"":', isRegex: true },
|
|
171
|
+
// The regex also added those empty keys before `}` and `,`.
|
|
172
|
+
{ pattern: ',"":}', replacement: "}" },
|
|
173
|
+
{ pattern: ',"":,', replacement: "," },
|
|
174
|
+
// Undo the node list protection.
|
|
175
|
+
{ pattern: "{(", replacement: "(" },
|
|
176
|
+
// Every key was added with a leading `,`; the first key of an object does
|
|
177
|
+
// not need it.
|
|
178
|
+
{ pattern: "{,", replacement: "{" },
|
|
179
|
+
// pg_node_tree lists use `( )` with space separated items; JSON uses `[ ]`
|
|
180
|
+
// with commas.
|
|
181
|
+
{ pattern: "(", replacement: "[" },
|
|
182
|
+
{ pattern: ")", replacement: "]" },
|
|
183
|
+
{ pattern: " ", replacement: "," },
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Render {@link NODE_TREE_TO_JSON_REWRITES} applied to `expression` as nested
|
|
188
|
+
* SQL calls: the opening calls stacked, then each rewrite's arguments on the
|
|
189
|
+
* line that closes it.
|
|
190
|
+
*/
|
|
191
|
+
function nodeTreeToJson(expression: string): string {
|
|
192
|
+
const lines = NODE_TREE_TO_JSON_REWRITES.toReversed().map(
|
|
193
|
+
(rewrite) => ` ${rewrite.isRegex ? "regexp_replace" : "replace"}(`,
|
|
194
|
+
);
|
|
195
|
+
lines.push(` ${expression},`);
|
|
196
|
+
NODE_TREE_TO_JSON_REWRITES.forEach((rewrite, index) => {
|
|
197
|
+
const flags = rewrite.isRegex ? ", 'g'" : "";
|
|
198
|
+
const closing =
|
|
199
|
+
index === NODE_TREE_TO_JSON_REWRITES.length - 1 ? ")" : "),";
|
|
200
|
+
lines.push(
|
|
201
|
+
` ${literal(rewrite.pattern)}, ${literal(rewrite.replacement)}${flags}${closing}`,
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
return lines.join("\n");
|
|
205
|
+
}
|
package/src/sort.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* addressed by name (the generated type is an object), matching TypeScript.
|
|
27
27
|
*/
|
|
28
28
|
import type { GeneratorMetadata, PostgresRelationship } from "./types.ts";
|
|
29
|
+
import { compareStrings } from "./collation.ts";
|
|
29
30
|
|
|
30
31
|
// Relations (tables/views/materialized views/foreign tables) and types share a
|
|
31
32
|
// (schema, name) identity within a database; `id` breaks any residual tie.
|
|
@@ -33,8 +34,8 @@ const bySchemaName = (
|
|
|
33
34
|
a: { schema: string; name: string; id: number },
|
|
34
35
|
b: { schema: string; name: string; id: number },
|
|
35
36
|
): number =>
|
|
36
|
-
a.schema
|
|
37
|
-
a.name
|
|
37
|
+
compareStrings(a.schema, b.schema) ||
|
|
38
|
+
compareStrings(a.name, b.name) ||
|
|
38
39
|
a.id - b.id;
|
|
39
40
|
|
|
40
41
|
// Mirrors the TypeScript generator's historical `relationships.sort`.
|
|
@@ -42,9 +43,10 @@ const byRelationship = (
|
|
|
42
43
|
a: PostgresRelationship,
|
|
43
44
|
b: PostgresRelationship,
|
|
44
45
|
): number =>
|
|
45
|
-
a.foreign_key_name
|
|
46
|
-
a.referenced_relation
|
|
47
|
-
|
|
46
|
+
compareStrings(a.foreign_key_name, b.foreign_key_name) ||
|
|
47
|
+
compareStrings(a.referenced_relation, b.referenced_relation) ||
|
|
48
|
+
compareStrings(
|
|
49
|
+
JSON.stringify(a.referenced_columns),
|
|
48
50
|
JSON.stringify(b.referenced_columns),
|
|
49
51
|
);
|
|
50
52
|
|
|
@@ -60,7 +62,7 @@ export function sortGeneratorMetadata(
|
|
|
60
62
|
return {
|
|
61
63
|
version: metadata.version,
|
|
62
64
|
schemas: [...metadata.schemas].sort(
|
|
63
|
-
(a, b) => a.name
|
|
65
|
+
(a, b) => compareStrings(a.name, b.name) || a.id - b.id,
|
|
64
66
|
),
|
|
65
67
|
tables: [...metadata.tables].sort(bySchemaName),
|
|
66
68
|
foreignTables: [...metadata.foreignTables].sort(bySchemaName),
|
|
@@ -70,17 +72,17 @@ export function sortGeneratorMetadata(
|
|
|
70
72
|
// (matches the TypeScript generator's per-table `columns.sort(by name)`).
|
|
71
73
|
columns: [...metadata.columns].sort(
|
|
72
74
|
(a, b) =>
|
|
73
|
-
a.schema
|
|
74
|
-
a.table
|
|
75
|
-
a.name
|
|
75
|
+
compareStrings(a.schema, b.schema) ||
|
|
76
|
+
compareStrings(a.table, b.table) ||
|
|
77
|
+
compareStrings(a.name, b.name),
|
|
76
78
|
),
|
|
77
79
|
// Grouped by table like `columns`; within a table the SQL's declared
|
|
78
80
|
// column order (`array_position(indkey, attnum)`) is preserved by the
|
|
79
81
|
// stable sort, since composite key order is meaningful.
|
|
80
82
|
primaryKeys: [...metadata.primaryKeys].sort(
|
|
81
83
|
(a, b) =>
|
|
82
|
-
a.schema
|
|
83
|
-
a.table_name
|
|
84
|
+
compareStrings(a.schema, b.schema) ||
|
|
85
|
+
compareStrings(a.table_name, b.table_name),
|
|
84
86
|
),
|
|
85
87
|
relationships: [...metadata.relationships].sort(byRelationship),
|
|
86
88
|
// Functions can overload, so the signature is part of the identity. Args
|
|
@@ -88,14 +90,17 @@ export function sortGeneratorMetadata(
|
|
|
88
90
|
functions: [...metadata.functions]
|
|
89
91
|
.sort(
|
|
90
92
|
(a, b) =>
|
|
91
|
-
a.schema
|
|
92
|
-
a.name
|
|
93
|
-
|
|
93
|
+
compareStrings(a.schema, b.schema) ||
|
|
94
|
+
compareStrings(a.name, b.name) ||
|
|
95
|
+
compareStrings(
|
|
96
|
+
a.identity_argument_types,
|
|
97
|
+
b.identity_argument_types,
|
|
98
|
+
) ||
|
|
94
99
|
a.id - b.id,
|
|
95
100
|
)
|
|
96
101
|
.map((fn) => ({
|
|
97
102
|
...fn,
|
|
98
|
-
args: [...fn.args].sort((a, b) => a.name
|
|
103
|
+
args: [...fn.args].sort((a, b) => compareStrings(a.name, b.name)),
|
|
99
104
|
})),
|
|
100
105
|
types: [...metadata.types].sort(bySchemaName),
|
|
101
106
|
};
|