From be0fff95f42412dd95ed0ac6daeb87e30e99c574 Mon Sep 17 00:00:00 2001 From: im-jersh <4296435+im-jersh@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:14:30 -0700 Subject: [PATCH 1/3] Mapped pg numeric type to Swift Decimal type for the Swift type generator --- src/server/templates/swift.ts | 2 + test/db/00-init.sql | 3 +- test/lib/tables.ts | 22 +- test/server/query.ts | 2 + test/server/typegen.ts | 436 +++++++++++++++++++--------------- 5 files changed, 270 insertions(+), 195 deletions(-) diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts index e596610e..235cb093 100644 --- a/src/server/templates/swift.ts +++ b/src/server/templates/swift.ts @@ -309,6 +309,8 @@ const pgTypeToSwiftType = ( swiftType = 'Float' } else if (pgType === 'float8') { swiftType = 'Double' + } else if (pgType === 'numeric') { + swiftType = 'Decimal' } else if (pgType === 'uuid') { swiftType = 'UUID' } else if ( diff --git a/test/db/00-init.sql b/test/db/00-init.sql index 00c6a472..3551a4e7 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -8,7 +8,8 @@ CREATE TYPE composite_type_with_array_attribute AS (my_text_array text[]); CREATE TABLE public.users ( id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text, - status user_status DEFAULT 'ACTIVE' + status user_status DEFAULT 'ACTIVE', + decimal numeric ); INSERT INTO public.users (name) diff --git a/test/lib/tables.ts b/test/lib/tables.ts index c4c934e7..6fcb4c67 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -37,7 +37,7 @@ test('list', async () => { id: expect.any(Number), live_rows_estimate: expect.any(Number), size: expect.any(String), - }, + }, ` { "bytes": Any, @@ -78,6 +78,24 @@ test('list', async () => { "schema": "public", "table": "users", }, + { + "check": null, + "comment": null, + "data_type": "numeric", + "default_value": null, + "enums": [], + "format": "numeric", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "decimal", + "ordinal_position": 4, + "schema": "public", + "table": "users", + }, { "check": null, "comment": null, @@ -139,7 +157,7 @@ test('list', async () => { "size": Any, } ` - ) +) }) test('list without columns', async () => { diff --git a/test/server/query.ts b/test/server/query.ts index 2b4bc2ba..8a9d6076 100644 --- a/test/server/query.ts +++ b/test/server/query.ts @@ -10,11 +10,13 @@ test('query', async () => { expect(res.json()).toMatchInlineSnapshot(` [ { + "decimal": null, "id": 1, "name": "Joe Bloggs", "status": "ACTIVE", }, { + "decimal": null, "id": 2, "name": "Jane Doe", "status": "ACTIVE", diff --git a/test/server/typegen.ts b/test/server/typegen.ts index fa47cbec..87996416 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -221,16 +221,19 @@ test('typegen: typescript', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -362,16 +365,19 @@ test('typegen: typescript', async () => { } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -412,6 +418,7 @@ test('typegen: typescript', async () => { function_returning_row: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -420,6 +427,7 @@ test('typegen: typescript', async () => { function_returning_set_of_rows: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -856,16 +864,19 @@ test('typegen w/ one-to-one relationships', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1007,16 +1018,19 @@ test('typegen w/ one-to-one relationships', async () => { } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1057,6 +1071,7 @@ test('typegen w/ one-to-one relationships', async () => { function_returning_row: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -1065,6 +1080,7 @@ test('typegen w/ one-to-one relationships', async () => { function_returning_set_of_rows: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -1501,16 +1517,19 @@ test('typegen: typescript w/ one-to-one relationships', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1652,16 +1671,19 @@ test('typegen: typescript w/ one-to-one relationships', async () => { } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1702,6 +1724,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { function_returning_row: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -1710,6 +1733,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { function_returning_set_of_rows: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -2151,16 +2175,19 @@ test('typegen: typescript w/ postgrestVersion', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -2302,16 +2329,19 @@ test('typegen: typescript w/ postgrestVersion', async () => { } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -2352,6 +2382,7 @@ test('typegen: typescript w/ postgrestVersion', async () => { function_returning_row: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -2360,6 +2391,7 @@ test('typegen: typescript w/ postgrestVersion', async () => { function_returning_set_of_rows: { Args: Record Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null @@ -2566,198 +2598,202 @@ test('typegen: go', async () => { expect(body).toMatchInlineSnapshot(` "package database -type PublicUsersSelect struct { - Id int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicUsersInsert struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicUsersUpdate struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicTodosSelect struct { - Details *string \`json:"details"\` - Id int64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosInsert struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosUpdate struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicUsersAuditSelect struct { - CreatedAt *string \`json:"created_at"\` - Id int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUsersAuditInsert struct { - CreatedAt *string \`json:"created_at"\` - Id *int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUsersAuditUpdate struct { - CreatedAt *string \`json:"created_at"\` - Id *int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUserDetailsSelect struct { - Details *string \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsInsert struct { - Details *string \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsUpdate struct { - Details *string \`json:"details"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicEmptySelect struct { - -} - -type PublicEmptyInsert struct { - -} - -type PublicEmptyUpdate struct { - -} - -type PublicTableWithOtherTablesRowTypeSelect struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeInsert struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeUpdate struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdSelect struct { - Name *string \`json:"name"\` - OtherId int64 \`json:"other_id"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdInsert struct { - Name *string \`json:"name"\` - OtherId *int64 \`json:"other_id"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { - Name *string \`json:"name"\` - OtherId *int64 \`json:"other_id"\` -} - -type PublicCategorySelect struct { - Id int32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryInsert struct { - Id *int32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryUpdate struct { - Id *int32 \`json:"id"\` - Name *string \`json:"name"\` -} - -type PublicMemesSelect struct { - Category *int32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicMemesInsert struct { - Category *int32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id *int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicMemesUpdate struct { - Category *int32 \`json:"category"\` - CreatedAt *string \`json:"created_at"\` - Id *int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicTodosViewSelect struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicUsersViewSelect struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicAViewSelect struct { - Id *int64 \`json:"id"\` -} - -type PublicUsersViewWithMultipleRefsToUsersSelect struct { - InitialId *int64 \`json:"initial_id"\` - InitialName *string \`json:"initial_name"\` - SecondId *int64 \`json:"second_id"\` - SecondName *string \`json:"second_name"\` -} - -type PublicTodosMatviewSelect struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicCompositeTypeWithArrayAttribute struct { - MyTextArray interface{} \`json:"my_text_array"\` -} - -type PublicCompositeTypeWithRecordAttribute struct { - Todo interface{} \`json:"todo"\` -}" + type PublicUsersSelect struct { + Decimal *float64 \`json:"decimal"\` + Id int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicUsersInsert struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicUsersUpdate struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicTodosSelect struct { + Details *string \`json:"details"\` + Id int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosInsert struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosUpdate struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersAuditSelect struct { + CreatedAt *string \`json:"created_at"\` + Id int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditInsert struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditUpdate struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUserDetailsSelect struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsInsert struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsUpdate struct { + Details *string \`json:"details"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicEmptySelect struct { + + } + + type PublicEmptyInsert struct { + + } + + type PublicEmptyUpdate struct { + + } + + type PublicTableWithOtherTablesRowTypeSelect struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeInsert struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeUpdate struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdSelect struct { + Name *string \`json:"name"\` + OtherId int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdInsert struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicCategorySelect struct { + Id int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryInsert struct { + Id *int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryUpdate struct { + Id *int32 \`json:"id"\` + Name *string \`json:"name"\` + } + + type PublicMemesSelect struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesInsert struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesUpdate struct { + Category *int32 \`json:"category"\` + CreatedAt *string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicTodosViewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersViewSelect struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicAViewSelect struct { + Id *int64 \`json:"id"\` + } + + type PublicUsersViewWithMultipleRefsToUsersSelect struct { + InitialId *int64 \`json:"initial_id"\` + InitialName *string \`json:"initial_name"\` + SecondId *int64 \`json:"second_id"\` + SecondName *string \`json:"second_name"\` + } + + type PublicTodosMatviewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicCompositeTypeWithArrayAttribute struct { + MyTextArray interface{} \`json:"my_text_array"\` + } + + type PublicCompositeTypeWithRecordAttribute struct { + Todo interface{} \`json:"todo"\` + }" `) }) @@ -2991,30 +3027,36 @@ test('typegen: swift', async () => { } } internal struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64 internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } internal struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } internal struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3083,10 +3125,12 @@ test('typegen: swift', async () => { } } internal struct UsersViewSelect: Codable, Hashable, Sendable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3354,30 +3398,36 @@ test('typegen: swift w/ public access control', async () => { } } public struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64 public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } public struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } public struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3446,10 +3496,12 @@ test('typegen: swift w/ public access control', async () => { } } public struct UsersViewSelect: Codable, Hashable, Sendable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" From 2db2ddaed2b7dd2b14cf282fc277ecf5216717f9 Mon Sep 17 00:00:00 2001 From: JERSH <4296435+im-jersh@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:39:06 -0700 Subject: [PATCH 2/3] Including decimal alias for postgres numeric type in the Swift type generator --- src/server/templates/swift.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts index 235cb093..7bb41207 100644 --- a/src/server/templates/swift.ts +++ b/src/server/templates/swift.ts @@ -309,7 +309,7 @@ const pgTypeToSwiftType = ( swiftType = 'Float' } else if (pgType === 'float8') { swiftType = 'Double' - } else if (pgType === 'numeric') { + } else if (['numeric', 'decimal'].includes(pgType)) { swiftType = 'Decimal' } else if (pgType === 'uuid') { swiftType = 'UUID' From b6e8cfe63a74207c2e032f9a5aa4a4567c0d4eff Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Mon, 28 Jul 2025 23:44:51 +0900 Subject: [PATCH 3/3] chore: prettier --- test/lib/tables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lib/tables.ts b/test/lib/tables.ts index 6fcb4c67..c35546b8 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -37,7 +37,7 @@ test('list', async () => { id: expect.any(Number), live_rows_estimate: expect.any(Number), size: expect.any(String), - }, + }, ` { "bytes": Any, @@ -157,7 +157,7 @@ test('list', async () => { "size": Any, } ` -) + ) }) test('list without columns', async () => {