@supabase/postgrest-typegen 0.2.0 → 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.
Files changed (66) hide show
  1. package/README.md +12 -0
  2. package/dist/collation.d.ts +2 -0
  3. package/dist/collation.js +15 -0
  4. package/dist/generation/go.js +30 -2
  5. package/dist/generation/index.d.ts +1 -1
  6. package/dist/generation/python.js +24 -10
  7. package/dist/generation/swift.js +52 -3
  8. package/dist/generation/typescript.d.ts +18 -7
  9. package/dist/generation/typescript.js +122 -82
  10. package/dist/introspection/index.js +6 -23
  11. package/dist/introspection/relationships.d.ts +4 -4
  12. package/dist/introspection/relationships.js +4 -11
  13. package/dist/introspection/sql/columns.sql.d.ts +2 -7
  14. package/dist/introspection/sql/columns.sql.js +32 -9
  15. package/dist/introspection/sql/common.d.ts +7 -15
  16. package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
  17. package/dist/introspection/sql/foreign_tables.sql.js +0 -5
  18. package/dist/introspection/sql/functions.sql.d.ts +2 -5
  19. package/dist/introspection/sql/functions.sql.js +0 -33
  20. package/dist/introspection/sql/helpers.d.ts +1 -1
  21. package/dist/introspection/sql/helpers.js +2 -2
  22. package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
  23. package/dist/introspection/sql/materialized_views.sql.js +0 -5
  24. package/dist/introspection/sql/pg-format.d.ts +21 -0
  25. package/dist/introspection/sql/pg-format.js +92 -0
  26. package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
  27. package/dist/introspection/sql/primary_keys.sql.js +0 -4
  28. package/dist/introspection/sql/schemas.sql.d.ts +2 -6
  29. package/dist/introspection/sql/schemas.sql.js +2 -6
  30. package/dist/introspection/sql/table.sql.d.ts +2 -4
  31. package/dist/introspection/sql/table.sql.js +0 -5
  32. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
  33. package/dist/introspection/sql/types.sql.d.ts +5 -5
  34. package/dist/introspection/sql/types.sql.js +6 -17
  35. package/dist/introspection/sql/views.sql.d.ts +2 -4
  36. package/dist/introspection/sql/views.sql.js +6 -5
  37. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
  38. package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
  39. package/dist/sort.js +16 -15
  40. package/dist/types.d.ts +4 -0
  41. package/dist/types.js +11 -0
  42. package/package.json +3 -5
  43. package/src/collation.ts +16 -0
  44. package/src/generation/go.ts +31 -2
  45. package/src/generation/index.ts +1 -0
  46. package/src/generation/python.ts +25 -10
  47. package/src/generation/swift.ts +53 -3
  48. package/src/generation/typescript.ts +187 -107
  49. package/src/introspection/index.ts +7 -32
  50. package/src/introspection/relationships.ts +10 -19
  51. package/src/introspection/sql/columns.sql.ts +34 -18
  52. package/src/introspection/sql/common.ts +7 -17
  53. package/src/introspection/sql/foreign_tables.sql.ts +2 -13
  54. package/src/introspection/sql/functions.sql.ts +2 -44
  55. package/src/introspection/sql/helpers.ts +2 -2
  56. package/src/introspection/sql/materialized_views.sql.ts +2 -11
  57. package/src/introspection/sql/pg-format.ts +101 -0
  58. package/src/introspection/sql/primary_keys.sql.ts +2 -10
  59. package/src/introspection/sql/schemas.sql.ts +4 -14
  60. package/src/introspection/sql/table.sql.ts +2 -11
  61. package/src/introspection/sql/table_relationships.sql.ts +2 -4
  62. package/src/introspection/sql/types.sql.ts +6 -26
  63. package/src/introspection/sql/views.sql.ts +8 -11
  64. package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
  65. package/src/sort.ts +20 -15
  66. package/src/types.ts +11 -0
@@ -1,7 +1,3 @@
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
1
  export const FUNCTIONS_SQL = (props) => /* SQL */ `
6
2
  -- CTE with sane arg_modes, arg_names, and arg_types.
7
3
  -- All three are always of the same length.
@@ -29,33 +25,6 @@ with functions as (
29
25
  ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ""}
30
26
  where
31
27
  ${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
28
  p.prokind = 'f'
60
29
  )
61
30
  select
@@ -143,6 +112,4 @@ from
143
112
  group by
144
113
  t1.oid
145
114
  ) f_args on f_args.oid = f.oid
146
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
147
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
148
115
  `;
@@ -6,7 +6,7 @@ export declare const DEFAULT_SYSTEM_SCHEMAS: string[];
6
6
  /**
7
7
  * Build a SQL `IN (...)` / `NOT IN (...)` fragment for an include/exclude list,
8
8
  * ported verbatim from `postgres-meta/src/lib/helpers.ts`. Values are escaped
9
- * with pg-format's `literal`. Returns an empty string when there is nothing to
9
+ * with the inlined pg-format `literal`. Returns an empty string when there is nothing to
10
10
  * filter, so callers interpolate it conditionally.
11
11
  */
12
12
  export declare const filterByList: (include?: (string | number)[], exclude?: (string | number)[], defaultExclude?: (string | number)[]) => string;
@@ -1,4 +1,4 @@
1
- import { literal } from "pg-format";
1
+ import { literal } from "./pg-format.js";
2
2
  /**
3
3
  * Schemas that postgres-meta excludes by default when `includeSystemSchemas`
4
4
  * is false. Ported from `postgres-meta/src/lib/constants.ts`.
@@ -11,7 +11,7 @@ export const DEFAULT_SYSTEM_SCHEMAS = [
11
11
  /**
12
12
  * Build a SQL `IN (...)` / `NOT IN (...)` fragment for an include/exclude list,
13
13
  * ported verbatim from `postgres-meta/src/lib/helpers.ts`. Values are escaped
14
- * with pg-format's `literal`. Returns an empty string when there is nothing to
14
+ * with the inlined pg-format `literal`. Returns an empty string when there is nothing to
15
15
  * filter, so callers interpolate it conditionally.
16
16
  */
17
17
  export const filterByList = (include, exclude, defaultExclude) => {
@@ -1,4 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
- export declare const MATERIALIZED_VIEWS_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
- materializedViewIdentifierFilter?: string;
4
- }) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const MATERIALIZED_VIEWS_SQL: (props: SchemaFilterProps) => string;
@@ -1,4 +1,3 @@
1
- import { literal } from "pg-format";
2
1
  export const MATERIALIZED_VIEWS_SQL = (props) => /* SQL */ `
3
2
  select
4
3
  c.oid::int8 as id,
@@ -11,9 +10,5 @@ from
11
10
  join pg_namespace n on n.oid = c.relnamespace
12
11
  where
13
12
  ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
14
- ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
15
- ${props.materializedViewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.materializedViewIdentifierFilter} AND` : ""}
16
13
  c.relkind = 'm'
17
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
18
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
19
14
  `;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Minimal inline port of the `pg-format` helpers this package uses, so the
3
+ * package carries no runtime dependency for them (the CommonJS-only
4
+ * `pg-format` package is awkward to consume from ESM/bundled CLIs).
5
+ *
6
+ * Mirrors `pg-format@1.0.4` byte-for-byte for the inputs postgres-meta feeds
7
+ * it — in particular numbers are quoted (`literal(10)` is `'10'`), because
8
+ * that is what postgres-meta's SQL emits and this package is byte-parity
9
+ * constrained against it. The `supabase/supabase` `packages/pg-meta` port
10
+ * deliberately diverges here (unquoted numbers); do not "fix" this to match it.
11
+ *
12
+ * Originally ported from PostgreSQL's `src/interfaces/libpq/fe-exec.c`.
13
+ */
14
+ /**
15
+ * Quote a value as a SQL literal. `null`/`undefined` become `NULL`, booleans
16
+ * `'t'`/`'f'`, dates ISO 8601 strings, arrays comma-separated lists (nested
17
+ * arrays become parenthesised groups), plain objects `'…'::jsonb`, and
18
+ * everything else — including numbers — a single-quoted string with embedded
19
+ * quotes doubled. Strings containing a backslash use the `E'…'` form.
20
+ */
21
+ export declare function literal(value?: unknown): string;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Minimal inline port of the `pg-format` helpers this package uses, so the
3
+ * package carries no runtime dependency for them (the CommonJS-only
4
+ * `pg-format` package is awkward to consume from ESM/bundled CLIs).
5
+ *
6
+ * Mirrors `pg-format@1.0.4` byte-for-byte for the inputs postgres-meta feeds
7
+ * it — in particular numbers are quoted (`literal(10)` is `'10'`), because
8
+ * that is what postgres-meta's SQL emits and this package is byte-parity
9
+ * constrained against it. The `supabase/supabase` `packages/pg-meta` port
10
+ * deliberately diverges here (unquoted numbers); do not "fix" this to match it.
11
+ *
12
+ * Originally ported from PostgreSQL's `src/interfaces/libpq/fe-exec.c`.
13
+ */
14
+ /** Convert a JS ISO string to Postgres' default ISO 8601 form. */
15
+ function formatDate(date) {
16
+ return date.replace("T", " ").replace("Z", "+00");
17
+ }
18
+ function arrayToList(useSpace, array, formatter) {
19
+ let sql = useSpace ? " (" : "(";
20
+ for (const [index, element] of array.entries()) {
21
+ sql += (index === 0 ? "" : ", ") + formatter(element);
22
+ }
23
+ sql += ")";
24
+ return sql;
25
+ }
26
+ /**
27
+ * Quote a value as a SQL literal. `null`/`undefined` become `NULL`, booleans
28
+ * `'t'`/`'f'`, dates ISO 8601 strings, arrays comma-separated lists (nested
29
+ * arrays become parenthesised groups), plain objects `'…'::jsonb`, and
30
+ * everything else — including numbers — a single-quoted string with embedded
31
+ * quotes doubled. Strings containing a backslash use the `E'…'` form.
32
+ */
33
+ export function literal(value) {
34
+ let text;
35
+ let explicitCast;
36
+ if (value === undefined || value === null) {
37
+ return "NULL";
38
+ }
39
+ if (value === false) {
40
+ return "'f'";
41
+ }
42
+ if (value === true) {
43
+ return "'t'";
44
+ }
45
+ if (value instanceof Date) {
46
+ return `'${formatDate(value.toISOString())}'`;
47
+ }
48
+ if (Array.isArray(value)) {
49
+ const parts = [];
50
+ for (const [index, element] of value.entries()) {
51
+ if (Array.isArray(element)) {
52
+ parts.push(arrayToList(index !== 0, element, literal));
53
+ }
54
+ else {
55
+ parts.push(literal(element));
56
+ }
57
+ }
58
+ return parts.toString();
59
+ }
60
+ if (typeof value === "string") {
61
+ text = value;
62
+ }
63
+ else if (typeof value === "number" || typeof value === "bigint") {
64
+ text = value.toString();
65
+ }
66
+ else {
67
+ explicitCast = "jsonb";
68
+ text = JSON.stringify(value);
69
+ }
70
+ let hasBackslash = false;
71
+ let quoted = "'";
72
+ for (const c of text) {
73
+ if (c === "'") {
74
+ quoted += c + c;
75
+ }
76
+ else if (c === "\\") {
77
+ quoted += c + c;
78
+ hasBackslash = true;
79
+ }
80
+ else {
81
+ quoted += c;
82
+ }
83
+ }
84
+ quoted += "'";
85
+ if (hasBackslash) {
86
+ quoted = `E${quoted}`;
87
+ }
88
+ if (explicitCast) {
89
+ quoted += `::${explicitCast}`;
90
+ }
91
+ return quoted;
92
+ }
@@ -1,4 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
- export declare const PRIMARY_KEYS_SQL: (props: SQLQueryPropsWithSchemaFilter & {
3
- tableIdentifierFilter?: string;
4
- }) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const PRIMARY_KEYS_SQL: (props: SchemaFilterProps) => string;
@@ -1,4 +1,3 @@
1
- import { literal } from "pg-format";
2
1
  export const PRIMARY_KEYS_SQL = (props) => /* SQL */ `
3
2
  SELECT
4
3
  c.oid :: int8 AS table_id,
@@ -12,7 +11,6 @@ FROM
12
11
  JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
13
12
  WHERE
14
13
  ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
15
- ${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
16
14
  i.indisprimary
17
15
  AND c.relkind IN ('r', 'p')
18
16
  AND NOT pg_is_other_temp_schema(n.oid)
@@ -27,6 +25,4 @@ WHERE
27
25
  ORDER BY
28
26
  c.oid,
29
27
  array_position(i.indkey, a.attnum)
30
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
31
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
32
28
  `;
@@ -1,6 +1,2 @@
1
- import type { SQLQueryProps } from "./common.ts";
2
- export declare const SCHEMAS_SQL: (props: SQLQueryProps & {
3
- nameFilter?: string;
4
- idsFilter?: string;
5
- includeSystemSchemas?: boolean;
6
- }) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const SCHEMAS_SQL: (props: SchemaFilterProps) => string;
@@ -1,4 +1,3 @@
1
- import { literal } from "pg-format";
2
1
  export const SCHEMAS_SQL = (props) => /* SQL */ `
3
2
  -- Adapted from information_schema.schemata
4
3
  select
@@ -10,15 +9,12 @@ from
10
9
  pg_roles u
11
10
  where
12
11
  n.nspowner = u.oid
13
- ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ""}
14
- ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ""}
15
- ${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ""}
12
+ ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
13
+ and not pg_catalog.starts_with(n.nspname, 'pg_')
16
14
  and (
17
15
  pg_has_role(n.nspowner, 'USAGE')
18
16
  or has_schema_privilege(n.oid, 'CREATE, USAGE')
19
17
  )
20
18
  and not pg_catalog.starts_with(n.nspname, 'pg_temp_')
21
19
  and not pg_catalog.starts_with(n.nspname, 'pg_toast_temp_')
22
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
23
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
24
20
  `;
@@ -1,4 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
- export declare const TABLES_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
- tableIdentifierFilter?: string;
4
- }) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const TABLES_SQL: (props: SchemaFilterProps) => string;
@@ -1,4 +1,3 @@
1
- import { literal } from "pg-format";
2
1
  export const TABLES_SQL = (props) => /* SQL */ `
3
2
  SELECT
4
3
  c.oid :: int8 AS id,
@@ -24,8 +23,6 @@ FROM
24
23
  JOIN pg_class c ON nc.oid = c.relnamespace
25
24
  WHERE
26
25
  ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ""}
27
- ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
28
- ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
29
26
  c.relkind IN ('r', 'p')
30
27
  AND NOT pg_is_other_temp_schema(nc.oid)
31
28
  AND (
@@ -36,6 +33,4 @@ WHERE
36
33
  )
37
34
  OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
38
35
  )
39
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
40
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
41
36
  `;
@@ -1,2 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
- export declare const TABLE_RELATIONSHIPS_SQL: (props: SQLQueryPropsWithSchemaFilter) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const TABLE_RELATIONSHIPS_SQL: (props: SchemaFilterProps) => string;
@@ -1,5 +1,5 @@
1
- import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
- export declare const TYPES_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
- includeTableTypes?: boolean;
4
- includeArrayTypes?: boolean;
5
- }) => string;
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 declare const TYPES_SQL = "\nselect\n t.oid::int8 as id,\n t.typname as name,\n n.nspname as schema,\n format_type (t.oid, null) as format,\n coalesce(t_enums.enums, '[]') as enums,\n coalesce(t_attributes.attributes, '[]') as attributes,\n obj_description (t.oid, 'pg_type') as comment,\n nullif(t.typrelid::int8, 0) as type_relation_id\nfrom\n pg_type t\n left join pg_namespace n on n.oid = t.typnamespace\n left join (\n select\n enumtypid,\n jsonb_agg(enumlabel order by enumsortorder) as enums\n from\n pg_enum\n group by\n enumtypid\n ) as t_enums on t_enums.enumtypid = t.oid\n left join (\n select\n oid,\n jsonb_agg(\n jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8)\n order by a.attnum asc\n ) as attributes\n from\n pg_class c\n join pg_attribute a on a.attrelid = c.oid\n where\n c.relkind = 'c' and not a.attisdropped\n group by\n c.oid\n ) as t_attributes on t_attributes.oid = t.typrelid\n where\n (\n t.typrelid = 0\n or (\n select\n c.relkind in ('c', 'r', 'v', 'm', 'p', 'f')\n from\n pg_class c\n where\n c.oid = t.typrelid\n )\n )\n";
@@ -1,5 +1,8 @@
1
- import { literal } from "pg-format";
2
- export const TYPES_SQL = (props) => /* 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 */ `
3
6
  select
4
7
  t.oid::int8 as id,
5
8
  t.typname as name,
@@ -41,25 +44,11 @@ from
41
44
  t.typrelid = 0
42
45
  or (
43
46
  select
44
- c.relkind ${props.includeTableTypes ? `in ('c', 'r', 'v', 'm', 'p')` : `= 'c'`}
47
+ c.relkind in ('c', 'r', 'v', 'm', 'p', 'f')
45
48
  from
46
49
  pg_class c
47
50
  where
48
51
  c.oid = t.typrelid
49
52
  )
50
53
  )
51
- ${!props.includeArrayTypes
52
- ? `and not exists (
53
- select
54
- from
55
- pg_type el
56
- where
57
- el.oid = t.typelem
58
- and el.typarray = t.oid
59
- )`
60
- : ""}
61
- ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
62
- ${props.idsFilter ? `and t.oid ${props.idsFilter}` : ""}
63
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
64
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
65
54
  `;
@@ -1,4 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
- export declare const VIEWS_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
- viewIdentifierFilter?: string;
4
- }) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const VIEWS_SQL: (props: SchemaFilterProps) => string;
@@ -1,4 +1,3 @@
1
- import { literal } from "pg-format";
2
1
  export const VIEWS_SQL = (props) => /* SQL */ `
3
2
  SELECT
4
3
  c.oid :: int8 AS id,
@@ -6,15 +5,17 @@ SELECT
6
5
  c.relname AS name,
7
6
  -- See definition of information_schema.views
8
7
  (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable,
8
+ -- Passing include_triggers => true also counts views made writable by
9
+ -- INSTEAD OF triggers (and INSTEAD rules), which PostgREST can write
10
+ -- through even when the view is not auto-updatable. Bit 8 is INSERT,
11
+ -- bit 4 is UPDATE (see information_schema.views).
12
+ (pg_relation_is_updatable(c.oid, true) & 8) = 8 AS is_insert_enabled,
13
+ (pg_relation_is_updatable(c.oid, true) & 4) = 4 AS is_update_enabled,
9
14
  obj_description(c.oid) AS comment
10
15
  FROM
11
16
  pg_class c
12
17
  JOIN pg_namespace n ON n.oid = c.relnamespace
13
18
  WHERE
14
19
  ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
15
- ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
16
- ${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ""}
17
20
  c.relkind = 'v'
18
- ${props.limit ? `limit ${literal(props.limit)}` : ""}
19
- ${props.offset ? `offset ${literal(props.offset)}` : ""}
20
21
  `;
@@ -1,2 +1,2 @@
1
- import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
- export declare const VIEWS_KEY_DEPENDENCIES_SQL: (props: SQLQueryPropsWithSchemaFilter) => string;
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ export declare const VIEWS_KEY_DEPENDENCIES_SQL: (props: SchemaFilterProps) => string;
@@ -1,3 +1,4 @@
1
+ import { literal } from "./pg-format.js";
1
2
  export const VIEWS_KEY_DEPENDENCIES_SQL = (props) => /* SQL */ `
2
3
  -- Adapted from
3
4
  -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820
@@ -43,81 +44,7 @@ views as (
43
44
  transform_json as (
44
45
  select
45
46
  view_id, view_schema, view_name,
46
- -- the following formatting is without indentation on purpose
47
- -- to allow simple diffs, with less whitespace noise
48
- replace(
49
- replace(
50
- replace(
51
- replace(
52
- replace(
53
- replace(
54
- replace(
55
- regexp_replace(
56
- replace(
57
- replace(
58
- replace(
59
- replace(
60
- replace(
61
- replace(
62
- replace(
63
- replace(
64
- replace(
65
- replace(
66
- replace(
67
- view_definition::text,
68
- -- This conversion to json is heavily optimized for performance.
69
- -- The general idea is to use as few regexp_replace() calls as possible.
70
- -- Simple replace() is a lot faster, so we jump through some hoops
71
- -- to be able to use regexp_replace() only once.
72
- -- This has been tested against a huge schema with 250+ different views.
73
- -- The unit tests do NOT reflect all possible inputs. Be careful when changing this!
74
- -- -----------------------------------------------
75
- -- pattern | replacement | flags
76
- -- -----------------------------------------------
77
- -- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof
78
- -- we need to make this an empty array here to prevent json_array_elements from throwing an error
79
- -- when the targetList is null.
80
- -- We'll need to put it first, to make the node protection below work for node lists that start with
81
- -- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value.
82
- '<>' , '()'
83
- -- , is not part of the pg_node_tree format, but used in the regex.
84
- -- This removes all , that might be part of column names.
85
- ), ',' , ''
86
- -- The same applies for { and }, although those are used a lot in pg_node_tree.
87
- -- We remove the escaped ones, which might be part of column names again.
88
- ), E'\\\\{' , ''
89
- ), E'\\\\}' , ''
90
- -- The fields we need are formatted as json manually to protect them from the regex.
91
- ), ' :targetList ' , ',"targetList":'
92
- ), ' :resno ' , ',"resno":'
93
- ), ' :resorigtbl ' , ',"resorigtbl":'
94
- ), ' :resorigcol ' , ',"resorigcol":'
95
- -- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass.
96
- ), '{' , '{ :'
97
- -- Protect node lists, which start with \`({\` or \`((\` from the greedy regex.
98
- -- The extra \`{\` is removed again later.
99
- ), '((' , '{(('
100
- ), '({' , '{({'
101
- -- This regex removes all unused fields to avoid the need to format all of them correctly.
102
- -- This leads to a smaller json result as well.
103
- -- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node.
104
- -- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and
105
- -- add an empty key for the followig node.
106
- ), ' :[^}{,]+' , ',"":' , 'g'
107
- -- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`.
108
- -- Those are removed next.
109
- ), ',"":}' , '}'
110
- ), ',"":,' , ','
111
- -- This reverses the "node list protection" from above.
112
- ), '{(' , '('
113
- -- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it.
114
- ), '{,' , '{'
115
- -- pg_node_tree has \`()\` around lists, but JSON uses \`[]\`
116
- ), '(' , '['
117
- ), ')' , ']'
118
- -- pg_node_tree has \` \` between list items, but JSON uses \`,\`
119
- ), ' ' , ','
120
- )::json as view_definition
47
+ ${nodeTreeToJson("view_definition::text")}::json as view_definition
121
48
  from views
122
49
  ),
123
50
  target_entries as(
@@ -193,3 +120,65 @@ group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conn
193
120
  -- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs
194
121
  having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
195
122
  `;
123
+ /**
124
+ * The rewrites, innermost first, that turn the `pg_node_tree` text of a view
125
+ * definition into JSON exposing only `targetList`, `resno`, `resorigtbl` and
126
+ * `resorigcol`. Ported from PostgREST's schema cache query, which keeps them
127
+ * as plain `replace` calls with a single `regexp_replace` for speed; the
128
+ * order matters, and the comments carry PostgREST's reasoning.
129
+ */
130
+ const NODE_TREE_TO_JSON_REWRITES = [
131
+ // `<>` is pg_node_tree's null. json_typeof is too slow to special-case it,
132
+ // so it becomes an empty list, which also keeps the node protection below
133
+ // working for lists that start with null, such as coldefexprs whose first
134
+ // column has no default.
135
+ { pattern: "<>", replacement: "()" },
136
+ // `,`, `{` and `}` are used by the JSON conversion; drop the ones that may
137
+ // be part of column names (`{` and `}` are escaped there).
138
+ { pattern: ",", replacement: "" },
139
+ { pattern: "\\{", replacement: "" },
140
+ { pattern: "\\}", replacement: "" },
141
+ // Format the fields we need as JSON keys so the regex below leaves them.
142
+ { pattern: " :targetList ", replacement: ',"targetList":' },
143
+ { pattern: " :resno ", replacement: ',"resno":' },
144
+ { pattern: " :resorigtbl ", replacement: ',"resorigtbl":' },
145
+ { pattern: " :resorigcol ", replacement: ',"resorigcol":' },
146
+ // Make the regex also match the node type, e.g. `{QUERY ...`.
147
+ { pattern: "{", replacement: "{ :" },
148
+ // Protect node lists, which start with `({` or `((`, from the greedy regex.
149
+ // The extra `{` is removed again below.
150
+ { pattern: "((", replacement: "{((" },
151
+ { pattern: "({", replacement: "{({" },
152
+ // Remove every unused field. Removal stops at `,` for the kept fields, at
153
+ // `}` for the end of the node and at `{` for a nested node, leaving an empty
154
+ // key for the following node.
155
+ { pattern: " :[^}{,]+", replacement: ',"":', isRegex: true },
156
+ // The regex also added those empty keys before `}` and `,`.
157
+ { pattern: ',"":}', replacement: "}" },
158
+ { pattern: ',"":,', replacement: "," },
159
+ // Undo the node list protection.
160
+ { pattern: "{(", replacement: "(" },
161
+ // Every key was added with a leading `,`; the first key of an object does
162
+ // not need it.
163
+ { pattern: "{,", replacement: "{" },
164
+ // pg_node_tree lists use `( )` with space separated items; JSON uses `[ ]`
165
+ // with commas.
166
+ { pattern: "(", replacement: "[" },
167
+ { pattern: ")", replacement: "]" },
168
+ { pattern: " ", replacement: "," },
169
+ ];
170
+ /**
171
+ * Render {@link NODE_TREE_TO_JSON_REWRITES} applied to `expression` as nested
172
+ * SQL calls: the opening calls stacked, then each rewrite's arguments on the
173
+ * line that closes it.
174
+ */
175
+ function nodeTreeToJson(expression) {
176
+ const lines = NODE_TREE_TO_JSON_REWRITES.toReversed().map((rewrite) => ` ${rewrite.isRegex ? "regexp_replace" : "replace"}(`);
177
+ lines.push(` ${expression},`);
178
+ NODE_TREE_TO_JSON_REWRITES.forEach((rewrite, index) => {
179
+ const flags = rewrite.isRegex ? ", 'g'" : "";
180
+ const closing = index === NODE_TREE_TO_JSON_REWRITES.length - 1 ? ")" : "),";
181
+ lines.push(` ${literal(rewrite.pattern)}, ${literal(rewrite.replacement)}${flags}${closing}`);
182
+ });
183
+ return lines.join("\n");
184
+ }
package/dist/sort.js CHANGED
@@ -1,12 +1,13 @@
1
+ import { compareStrings } from "./collation.js";
1
2
  // Relations (tables/views/materialized views/foreign tables) and types share a
2
3
  // (schema, name) identity within a database; `id` breaks any residual tie.
3
- const bySchemaName = (a, b) => a.schema.localeCompare(b.schema) ||
4
- a.name.localeCompare(b.name) ||
4
+ const bySchemaName = (a, b) => compareStrings(a.schema, b.schema) ||
5
+ compareStrings(a.name, b.name) ||
5
6
  a.id - b.id;
6
7
  // Mirrors the TypeScript generator's historical `relationships.sort`.
7
- const byRelationship = (a, b) => a.foreign_key_name.localeCompare(b.foreign_key_name) ||
8
- a.referenced_relation.localeCompare(b.referenced_relation) ||
9
- JSON.stringify(a.referenced_columns).localeCompare(JSON.stringify(b.referenced_columns));
8
+ const byRelationship = (a, b) => compareStrings(a.foreign_key_name, b.foreign_key_name) ||
9
+ compareStrings(a.referenced_relation, b.referenced_relation) ||
10
+ compareStrings(JSON.stringify(a.referenced_columns), JSON.stringify(b.referenced_columns));
10
11
  /**
11
12
  * Return a new {@link GeneratorMetadata} with every collection ordered by a
12
13
  * stable, total, semantic key (matching the TypeScript generator's ordering).
@@ -16,32 +17,32 @@ const byRelationship = (a, b) => a.foreign_key_name.localeCompare(b.foreign_key_
16
17
  export function sortGeneratorMetadata(metadata) {
17
18
  return {
18
19
  version: metadata.version,
19
- schemas: [...metadata.schemas].sort((a, b) => a.name.localeCompare(b.name) || a.id - b.id),
20
+ schemas: [...metadata.schemas].sort((a, b) => compareStrings(a.name, b.name) || a.id - b.id),
20
21
  tables: [...metadata.tables].sort(bySchemaName),
21
22
  foreignTables: [...metadata.foreignTables].sort(bySchemaName),
22
23
  views: [...metadata.views].sort(bySchemaName),
23
24
  materializedViews: [...metadata.materializedViews].sort(bySchemaName),
24
25
  // Group columns by their table's (schema, name); name-order within a table
25
26
  // (matches the TypeScript generator's per-table `columns.sort(by name)`).
26
- columns: [...metadata.columns].sort((a, b) => a.schema.localeCompare(b.schema) ||
27
- a.table.localeCompare(b.table) ||
28
- a.name.localeCompare(b.name)),
27
+ columns: [...metadata.columns].sort((a, b) => compareStrings(a.schema, b.schema) ||
28
+ compareStrings(a.table, b.table) ||
29
+ compareStrings(a.name, b.name)),
29
30
  // Grouped by table like `columns`; within a table the SQL's declared
30
31
  // column order (`array_position(indkey, attnum)`) is preserved by the
31
32
  // stable sort, since composite key order is meaningful.
32
- primaryKeys: [...metadata.primaryKeys].sort((a, b) => a.schema.localeCompare(b.schema) ||
33
- a.table_name.localeCompare(b.table_name)),
33
+ primaryKeys: [...metadata.primaryKeys].sort((a, b) => compareStrings(a.schema, b.schema) ||
34
+ compareStrings(a.table_name, b.table_name)),
34
35
  relationships: [...metadata.relationships].sort(byRelationship),
35
36
  // Functions can overload, so the signature is part of the identity. Args
36
37
  // are addressed by name in generated RPC types, so sort them by name too.
37
38
  functions: [...metadata.functions]
38
- .sort((a, b) => a.schema.localeCompare(b.schema) ||
39
- a.name.localeCompare(b.name) ||
40
- a.identity_argument_types.localeCompare(b.identity_argument_types) ||
39
+ .sort((a, b) => compareStrings(a.schema, b.schema) ||
40
+ compareStrings(a.name, b.name) ||
41
+ compareStrings(a.identity_argument_types, b.identity_argument_types) ||
41
42
  a.id - b.id)
42
43
  .map((fn) => ({
43
44
  ...fn,
44
- args: [...fn.args].sort((a, b) => a.name.localeCompare(b.name)),
45
+ args: [...fn.args].sort((a, b) => compareStrings(a.name, b.name)),
45
46
  })),
46
47
  types: [...metadata.types].sort(bySchemaName),
47
48
  };
package/dist/types.d.ts CHANGED
@@ -182,6 +182,8 @@ declare const postgresViewSchema: import("arktype/internal/variants/object.ts").
182
182
  schema: string;
183
183
  name: string;
184
184
  is_updatable: boolean;
185
+ is_insert_enabled?: boolean | undefined;
186
+ is_update_enabled?: boolean | undefined;
185
187
  comment: string | null;
186
188
  columns?: {
187
189
  table_id: number;
@@ -250,7 +252,9 @@ export declare const generatorMetadataSchema: import("arktype/internal/variants/
250
252
  views: {
251
253
  comment: string | null;
252
254
  id: number;
255
+ is_insert_enabled?: boolean | undefined;
253
256
  is_updatable: boolean;
257
+ is_update_enabled?: boolean | undefined;
254
258
  name: string;
255
259
  schema: string;
256
260
  }[];