Skip to content

Commit d625118

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

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
@@ -1481,64 +1481,142 @@ test('typegen: go', async () => {
14811481
expect(body).toMatchInlineSnapshot(`
14821482
"package database
14831483
1484-
type PublicUsers struct {
1485-
Id int64 \`json:"id"\`
1486-
Name string \`json:"name"\`
1487-
Status string \`json:"status"\`
1488-
}
1484+
import "database/sql"
14891485
1490-
type PublicTodos struct {
1491-
Details string \`json:"details"\`
1492-
Id int64 \`json:"id"\`
1493-
UserId int64 \`json:"user-id"\`
1494-
}
1486+
type PublicUsersSelect struct {
1487+
Id int64 \`json:"id"\`
1488+
Name sql.NullString \`json:"name"\`
1489+
Status sql.NullString \`json:"status"\`
1490+
}
14951491
1496-
type PublicUsersAudit struct {
1497-
CreatedAt string \`json:"created_at"\`
1498-
Id int64 \`json:"id"\`
1499-
PreviousValue interface{} \`json:"previous_value"\`
1500-
UserId int64 \`json:"user_id"\`
1501-
}
1492+
type PublicUsersInsert struct {
1493+
Id sql.NullInt64 \`json:"id"\`
1494+
Name sql.NullString \`json:"name"\`
1495+
Status sql.NullString \`json:"status"\`
1496+
}
15021497
1503-
type PublicUserDetails struct {
1504-
Details string \`json:"details"\`
1505-
UserId int64 \`json:"user_id"\`
1506-
}
1498+
type PublicUsersUpdate struct {
1499+
Id sql.NullInt64 \`json:"id"\`
1500+
Name sql.NullString \`json:"name"\`
1501+
Status sql.NullString \`json:"status"\`
1502+
}
15071503
1508-
type PublicCategory struct {
1509-
Id int32 \`json:"id"\`
1510-
Name string \`json:"name"\`
1511-
}
1504+
type PublicTodosSelect struct {
1505+
Details sql.NullString \`json:"details"\`
1506+
Id int64 \`json:"id"\`
1507+
UserId int64 \`json:"user-id"\`
1508+
}
15121509
1513-
type PublicMemes struct {
1514-
Category int32 \`json:"category"\`
1515-
CreatedAt string \`json:"created_at"\`
1516-
Id int32 \`json:"id"\`
1517-
Metadata interface{} \`json:"metadata"\`
1518-
Name string \`json:"name"\`
1519-
Status string \`json:"status"\`
1520-
}
1510+
type PublicTodosInsert struct {
1511+
Details sql.NullString \`json:"details"\`
1512+
Id sql.NullInt64 \`json:"id"\`
1513+
UserId int64 \`json:"user-id"\`
1514+
}
15211515
1522-
type PublicTodosView struct {
1523-
Details string \`json:"details"\`
1524-
Id int64 \`json:"id"\`
1525-
UserId int64 \`json:"user-id"\`
1526-
}
1516+
type PublicTodosUpdate struct {
1517+
Details sql.NullString \`json:"details"\`
1518+
Id sql.NullInt64 \`json:"id"\`
1519+
UserId sql.NullInt64 \`json:"user-id"\`
1520+
}
15271521
1528-
type PublicUsersView struct {
1529-
Id int64 \`json:"id"\`
1530-
Name string \`json:"name"\`
1531-
Status string \`json:"status"\`
1532-
}
1522+
type PublicUsersAuditSelect struct {
1523+
CreatedAt sql.NullString \`json:"created_at"\`
1524+
Id int64 \`json:"id"\`
1525+
PreviousValue interface{} \`json:"previous_value"\`
1526+
UserId sql.NullInt64 \`json:"user_id"\`
1527+
}
15331528
1534-
type PublicAView struct {
1535-
Id int64 \`json:"id"\`
1536-
}
1529+
type PublicUsersAuditInsert struct {
1530+
CreatedAt sql.NullString \`json:"created_at"\`
1531+
Id sql.NullInt64 \`json:"id"\`
1532+
PreviousValue interface{} \`json:"previous_value"\`
1533+
UserId sql.NullInt64 \`json:"user_id"\`
1534+
}
1535+
1536+
type PublicUsersAuditUpdate struct {
1537+
CreatedAt sql.NullString \`json:"created_at"\`
1538+
Id sql.NullInt64 \`json:"id"\`
1539+
PreviousValue interface{} \`json:"previous_value"\`
1540+
UserId sql.NullInt64 \`json:"user_id"\`
1541+
}
1542+
1543+
type PublicUserDetailsSelect struct {
1544+
Details sql.NullString \`json:"details"\`
1545+
UserId int64 \`json:"user_id"\`
1546+
}
1547+
1548+
type PublicUserDetailsInsert struct {
1549+
Details sql.NullString \`json:"details"\`
1550+
UserId int64 \`json:"user_id"\`
1551+
}
1552+
1553+
type PublicUserDetailsUpdate struct {
1554+
Details sql.NullString \`json:"details"\`
1555+
UserId sql.NullInt64 \`json:"user_id"\`
1556+
}
1557+
1558+
type PublicCategorySelect struct {
1559+
Id int32 \`json:"id"\`
1560+
Name string \`json:"name"\`
1561+
}
1562+
1563+
type PublicCategoryInsert struct {
1564+
Id sql.NullInt32 \`json:"id"\`
1565+
Name string \`json:"name"\`
1566+
}
1567+
1568+
type PublicCategoryUpdate struct {
1569+
Id sql.NullInt32 \`json:"id"\`
1570+
Name sql.NullString \`json:"name"\`
1571+
}
1572+
1573+
type PublicMemesSelect struct {
1574+
Category sql.NullInt32 \`json:"category"\`
1575+
CreatedAt string \`json:"created_at"\`
1576+
Id int32 \`json:"id"\`
1577+
Metadata interface{} \`json:"metadata"\`
1578+
Name string \`json:"name"\`
1579+
Status sql.NullString \`json:"status"\`
1580+
}
1581+
1582+
type PublicMemesInsert struct {
1583+
Category sql.NullInt32 \`json:"category"\`
1584+
CreatedAt string \`json:"created_at"\`
1585+
Id sql.NullInt32 \`json:"id"\`
1586+
Metadata interface{} \`json:"metadata"\`
1587+
Name string \`json:"name"\`
1588+
Status sql.NullString \`json:"status"\`
1589+
}
1590+
1591+
type PublicMemesUpdate struct {
1592+
Category sql.NullInt32 \`json:"category"\`
1593+
CreatedAt sql.NullString \`json:"created_at"\`
1594+
Id sql.NullInt32 \`json:"id"\`
1595+
Metadata interface{} \`json:"metadata"\`
1596+
Name sql.NullString \`json:"name"\`
1597+
Status sql.NullString \`json:"status"\`
1598+
}
1599+
1600+
type PublicTodosViewSelect struct {
1601+
Details sql.NullString \`json:"details"\`
1602+
Id sql.NullInt64 \`json:"id"\`
1603+
UserId sql.NullInt64 \`json:"user-id"\`
1604+
}
1605+
1606+
type PublicUsersViewSelect struct {
1607+
Id sql.NullInt64 \`json:"id"\`
1608+
Name sql.NullString \`json:"name"\`
1609+
Status sql.NullString \`json:"status"\`
1610+
}
1611+
1612+
type PublicAViewSelect struct {
1613+
Id sql.NullInt64 \`json:"id"\`
1614+
}
15371615
1538-
type PublicTodosMatview struct {
1539-
Details string \`json:"details"\`
1540-
Id int64 \`json:"id"\`
1541-
UserId int64 \`json:"user-id"\`
1542-
}"
1616+
type PublicTodosMatviewSelect struct {
1617+
Details sql.NullString \`json:"details"\`
1618+
Id sql.NullInt64 \`json:"id"\`
1619+
UserId sql.NullInt64 \`json:"user-id"\`
1620+
}"
15431621
`)
15441622
})

0 commit comments

Comments
 (0)