@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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/generation/constants.d.ts +9 -0
  4. package/dist/generation/constants.js +10 -0
  5. package/dist/generation/go.d.ts +6 -0
  6. package/dist/generation/go.js +233 -0
  7. package/dist/generation/index.d.ts +5 -0
  8. package/dist/generation/index.js +5 -0
  9. package/dist/generation/python.d.ts +6 -0
  10. package/dist/generation/python.js +339 -0
  11. package/dist/generation/swift.d.ts +11 -0
  12. package/dist/generation/swift.js +309 -0
  13. package/dist/generation/typescript.d.ts +32 -0
  14. package/dist/generation/typescript.js +796 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +4 -0
  17. package/dist/introspection/index.d.ts +27 -0
  18. package/dist/introspection/index.js +70 -0
  19. package/dist/introspection/normalize.d.ts +31 -0
  20. package/dist/introspection/normalize.js +65 -0
  21. package/dist/introspection/relationships.d.ts +47 -0
  22. package/dist/introspection/relationships.js +85 -0
  23. package/dist/introspection/sql/columns.sql.d.ts +7 -0
  24. package/dist/introspection/sql/columns.sql.js +122 -0
  25. package/dist/introspection/sql/common.d.ts +16 -0
  26. package/dist/introspection/sql/common.js +1 -0
  27. package/dist/introspection/sql/foreign_tables.sql.d.ts +6 -0
  28. package/dist/introspection/sql/foreign_tables.sql.js +18 -0
  29. package/dist/introspection/sql/functions.sql.d.ts +5 -0
  30. package/dist/introspection/sql/functions.sql.js +148 -0
  31. package/dist/introspection/sql/helpers.d.ts +12 -0
  32. package/dist/introspection/sql/helpers.js +28 -0
  33. package/dist/introspection/sql/materialized_views.sql.d.ts +4 -0
  34. package/dist/introspection/sql/materialized_views.sql.js +19 -0
  35. package/dist/introspection/sql/primary_keys.sql.d.ts +4 -0
  36. package/dist/introspection/sql/primary_keys.sql.js +32 -0
  37. package/dist/introspection/sql/schemas.sql.d.ts +6 -0
  38. package/dist/introspection/sql/schemas.sql.js +24 -0
  39. package/dist/introspection/sql/table.sql.d.ts +4 -0
  40. package/dist/introspection/sql/table.sql.js +41 -0
  41. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -0
  42. package/dist/introspection/sql/table_relationships.sql.js +49 -0
  43. package/dist/introspection/sql/types.sql.d.ts +5 -0
  44. package/dist/introspection/sql/types.sql.js +65 -0
  45. package/dist/introspection/sql/views.sql.d.ts +4 -0
  46. package/dist/introspection/sql/views.sql.js +20 -0
  47. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -0
  48. package/dist/introspection/sql/views_key_dependencies.sql.js +195 -0
  49. package/dist/sort.d.ts +35 -0
  50. package/dist/sort.js +48 -0
  51. package/dist/types.d.ts +372 -0
  52. package/dist/types.js +207 -0
  53. package/package.json +87 -0
  54. package/src/generation/constants.ts +11 -0
  55. package/src/generation/go.ts +356 -0
  56. package/src/generation/index.ts +13 -0
  57. package/src/generation/python.ts +461 -0
  58. package/src/generation/swift.ts +481 -0
  59. package/src/generation/typescript.ts +1144 -0
  60. package/src/index.ts +4 -0
  61. package/src/introspection/index.ts +124 -0
  62. package/src/introspection/normalize.ts +71 -0
  63. package/src/introspection/relationships.ts +173 -0
  64. package/src/introspection/sql/columns.sql.ts +131 -0
  65. package/src/introspection/sql/common.ts +18 -0
  66. package/src/introspection/sql/foreign_tables.sql.ts +26 -0
  67. package/src/introspection/sql/functions.sql.ts +159 -0
  68. package/src/introspection/sql/helpers.ts +33 -0
  69. package/src/introspection/sql/materialized_views.sql.ts +25 -0
  70. package/src/introspection/sql/primary_keys.sql.ts +38 -0
  71. package/src/introspection/sql/schemas.sql.ts +32 -0
  72. package/src/introspection/sql/table.sql.ts +47 -0
  73. package/src/introspection/sql/table_relationships.sql.ts +53 -0
  74. package/src/introspection/sql/types.sql.ts +74 -0
  75. package/src/introspection/sql/views.sql.ts +26 -0
  76. package/src/introspection/sql/views_key_dependencies.sql.ts +199 -0
  77. package/src/sort.ts +102 -0
  78. package/src/types.ts +237 -0
@@ -0,0 +1,159 @@
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
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
6
+
7
+ export const FUNCTIONS_SQL = (
8
+ props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
9
+ nameFilter?: string;
10
+ args?: string[];
11
+ },
12
+ ) => /* SQL */ `
13
+ -- CTE with sane arg_modes, arg_names, and arg_types.
14
+ -- All three are always of the same length.
15
+ -- All three include all args, including OUT and TABLE args.
16
+ with functions as (
17
+ select
18
+ p.*,
19
+ -- proargmodes is null when all arg modes are IN
20
+ coalesce(
21
+ p.proargmodes,
22
+ array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
23
+ ) as arg_modes,
24
+ -- proargnames is null when all args are unnamed
25
+ coalesce(
26
+ p.proargnames,
27
+ array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
28
+ ) as arg_names,
29
+ -- proallargtypes is null when all arg modes are IN
30
+ coalesce(p.proallargtypes, p.proargtypes) as arg_types,
31
+ array_cat(
32
+ array_fill(false, array[pronargs - pronargdefaults]),
33
+ array_fill(true, array[pronargdefaults])) as arg_has_defaults
34
+ from
35
+ pg_proc as p
36
+ ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ""}
37
+ where
38
+ ${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
+ p.prokind = 'f'
71
+ )
72
+ select
73
+ f.oid::int8 as id,
74
+ n.nspname as schema,
75
+ f.proname as name,
76
+ l.lanname as language,
77
+ case
78
+ when l.lanname = 'internal' then ''
79
+ else f.prosrc
80
+ end as definition,
81
+ case
82
+ when l.lanname = 'internal' then f.prosrc
83
+ else pg_get_functiondef(f.oid)
84
+ end as complete_statement,
85
+ coalesce(f_args.args, '[]') as args,
86
+ pg_get_function_arguments(f.oid) as argument_types,
87
+ pg_get_function_identity_arguments(f.oid) as identity_argument_types,
88
+ f.prorettype::int8 as return_type_id,
89
+ pg_get_function_result(f.oid) as return_type,
90
+ nullif(rt.typrelid::int8, 0) as return_type_relation_id,
91
+ f.proretset as is_set_returning_function,
92
+ case
93
+ when f.proretset then nullif(f.prorows, 0)
94
+ else null
95
+ end as prorows,
96
+ case
97
+ when f.provolatile = 'i' then 'IMMUTABLE'
98
+ when f.provolatile = 's' then 'STABLE'
99
+ when f.provolatile = 'v' then 'VOLATILE'
100
+ end as behavior,
101
+ f.prosecdef as security_definer,
102
+ f_config.config_params as config_params
103
+ from
104
+ functions f
105
+ left join pg_namespace n on f.pronamespace = n.oid
106
+ left join pg_language l on f.prolang = l.oid
107
+ left join pg_type rt on rt.oid = f.prorettype
108
+ left join (
109
+ select
110
+ oid,
111
+ jsonb_object_agg(param, value) filter (where param is not null) as config_params
112
+ from
113
+ (
114
+ select
115
+ oid,
116
+ (string_to_array(unnest(proconfig), '='))[1] as param,
117
+ (string_to_array(unnest(proconfig), '='))[2] as value
118
+ from
119
+ functions
120
+ ) as t
121
+ group by
122
+ oid
123
+ ) f_config on f_config.oid = f.oid
124
+ left join (
125
+ select
126
+ oid,
127
+ jsonb_agg(jsonb_build_object(
128
+ 'mode', t2.mode,
129
+ 'name', name,
130
+ 'type_id', type_id,
131
+ 'has_default', has_default
132
+ )) as args
133
+ from
134
+ (
135
+ select
136
+ oid,
137
+ unnest(arg_modes) as mode,
138
+ unnest(arg_names) as name,
139
+ unnest(arg_types)::int8 as type_id,
140
+ unnest(arg_has_defaults) as has_default
141
+ from
142
+ functions
143
+ ) as t1,
144
+ lateral (
145
+ select
146
+ case
147
+ when t1.mode = 'i' then 'in'
148
+ when t1.mode = 'o' then 'out'
149
+ when t1.mode = 'b' then 'inout'
150
+ when t1.mode = 'v' then 'variadic'
151
+ else 'table'
152
+ end as mode
153
+ ) as t2
154
+ group by
155
+ t1.oid
156
+ ) f_args on f_args.oid = f.oid
157
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
158
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
159
+ `;
@@ -0,0 +1,33 @@
1
+ import { literal } from "pg-format";
2
+
3
+ /**
4
+ * Schemas that postgres-meta excludes by default when `includeSystemSchemas`
5
+ * is false. Ported from `postgres-meta/src/lib/constants.ts`.
6
+ */
7
+ export const DEFAULT_SYSTEM_SCHEMAS = [
8
+ "information_schema",
9
+ "pg_catalog",
10
+ "pg_toast",
11
+ ];
12
+
13
+ /**
14
+ * Build a SQL `IN (...)` / `NOT IN (...)` fragment for an include/exclude list,
15
+ * ported verbatim from `postgres-meta/src/lib/helpers.ts`. Values are escaped
16
+ * with pg-format's `literal`. Returns an empty string when there is nothing to
17
+ * filter, so callers interpolate it conditionally.
18
+ */
19
+ export const filterByList = (
20
+ include?: (string | number)[],
21
+ exclude?: (string | number)[],
22
+ defaultExclude?: (string | number)[],
23
+ ) => {
24
+ if (defaultExclude) {
25
+ exclude = defaultExclude.concat(exclude ?? []);
26
+ }
27
+ if (include?.length) {
28
+ return `IN (${include.map(literal).join(",")})`;
29
+ } else if (exclude?.length) {
30
+ return `NOT IN (${exclude.map(literal).join(",")})`;
31
+ }
32
+ return "";
33
+ };
@@ -0,0 +1,25 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
3
+
4
+ export const MATERIALIZED_VIEWS_SQL = (
5
+ props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
6
+ materializedViewIdentifierFilter?: string;
7
+ },
8
+ ) => /* SQL */ `
9
+ select
10
+ c.oid::int8 as id,
11
+ n.nspname as schema,
12
+ c.relname as name,
13
+ c.relispopulated as is_populated,
14
+ obj_description(c.oid) as comment
15
+ from
16
+ pg_class c
17
+ join pg_namespace n on n.oid = c.relnamespace
18
+ where
19
+ ${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
+ c.relkind = 'm'
23
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
24
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
25
+ `;
@@ -0,0 +1,38 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
3
+
4
+ export const PRIMARY_KEYS_SQL = (
5
+ props: SQLQueryPropsWithSchemaFilter & {
6
+ tableIdentifierFilter?: string;
7
+ },
8
+ ) => /* SQL */ `
9
+ SELECT
10
+ c.oid :: int8 AS table_id,
11
+ n.nspname AS schema,
12
+ c.relname AS table_name,
13
+ a.attname AS name
14
+ FROM
15
+ pg_index i
16
+ JOIN pg_class c ON i.indrelid = c.oid
17
+ JOIN pg_namespace n ON c.relnamespace = n.oid
18
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
19
+ WHERE
20
+ ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
21
+ ${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
22
+ i.indisprimary
23
+ AND c.relkind IN ('r', 'p')
24
+ AND NOT pg_is_other_temp_schema(n.oid)
25
+ AND (
26
+ pg_has_role(c.relowner, 'USAGE')
27
+ OR has_table_privilege(
28
+ c.oid,
29
+ 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
30
+ )
31
+ OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
32
+ )
33
+ ORDER BY
34
+ c.oid,
35
+ array_position(i.indkey, a.attnum)
36
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
37
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
38
+ `;
@@ -0,0 +1,32 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryProps } from "./common.ts";
3
+
4
+ export const SCHEMAS_SQL = (
5
+ props: SQLQueryProps & {
6
+ nameFilter?: string;
7
+ idsFilter?: string;
8
+ includeSystemSchemas?: boolean;
9
+ },
10
+ ) => /* SQL */ `
11
+ -- Adapted from information_schema.schemata
12
+ select
13
+ n.oid::int8 as id,
14
+ n.nspname as name,
15
+ u.rolname as owner
16
+ from
17
+ pg_namespace n,
18
+ pg_roles u
19
+ where
20
+ n.nspowner = u.oid
21
+ ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ""}
22
+ ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ""}
23
+ ${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ""}
24
+ and (
25
+ pg_has_role(n.nspowner, 'USAGE')
26
+ or has_schema_privilege(n.oid, 'CREATE, USAGE')
27
+ )
28
+ and not pg_catalog.starts_with(n.nspname, 'pg_temp_')
29
+ 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
+ `;
@@ -0,0 +1,47 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
3
+
4
+ export const TABLES_SQL = (
5
+ props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
6
+ tableIdentifierFilter?: string;
7
+ },
8
+ ) => /* SQL */ `
9
+ SELECT
10
+ c.oid :: int8 AS id,
11
+ nc.nspname AS schema,
12
+ c.relname AS name,
13
+ c.relrowsecurity AS rls_enabled,
14
+ c.relforcerowsecurity AS rls_forced,
15
+ CASE
16
+ WHEN c.relreplident = 'd' THEN 'DEFAULT'
17
+ WHEN c.relreplident = 'i' THEN 'INDEX'
18
+ WHEN c.relreplident = 'f' THEN 'FULL'
19
+ ELSE 'NOTHING'
20
+ END AS replica_identity,
21
+ pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes,
22
+ pg_size_pretty(
23
+ pg_total_relation_size(format('%I.%I', nc.nspname, c.relname))
24
+ ) AS size,
25
+ pg_stat_get_live_tuples(c.oid) AS live_rows_estimate,
26
+ pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate,
27
+ obj_description(c.oid) AS comment
28
+ FROM
29
+ pg_namespace nc
30
+ JOIN pg_class c ON nc.oid = c.relnamespace
31
+ WHERE
32
+ ${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
+ c.relkind IN ('r', 'p')
36
+ AND NOT pg_is_other_temp_schema(nc.oid)
37
+ AND (
38
+ pg_has_role(c.relowner, 'USAGE')
39
+ OR has_table_privilege(
40
+ c.oid,
41
+ 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
42
+ )
43
+ OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
44
+ )
45
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
46
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
47
+ `;
@@ -0,0 +1,53 @@
1
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
+
3
+ export const TABLE_RELATIONSHIPS_SQL = (
4
+ props: SQLQueryPropsWithSchemaFilter,
5
+ ) => /* SQL */ `
6
+ -- Adapted from
7
+ -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722
8
+ WITH
9
+ pks_uniques_cols AS (
10
+ SELECT
11
+ connamespace,
12
+ conrelid,
13
+ jsonb_agg(column_info.cols) as cols
14
+ FROM pg_constraint
15
+ JOIN lateral (
16
+ SELECT array_agg(cols.attname order by cols.attnum) as cols
17
+ FROM ( select unnest(conkey) as col) _
18
+ JOIN pg_attribute cols on cols.attrelid = conrelid and cols.attnum = col
19
+ ) column_info ON TRUE
20
+ WHERE
21
+ contype IN ('p', 'u') and
22
+ connamespace::regnamespace::text <> 'pg_catalog'
23
+ ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ""}
24
+ GROUP BY connamespace, conrelid
25
+ )
26
+ SELECT
27
+ traint.conname AS foreign_key_name,
28
+ ns1.nspname AS schema,
29
+ tab.relname AS relation,
30
+ column_info.cols AS columns,
31
+ ns2.nspname AS referenced_schema,
32
+ other.relname AS referenced_relation,
33
+ column_info.refs AS referenced_columns,
34
+ (column_info.cols IN (SELECT * FROM jsonb_array_elements(pks_uqs.cols))) AS is_one_to_one
35
+ FROM pg_constraint traint
36
+ JOIN LATERAL (
37
+ SELECT
38
+ jsonb_agg(cols.attname order by ord) AS cols,
39
+ jsonb_agg(refs.attname order by ord) AS refs
40
+ FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord)
41
+ JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col
42
+ JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref
43
+ WHERE ${props.schemaFilter ? `traint.connamespace::regnamespace::text ${props.schemaFilter}` : "true"}
44
+ ) AS column_info ON TRUE
45
+ JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace
46
+ JOIN pg_class tab ON tab.oid = traint.conrelid
47
+ JOIN pg_class other ON other.oid = traint.confrelid
48
+ JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace
49
+ LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid
50
+ WHERE traint.contype = 'f'
51
+ AND traint.conparentid = 0
52
+ ${props.schemaFilter ? `and ns1.nspname ${props.schemaFilter}` : ""}
53
+ `;
@@ -0,0 +1,74 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
3
+
4
+ export const TYPES_SQL = (
5
+ props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
6
+ includeTableTypes?: boolean;
7
+ includeArrayTypes?: boolean;
8
+ },
9
+ ) => /* SQL */ `
10
+ select
11
+ t.oid::int8 as id,
12
+ t.typname as name,
13
+ n.nspname as schema,
14
+ format_type (t.oid, null) as format,
15
+ coalesce(t_enums.enums, '[]') as enums,
16
+ coalesce(t_attributes.attributes, '[]') as attributes,
17
+ obj_description (t.oid, 'pg_type') as comment,
18
+ nullif(t.typrelid::int8, 0) as type_relation_id
19
+ from
20
+ pg_type t
21
+ left join pg_namespace n on n.oid = t.typnamespace
22
+ left join (
23
+ select
24
+ enumtypid,
25
+ jsonb_agg(enumlabel order by enumsortorder) as enums
26
+ from
27
+ pg_enum
28
+ group by
29
+ enumtypid
30
+ ) as t_enums on t_enums.enumtypid = t.oid
31
+ left join (
32
+ select
33
+ oid,
34
+ jsonb_agg(
35
+ jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8)
36
+ order by a.attnum asc
37
+ ) as attributes
38
+ from
39
+ pg_class c
40
+ join pg_attribute a on a.attrelid = c.oid
41
+ where
42
+ c.relkind = 'c' and not a.attisdropped
43
+ group by
44
+ c.oid
45
+ ) as t_attributes on t_attributes.oid = t.typrelid
46
+ where
47
+ (
48
+ t.typrelid = 0
49
+ or (
50
+ select
51
+ c.relkind ${props.includeTableTypes ? `in ('c', 'r', 'v', 'm', 'p')` : `= 'c'`}
52
+ from
53
+ pg_class c
54
+ where
55
+ c.oid = t.typrelid
56
+ )
57
+ )
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
+ `;
@@ -0,0 +1,26 @@
1
+ import { literal } from "pg-format";
2
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
3
+
4
+ export const VIEWS_SQL = (
5
+ props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
6
+ viewIdentifierFilter?: string;
7
+ },
8
+ ) => /* SQL */ `
9
+ SELECT
10
+ c.oid :: int8 AS id,
11
+ n.nspname AS schema,
12
+ c.relname AS name,
13
+ -- See definition of information_schema.views
14
+ (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable,
15
+ obj_description(c.oid) AS comment
16
+ FROM
17
+ pg_class c
18
+ JOIN pg_namespace n ON n.oid = c.relnamespace
19
+ WHERE
20
+ ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
21
+ ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
22
+ ${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ""}
23
+ c.relkind = 'v'
24
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
25
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
26
+ `;
@@ -0,0 +1,199 @@
1
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
+
3
+ export const VIEWS_KEY_DEPENDENCIES_SQL = (
4
+ props: SQLQueryPropsWithSchemaFilter,
5
+ ) => /* SQL */ `
6
+ -- Adapted from
7
+ -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820
8
+ with recursive
9
+ pks_fks as (
10
+ -- pk + fk referencing col
11
+ select
12
+ contype::text as contype,
13
+ conname,
14
+ array_length(conkey, 1) as ncol,
15
+ conrelid as resorigtbl,
16
+ col as resorigcol,
17
+ ord
18
+ from pg_constraint
19
+ left join lateral unnest(conkey) with ordinality as _(col, ord) on true
20
+ where contype IN ('p', 'f')
21
+ union
22
+ -- fk referenced col
23
+ select
24
+ concat(contype, '_ref') as contype,
25
+ conname,
26
+ array_length(confkey, 1) as ncol,
27
+ confrelid,
28
+ col,
29
+ ord
30
+ from pg_constraint
31
+ left join lateral unnest(confkey) with ordinality as _(col, ord) on true
32
+ where contype='f'
33
+ ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ""}
34
+ ),
35
+ views as (
36
+ select
37
+ c.oid as view_id,
38
+ n.nspname as view_schema,
39
+ c.relname as view_name,
40
+ r.ev_action as view_definition
41
+ from pg_class c
42
+ join pg_namespace n on n.oid = c.relnamespace
43
+ join pg_rewrite r on r.ev_class = c.oid
44
+ where c.relkind in ('v', 'm')
45
+ ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
46
+ ),
47
+ transform_json as (
48
+ select
49
+ view_id, view_schema, view_name,
50
+ -- the following formatting is without indentation on purpose
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
125
+ from views
126
+ ),
127
+ target_entries as(
128
+ select
129
+ view_id, view_schema, view_name,
130
+ json_array_elements(view_definition->0->'targetList') as entry
131
+ from transform_json
132
+ ),
133
+ results as(
134
+ select
135
+ view_id, view_schema, view_name,
136
+ (entry->>'resno')::int as view_column,
137
+ (entry->>'resorigtbl')::oid as resorigtbl,
138
+ (entry->>'resorigcol')::int as resorigcol
139
+ from target_entries
140
+ ),
141
+ -- CYCLE detection according to PG docs: https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-CYCLE
142
+ -- Can be replaced with CYCLE clause once PG v13 is EOL.
143
+ recursion(view_id, view_schema, view_name, view_column, resorigtbl, resorigcol, is_cycle, path) as(
144
+ select
145
+ r.*,
146
+ false,
147
+ ARRAY[resorigtbl]
148
+ from results r
149
+ where ${props.schemaFilter ? `view_schema ${props.schemaFilter}` : "true"}
150
+ union all
151
+ select
152
+ view.view_id,
153
+ view.view_schema,
154
+ view.view_name,
155
+ view.view_column,
156
+ tab.resorigtbl,
157
+ tab.resorigcol,
158
+ tab.resorigtbl = ANY(path),
159
+ path || tab.resorigtbl
160
+ from recursion view
161
+ join results tab on view.resorigtbl=tab.view_id and view.resorigcol=tab.view_column
162
+ where not is_cycle
163
+ ),
164
+ repeated_references as(
165
+ select
166
+ view_id,
167
+ view_schema,
168
+ view_name,
169
+ resorigtbl,
170
+ resorigcol,
171
+ array_agg(attname) as view_columns
172
+ from recursion
173
+ join pg_attribute vcol on vcol.attrelid = view_id and vcol.attnum = view_column
174
+ group by
175
+ view_id,
176
+ view_schema,
177
+ view_name,
178
+ resorigtbl,
179
+ resorigcol
180
+ )
181
+ select
182
+ sch.nspname as table_schema,
183
+ tbl.relname as table_name,
184
+ rep.view_schema,
185
+ rep.view_name,
186
+ pks_fks.conname as constraint_name,
187
+ pks_fks.contype as constraint_type,
188
+ jsonb_agg(
189
+ jsonb_build_object('table_column', col.attname, 'view_columns', view_columns) order by pks_fks.ord
190
+ ) as column_dependencies
191
+ from repeated_references rep
192
+ join pks_fks using (resorigtbl, resorigcol)
193
+ join pg_class tbl on tbl.oid = rep.resorigtbl
194
+ join pg_attribute col on col.attrelid = tbl.oid and col.attnum = rep.resorigcol
195
+ join pg_namespace sch on sch.oid = tbl.relnamespace
196
+ group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conname, pks_fks.contype, pks_fks.ncol
197
+ -- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs
198
+ having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
199
+ `;