Skip to content

Commit 936a6d7

Browse files
feat(typegen): separate go types by operation and add nullable types
1 parent 720b2a0 commit 936a6d7

File tree

2 files changed

+210
-79
lines changed

2 files changed

+210
-79
lines changed

src/server/templates/go.ts

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import type {
1111
} from '../../lib/index.js'
1212
import type { GeneratorMetadata } from '../../lib/generators.js'
1313

14+
type Operation = 'Select' | 'Insert' | 'Update'
15+
1416
export const apply = ({
1517
schemas,
1618
tables,
@@ -32,35 +34,40 @@ export const apply = ({
3234
let output = `
3335
package database
3436
37+
import "database/sql"
38+
3539
${tables
36-
.map((table) =>
37-
generateTableStruct(
40+
.flatMap((table) =>
41+
generateTableStructsForOperations(
3842
schemas.find((schema) => schema.name === table.schema)!,
3943
table,
4044
columnsByTableId[table.id],
41-
types
45+
types,
46+
['Select', 'Insert', 'Update']
4247
)
4348
)
4449
.join('\n\n')}
4550
4651
${views
47-
.map((view) =>
48-
generateTableStruct(
52+
.flatMap((view) =>
53+
generateTableStructsForOperations(
4954
schemas.find((schema) => schema.name === view.schema)!,
5055
view,
5156
columnsByTableId[view.id],
52-
types
57+
types,
58+
['Select']
5359
)
5460
)
5561
.join('\n\n')}
5662
5763
${materializedViews
58-
.map((materializedView) =>
59-
generateTableStruct(
64+
.flatMap((materializedView) =>
65+
generateTableStructsForOperations(
6066
schemas.find((schema) => schema.name === materializedView.schema)!,
6167
materializedView,
6268
columnsByTableId[materializedView.id],
63-
types
69+
types,
70+
['Select']
6471
)
6572
)
6673
.join('\n\n')}
@@ -101,7 +108,8 @@ function generateTableStruct(
101108
schema: PostgresSchema,
102109
table: PostgresTable | PostgresView | PostgresMaterializedView,
103110
columns: PostgresColumn[],
104-
types: PostgresType[]
111+
types: PostgresType[],
112+
operation: Operation
105113
): string {
106114
// Storing columns as a tuple of [formattedName, type, name] rather than creating the string
107115
// representation of the line allows us to pre-format the entries. Go formats
@@ -111,11 +119,22 @@ function generateTableStruct(
111119
// id int `json:"id"`
112120
// name string `json:"name"`
113121
// }
114-
const columnEntries: [string, string, string][] = columns.map((column) => [
115-
formatForGoTypeName(column.name),
116-
pgTypeToGoType(column.format, types),
117-
column.name,
118-
])
122+
const columnEntries: [string, string, string][] = columns.map((column) => {
123+
let nullable: boolean
124+
if (operation === 'Insert') {
125+
nullable =
126+
column.is_nullable || column.is_identity || column.is_generated || !!column.default_value
127+
} else if (operation === 'Update') {
128+
nullable = true
129+
} else {
130+
nullable = column.is_nullable
131+
}
132+
return [
133+
formatForGoTypeName(column.name),
134+
pgTypeToGoType(column.format, nullable, types),
135+
column.name,
136+
]
137+
})
119138

120139
const [maxFormattedNameLength, maxTypeLength] = columnEntries.reduce(
121140
([maxFormattedName, maxType], [formattedName, type]) => {
@@ -133,12 +152,24 @@ function generateTableStruct(
133152
})
134153

135154
return `
136-
type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(table.name)} struct {
155+
type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(table.name)}${operation} struct {
137156
${formattedColumnEntries.join('\n')}
138157
}
139158
`.trim()
140159
}
141160

161+
function generateTableStructsForOperations(
162+
schema: PostgresSchema,
163+
table: PostgresTable | PostgresView | PostgresMaterializedView,
164+
columns: PostgresColumn[],
165+
types: PostgresType[],
166+
operations: Operation[]
167+
): string[] {
168+
return operations.map((operation) =>
169+
generateTableStruct(schema, table, columns, types, operation)
170+
)
171+
}
172+
142173
function generateCompositeTypeStruct(
143174
schema: PostgresSchema,
144175
type: PostgresType,
@@ -158,7 +189,7 @@ function generateCompositeTypeStruct(
158189
const attributeEntries: [string, string, string][] = typeWithRetrievedAttributes.attributes.map(
159190
(attribute) => [
160191
formatForGoTypeName(attribute.name),
161-
pgTypeToGoType(attribute.type!.format),
192+
pgTypeToGoType(attribute.type!.format, false),
162193
attribute.name,
163194
]
164195
)
@@ -187,7 +218,7 @@ ${formattedAttributeEntries.join('\n')}
187218

188219
// Note: the type map uses `interface{ } `, not `any`, to remain compatible with
189220
// older versions of Go.
190-
const GO_TYPE_MAP: Record<string, string> = {
221+
const GO_TYPE_MAP = {
191222
// Bool
192223
bool: 'bool',
193224

@@ -234,24 +265,40 @@ const GO_TYPE_MAP: Record<string, string> = {
234265
// Misc
235266
void: 'interface{}',
236267
record: 'map[string]interface{}',
237-
}
268+
} as const
238269

239-
function pgTypeToGoType(pgType: string, types: PostgresType[] = []): string {
240-
let goType = GO_TYPE_MAP[pgType]
241-
if (goType) {
242-
return goType
243-
}
270+
type GoType = (typeof GO_TYPE_MAP)[keyof typeof GO_TYPE_MAP]
244271

245-
// Arrays
246-
if (pgType.startsWith('_')) {
247-
const innerType = pgTypeToGoType(pgType.slice(1))
248-
return `[]${innerType} `
272+
const GO_NULLABLE_TYPE_MAP: Record<GoType, string> = {
273+
string: 'sql.NullString',
274+
bool: 'sql.NullBool',
275+
int16: 'sql.NullInt32',
276+
int32: 'sql.NullInt32',
277+
int64: 'sql.NullInt64',
278+
float32: 'sql.NullFloat64',
279+
float64: 'sql.NullFloat64',
280+
'[]byte': '[]byte',
281+
'interface{}': 'interface{}',
282+
'map[string]interface{}': 'map[string]interface{}',
283+
}
284+
285+
function pgTypeToGoType(pgType: string, nullable: boolean, types: PostgresType[] = []): string {
286+
let goType: GoType | undefined = undefined
287+
if (pgType in GO_TYPE_MAP) {
288+
goType = GO_TYPE_MAP[pgType as keyof typeof GO_TYPE_MAP]
249289
}
250290

251291
// Enums
252292
const enumType = types.find((type) => type.name === pgType && type.enums.length > 0)
253293
if (enumType) {
254-
return 'string'
294+
goType = 'string'
295+
}
296+
297+
if (goType) {
298+
if (nullable) {
299+
return GO_NULLABLE_TYPE_MAP[goType]
300+
}
301+
return goType
255302
}
256303

257304
// Composite types
@@ -262,6 +309,12 @@ function pgTypeToGoType(pgType: string, types: PostgresType[] = []): string {
262309
return 'map[string]interface{}'
263310
}
264311

312+
// Arrays
313+
if (pgType.startsWith('_')) {
314+
const innerType = pgTypeToGoType(pgType.slice(1), nullable)
315+
return `[]${innerType} `
316+
}
317+
265318
// Fallback
266319
return 'interface{}'
267320
}

test/server/typegen.ts

Lines changed: 128 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,64 +1445,142 @@ test('typegen: go', async () => {
14451445
expect(body).toMatchInlineSnapshot(`
14461446
"package database
14471447
1448-
type PublicUsers struct {
1449-
Id int64 \`json:"id"\`
1450-
Name string \`json:"name"\`
1451-
Status string \`json:"status"\`
1452-
}
1448+
import "database/sql"
14531449
1454-
type PublicTodos struct {
1455-
Details string \`json:"details"\`
1456-
Id int64 \`json:"id"\`
1457-
UserId int64 \`json:"user-id"\`
1458-
}
1450+
type PublicUsersSelect struct {
1451+
Id int64 \`json:"id"\`
1452+
Name sql.NullString \`json:"name"\`
1453+
Status sql.NullString \`json:"status"\`
1454+
}
14591455
1460-
type PublicUsersAudit struct {
1461-
CreatedAt string \`json:"created_at"\`
1462-
Id int64 \`json:"id"\`
1463-
PreviousValue interface{} \`json:"previous_value"\`
1464-
UserId int64 \`json:"user_id"\`
1465-
}
1456+
type PublicUsersInsert struct {
1457+
Id sql.NullInt64 \`json:"id"\`
1458+
Name sql.NullString \`json:"name"\`
1459+
Status sql.NullString \`json:"status"\`
1460+
}
14661461
1467-
type PublicUserDetails struct {
1468-
Details string \`json:"details"\`
1469-
UserId int64 \`json:"user_id"\`
1470-
}
1462+
type PublicUsersUpdate struct {
1463+
Id sql.NullInt64 \`json:"id"\`
1464+
Name sql.NullString \`json:"name"\`
1465+
Status sql.NullString \`json:"status"\`
1466+
}
14711467
1472-
type PublicCategory struct {
1473-
Id int32 \`json:"id"\`
1474-
Name string \`json:"name"\`
1475-
}
1468+
type PublicTodosSelect struct {
1469+
Details sql.NullString \`json:"details"\`
1470+
Id int64 \`json:"id"\`
1471+
UserId int64 \`json:"user-id"\`
1472+
}
14761473
1477-
type PublicMemes struct {
1478-
Category int32 \`json:"category"\`
1479-
CreatedAt string \`json:"created_at"\`
1480-
Id int32 \`json:"id"\`
1481-
Metadata interface{} \`json:"metadata"\`
1482-
Name string \`json:"name"\`
1483-
Status string \`json:"status"\`
1484-
}
1474+
type PublicTodosInsert struct {
1475+
Details sql.NullString \`json:"details"\`
1476+
Id sql.NullInt64 \`json:"id"\`
1477+
UserId int64 \`json:"user-id"\`
1478+
}
14851479
1486-
type PublicTodosView struct {
1487-
Details string \`json:"details"\`
1488-
Id int64 \`json:"id"\`
1489-
UserId int64 \`json:"user-id"\`
1490-
}
1480+
type PublicTodosUpdate struct {
1481+
Details sql.NullString \`json:"details"\`
1482+
Id sql.NullInt64 \`json:"id"\`
1483+
UserId sql.NullInt64 \`json:"user-id"\`
1484+
}
14911485
1492-
type PublicUsersView struct {
1493-
Id int64 \`json:"id"\`
1494-
Name string \`json:"name"\`
1495-
Status string \`json:"status"\`
1496-
}
1486+
type PublicUsersAuditSelect struct {
1487+
CreatedAt sql.NullString \`json:"created_at"\`
1488+
Id int64 \`json:"id"\`
1489+
PreviousValue interface{} \`json:"previous_value"\`
1490+
UserId sql.NullInt64 \`json:"user_id"\`
1491+
}
14971492
1498-
type PublicAView struct {
1499-
Id int64 \`json:"id"\`
1500-
}
1493+
type PublicUsersAuditInsert struct {
1494+
CreatedAt sql.NullString \`json:"created_at"\`
1495+
Id sql.NullInt64 \`json:"id"\`
1496+
PreviousValue interface{} \`json:"previous_value"\`
1497+
UserId sql.NullInt64 \`json:"user_id"\`
1498+
}
1499+
1500+
type PublicUsersAuditUpdate struct {
1501+
CreatedAt sql.NullString \`json:"created_at"\`
1502+
Id sql.NullInt64 \`json:"id"\`
1503+
PreviousValue interface{} \`json:"previous_value"\`
1504+
UserId sql.NullInt64 \`json:"user_id"\`
1505+
}
1506+
1507+
type PublicUserDetailsSelect struct {
1508+
Details sql.NullString \`json:"details"\`
1509+
UserId int64 \`json:"user_id"\`
1510+
}
1511+
1512+
type PublicUserDetailsInsert struct {
1513+
Details sql.NullString \`json:"details"\`
1514+
UserId int64 \`json:"user_id"\`
1515+
}
1516+
1517+
type PublicUserDetailsUpdate struct {
1518+
Details sql.NullString \`json:"details"\`
1519+
UserId sql.NullInt64 \`json:"user_id"\`
1520+
}
1521+
1522+
type PublicCategorySelect struct {
1523+
Id int32 \`json:"id"\`
1524+
Name string \`json:"name"\`
1525+
}
1526+
1527+
type PublicCategoryInsert struct {
1528+
Id sql.NullInt32 \`json:"id"\`
1529+
Name string \`json:"name"\`
1530+
}
1531+
1532+
type PublicCategoryUpdate struct {
1533+
Id sql.NullInt32 \`json:"id"\`
1534+
Name sql.NullString \`json:"name"\`
1535+
}
1536+
1537+
type PublicMemesSelect struct {
1538+
Category sql.NullInt32 \`json:"category"\`
1539+
CreatedAt string \`json:"created_at"\`
1540+
Id int32 \`json:"id"\`
1541+
Metadata interface{} \`json:"metadata"\`
1542+
Name string \`json:"name"\`
1543+
Status sql.NullString \`json:"status"\`
1544+
}
1545+
1546+
type PublicMemesInsert struct {
1547+
Category sql.NullInt32 \`json:"category"\`
1548+
CreatedAt string \`json:"created_at"\`
1549+
Id sql.NullInt32 \`json:"id"\`
1550+
Metadata interface{} \`json:"metadata"\`
1551+
Name string \`json:"name"\`
1552+
Status sql.NullString \`json:"status"\`
1553+
}
1554+
1555+
type PublicMemesUpdate struct {
1556+
Category sql.NullInt32 \`json:"category"\`
1557+
CreatedAt sql.NullString \`json:"created_at"\`
1558+
Id sql.NullInt32 \`json:"id"\`
1559+
Metadata interface{} \`json:"metadata"\`
1560+
Name sql.NullString \`json:"name"\`
1561+
Status sql.NullString \`json:"status"\`
1562+
}
1563+
1564+
type PublicTodosViewSelect struct {
1565+
Details sql.NullString \`json:"details"\`
1566+
Id sql.NullInt64 \`json:"id"\`
1567+
UserId sql.NullInt64 \`json:"user-id"\`
1568+
}
1569+
1570+
type PublicUsersViewSelect struct {
1571+
Id sql.NullInt64 \`json:"id"\`
1572+
Name sql.NullString \`json:"name"\`
1573+
Status sql.NullString \`json:"status"\`
1574+
}
1575+
1576+
type PublicAViewSelect struct {
1577+
Id sql.NullInt64 \`json:"id"\`
1578+
}
15011579
1502-
type PublicTodosMatview struct {
1503-
Details string \`json:"details"\`
1504-
Id int64 \`json:"id"\`
1505-
UserId int64 \`json:"user-id"\`
1506-
}"
1580+
type PublicTodosMatviewSelect struct {
1581+
Details sql.NullString \`json:"details"\`
1582+
Id sql.NullInt64 \`json:"id"\`
1583+
UserId sql.NullInt64 \`json:"user-id"\`
1584+
}"
15071585
`)
15081586
})

0 commit comments

Comments
 (0)