Skip to content

Commit c652162

Browse files
feat(typegen): separate go types by operation and add nullable types
1 parent e1e0195 commit c652162

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
@@ -1380,64 +1380,142 @@ test('typegen: go', async () => {
13801380
expect(body).toMatchInlineSnapshot(`
13811381
"package database
13821382
1383-
type PublicUsers struct {
1384-
Id int64 \`json:"id"\`
1385-
Name string \`json:"name"\`
1386-
Status string \`json:"status"\`
1387-
}
1383+
import "database/sql"
13881384
1389-
type PublicTodos struct {
1390-
Details string \`json:"details"\`
1391-
Id int64 \`json:"id"\`
1392-
UserId int64 \`json:"user-id"\`
1393-
}
1385+
type PublicUsersSelect struct {
1386+
Id int64 \`json:"id"\`
1387+
Name sql.NullString \`json:"name"\`
1388+
Status sql.NullString \`json:"status"\`
1389+
}
13941390
1395-
type PublicUsersAudit struct {
1396-
CreatedAt string \`json:"created_at"\`
1397-
Id int64 \`json:"id"\`
1398-
PreviousValue interface{} \`json:"previous_value"\`
1399-
UserId int64 \`json:"user_id"\`
1400-
}
1391+
type PublicUsersInsert struct {
1392+
Id sql.NullInt64 \`json:"id"\`
1393+
Name sql.NullString \`json:"name"\`
1394+
Status sql.NullString \`json:"status"\`
1395+
}
14011396
1402-
type PublicUserDetails struct {
1403-
Details string \`json:"details"\`
1404-
UserId int64 \`json:"user_id"\`
1405-
}
1397+
type PublicUsersUpdate struct {
1398+
Id sql.NullInt64 \`json:"id"\`
1399+
Name sql.NullString \`json:"name"\`
1400+
Status sql.NullString \`json:"status"\`
1401+
}
14061402
1407-
type PublicCategory struct {
1408-
Id int32 \`json:"id"\`
1409-
Name string \`json:"name"\`
1410-
}
1403+
type PublicTodosSelect struct {
1404+
Details sql.NullString \`json:"details"\`
1405+
Id int64 \`json:"id"\`
1406+
UserId int64 \`json:"user-id"\`
1407+
}
14111408
1412-
type PublicMemes struct {
1413-
Category int32 \`json:"category"\`
1414-
CreatedAt string \`json:"created_at"\`
1415-
Id int32 \`json:"id"\`
1416-
Metadata interface{} \`json:"metadata"\`
1417-
Name string \`json:"name"\`
1418-
Status string \`json:"status"\`
1419-
}
1409+
type PublicTodosInsert struct {
1410+
Details sql.NullString \`json:"details"\`
1411+
Id sql.NullInt64 \`json:"id"\`
1412+
UserId int64 \`json:"user-id"\`
1413+
}
14201414
1421-
type PublicTodosView struct {
1422-
Details string \`json:"details"\`
1423-
Id int64 \`json:"id"\`
1424-
UserId int64 \`json:"user-id"\`
1425-
}
1415+
type PublicTodosUpdate struct {
1416+
Details sql.NullString \`json:"details"\`
1417+
Id sql.NullInt64 \`json:"id"\`
1418+
UserId sql.NullInt64 \`json:"user-id"\`
1419+
}
14261420
1427-
type PublicUsersView struct {
1428-
Id int64 \`json:"id"\`
1429-
Name string \`json:"name"\`
1430-
Status string \`json:"status"\`
1431-
}
1421+
type PublicUsersAuditSelect struct {
1422+
CreatedAt sql.NullString \`json:"created_at"\`
1423+
Id int64 \`json:"id"\`
1424+
PreviousValue interface{} \`json:"previous_value"\`
1425+
UserId sql.NullInt64 \`json:"user_id"\`
1426+
}
14321427
1433-
type PublicAView struct {
1434-
Id int64 \`json:"id"\`
1435-
}
1428+
type PublicUsersAuditInsert struct {
1429+
CreatedAt sql.NullString \`json:"created_at"\`
1430+
Id sql.NullInt64 \`json:"id"\`
1431+
PreviousValue interface{} \`json:"previous_value"\`
1432+
UserId sql.NullInt64 \`json:"user_id"\`
1433+
}
1434+
1435+
type PublicUsersAuditUpdate struct {
1436+
CreatedAt sql.NullString \`json:"created_at"\`
1437+
Id sql.NullInt64 \`json:"id"\`
1438+
PreviousValue interface{} \`json:"previous_value"\`
1439+
UserId sql.NullInt64 \`json:"user_id"\`
1440+
}
1441+
1442+
type PublicUserDetailsSelect struct {
1443+
Details sql.NullString \`json:"details"\`
1444+
UserId int64 \`json:"user_id"\`
1445+
}
1446+
1447+
type PublicUserDetailsInsert struct {
1448+
Details sql.NullString \`json:"details"\`
1449+
UserId int64 \`json:"user_id"\`
1450+
}
1451+
1452+
type PublicUserDetailsUpdate struct {
1453+
Details sql.NullString \`json:"details"\`
1454+
UserId sql.NullInt64 \`json:"user_id"\`
1455+
}
1456+
1457+
type PublicCategorySelect struct {
1458+
Id int32 \`json:"id"\`
1459+
Name string \`json:"name"\`
1460+
}
1461+
1462+
type PublicCategoryInsert struct {
1463+
Id sql.NullInt32 \`json:"id"\`
1464+
Name string \`json:"name"\`
1465+
}
1466+
1467+
type PublicCategoryUpdate struct {
1468+
Id sql.NullInt32 \`json:"id"\`
1469+
Name sql.NullString \`json:"name"\`
1470+
}
1471+
1472+
type PublicMemesSelect struct {
1473+
Category sql.NullInt32 \`json:"category"\`
1474+
CreatedAt string \`json:"created_at"\`
1475+
Id int32 \`json:"id"\`
1476+
Metadata interface{} \`json:"metadata"\`
1477+
Name string \`json:"name"\`
1478+
Status sql.NullString \`json:"status"\`
1479+
}
1480+
1481+
type PublicMemesInsert struct {
1482+
Category sql.NullInt32 \`json:"category"\`
1483+
CreatedAt string \`json:"created_at"\`
1484+
Id sql.NullInt32 \`json:"id"\`
1485+
Metadata interface{} \`json:"metadata"\`
1486+
Name string \`json:"name"\`
1487+
Status sql.NullString \`json:"status"\`
1488+
}
1489+
1490+
type PublicMemesUpdate struct {
1491+
Category sql.NullInt32 \`json:"category"\`
1492+
CreatedAt sql.NullString \`json:"created_at"\`
1493+
Id sql.NullInt32 \`json:"id"\`
1494+
Metadata interface{} \`json:"metadata"\`
1495+
Name sql.NullString \`json:"name"\`
1496+
Status sql.NullString \`json:"status"\`
1497+
}
1498+
1499+
type PublicTodosViewSelect struct {
1500+
Details sql.NullString \`json:"details"\`
1501+
Id sql.NullInt64 \`json:"id"\`
1502+
UserId sql.NullInt64 \`json:"user-id"\`
1503+
}
1504+
1505+
type PublicUsersViewSelect struct {
1506+
Id sql.NullInt64 \`json:"id"\`
1507+
Name sql.NullString \`json:"name"\`
1508+
Status sql.NullString \`json:"status"\`
1509+
}
1510+
1511+
type PublicAViewSelect struct {
1512+
Id sql.NullInt64 \`json:"id"\`
1513+
}
14361514
1437-
type PublicTodosMatview struct {
1438-
Details string \`json:"details"\`
1439-
Id int64 \`json:"id"\`
1440-
UserId int64 \`json:"user-id"\`
1441-
}"
1515+
type PublicTodosMatviewSelect struct {
1516+
Details sql.NullString \`json:"details"\`
1517+
Id sql.NullInt64 \`json:"id"\`
1518+
UserId sql.NullInt64 \`json:"user-id"\`
1519+
}"
14421520
`)
14431521
})

0 commit comments

Comments
 (0)