From d087241c506a9f22e4186c6edff0c845d1b8698a Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Tue, 31 Oct 2023 19:59:41 +0800 Subject: [PATCH] feat(relationships): include is_one_to_one --- src/lib/PostgresMetaRelationships.ts | 3 +++ src/lib/sql/table_relationships.sql | 22 ++++++++++++++++- src/lib/types.ts | 1 + src/server/templates/typescript.ts | 2 ++ test/db/00-init.sql | 5 ++++ test/lib/tables.ts | 18 ++++++++++++++ test/server/typegen.ts | 37 ++++++++++++++++++++++++++++ 7 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/lib/PostgresMetaRelationships.ts b/src/lib/PostgresMetaRelationships.ts index 700982c7..059762c3 100644 --- a/src/lib/PostgresMetaRelationships.ts +++ b/src/lib/PostgresMetaRelationships.ts @@ -92,6 +92,7 @@ export default class PostgresMetaRelationships { schema: vtkd.view_schema, relation: vtkd.view_name, columns: viewColumns, + is_one_to_one: r.is_one_to_one, referenced_schema: r.referenced_schema, referenced_relation: r.referenced_relation, referenced_columns: r.referenced_columns, @@ -104,6 +105,7 @@ export default class PostgresMetaRelationships { schema: r.schema, relation: r.relation, columns: r.columns, + is_one_to_one: r.is_one_to_one, referenced_schema: tvkd.view_schema, referenced_relation: tvkd.view_name, referenced_columns: viewColumns, @@ -119,6 +121,7 @@ export default class PostgresMetaRelationships { schema: vtkd.view_schema, relation: vtkd.view_name, columns: viewColumns, + is_one_to_one: r.is_one_to_one, referenced_schema: tvkd.view_schema, referenced_relation: tvkd.view_name, referenced_columns: referencedViewColumns, diff --git a/src/lib/sql/table_relationships.sql b/src/lib/sql/table_relationships.sql index 0c303ff2..53b80ded 100644 --- a/src/lib/sql/table_relationships.sql +++ b/src/lib/sql/table_relationships.sql @@ -1,5 +1,22 @@ -- Adapted from -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722 +WITH +pks_uniques_cols AS ( + SELECT + connamespace, + conrelid, + jsonb_agg(column_info.cols) as cols + FROM pg_constraint + JOIN lateral ( + SELECT array_agg(cols.attname order by cols.attnum) as cols + FROM ( select unnest(conkey) as col) _ + JOIN pg_attribute cols on cols.attrelid = conrelid and cols.attnum = col + ) column_info ON TRUE + WHERE + contype IN ('p', 'u') and + connamespace::regnamespace::text <> 'pg_catalog' + GROUP BY connamespace, conrelid +) SELECT traint.conname AS foreign_key_name, ns1.nspname AS schema, @@ -7,7 +24,8 @@ SELECT column_info.cols AS columns, ns2.nspname AS referenced_schema, other.relname AS referenced_relation, - column_info.refs AS referenced_columns + column_info.refs AS referenced_columns, + (column_info.cols IN (SELECT * FROM jsonb_array_elements(pks_uqs.cols))) AS is_one_to_one FROM pg_constraint traint JOIN LATERAL ( SELECT @@ -21,4 +39,6 @@ JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace JOIN pg_class tab ON tab.oid = traint.conrelid JOIN pg_class other ON other.oid = traint.confrelid JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace +LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid WHERE traint.contype = 'f' +AND traint.conparentid = 0 diff --git a/src/lib/types.ts b/src/lib/types.ts index 333ce631..4b00a5f8 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -239,6 +239,7 @@ export const postgresRelationshipSchema = Type.Object({ schema: Type.String(), relation: Type.String(), columns: Type.Array(Type.String()), + is_one_to_one: Type.Boolean(), referenced_schema: Type.String(), referenced_relation: Type.String(), referenced_columns: Type.Array(Type.String()), diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index d0048709..992a4004 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -167,6 +167,7 @@ export interface Database { (relationship) => `{ foreignKeyName: ${JSON.stringify(relationship.foreign_key_name)} columns: ${JSON.stringify(relationship.columns)} + isOneToOne: ${relationship.is_one_to_one} referencedRelation: ${JSON.stringify(relationship.referenced_relation)} referencedColumns: ${JSON.stringify(relationship.referenced_columns)} }` @@ -235,6 +236,7 @@ export interface Database { (relationship) => `{ foreignKeyName: ${JSON.stringify(relationship.foreign_key_name)} columns: ${JSON.stringify(relationship.columns)} + isOneToOne: ${relationship.is_one_to_one} referencedRelation: ${JSON.stringify(relationship.referenced_relation)} referencedColumns: ${JSON.stringify(relationship.referenced_columns)} }` diff --git a/test/db/00-init.sql b/test/db/00-init.sql index ec1d9834..33a85958 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -96,3 +96,8 @@ $$; create or replace function public.polymorphic_function(text) returns void language sql as ''; create or replace function public.polymorphic_function(bool) returns void language sql as ''; + +create table user_details ( + user_id int8 references users(id) primary key, + details text +); diff --git a/test/lib/tables.ts b/test/lib/tables.ts index ae3dfe97..ba86cb28 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -121,6 +121,15 @@ test('list', async () => { "target_table_name": "users", "target_table_schema": "public", }, + { + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", + "source_schema": "public", + "source_table_name": "user_details", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, ], "replica_identity": "DEFAULT", "rls_enabled": false, @@ -176,6 +185,15 @@ test('list without columns', async () => { "target_table_name": "users", "target_table_schema": "public", }, + { + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", + "source_schema": "public", + "source_table_name": "user_details", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, ], "replica_identity": "DEFAULT", "rls_enabled": false, diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 43a10f3d..c9c04a0b 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -61,6 +61,7 @@ test('typegen', async () => { { foreignKeyName: "memes_category_fkey" columns: ["category"] + isOneToOne: false referencedRelation: "category" referencedColumns: ["id"] } @@ -87,12 +88,44 @@ test('typegen', async () => { { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false referencedRelation: "users" referencedColumns: ["id"] }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + } + ] + } + user_details: { + Row: { + details: string | null + user_id: number + } + Insert: { + details?: string | null + user_id: number + } + Update: { + details?: string | null + user_id?: number + } + Relationships: [ + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true referencedRelation: "users_view" referencedColumns: ["id"] } @@ -149,12 +182,14 @@ test('typegen', async () => { { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false referencedRelation: "users" referencedColumns: ["id"] }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false referencedRelation: "users_view" referencedColumns: ["id"] } @@ -180,12 +215,14 @@ test('typegen', async () => { { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false referencedRelation: "users" referencedColumns: ["id"] }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] + isOneToOne: false referencedRelation: "users_view" referencedColumns: ["id"] }