Skip to content

Commit e817d54

Browse files
committed
Add ClickHouse type mapping for Go code generation
Map ClickHouse types to Go types, update driver handling, and add import management for ClickHouse types.
1 parent 5112bab commit e817d54

5 files changed

Lines changed: 365 additions & 7 deletions

File tree

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package golang
2+
3+
import (
4+
"strings"
5+
6+
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
7+
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
8+
"github.com/sqlc-dev/sqlc/internal/plugin"
9+
)
10+
11+
func clickhouseType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
12+
columnType := sdk.DataType(col.Type)
13+
notNull := col.NotNull || col.IsArray
14+
15+
// Check if we're using the native ClickHouse driver
16+
driver := parseDriver(options.SqlPackage)
17+
useNativeDriver := driver.IsClickHouse()
18+
19+
switch columnType {
20+
21+
// String types
22+
case "string", "varchar", "text", "char", "fixedstring":
23+
if useNativeDriver {
24+
// Native driver uses *string for nullable
25+
if notNull {
26+
return "string"
27+
}
28+
if options.EmitPointersForNullTypes {
29+
return "*string"
30+
}
31+
return "sql.NullString"
32+
}
33+
if notNull {
34+
return "string"
35+
}
36+
return "sql.NullString"
37+
38+
// Integer types - UInt variants (unsigned)
39+
case "uint8":
40+
if useNativeDriver {
41+
if notNull {
42+
return "uint8"
43+
}
44+
if options.EmitPointersForNullTypes {
45+
return "*uint8"
46+
}
47+
return "sql.NullInt16"
48+
}
49+
if notNull {
50+
return "uint8"
51+
}
52+
return "sql.NullInt16" // database/sql doesn't have NullUint8
53+
54+
case "uint16":
55+
if useNativeDriver {
56+
if notNull {
57+
return "uint16"
58+
}
59+
if options.EmitPointersForNullTypes {
60+
return "*uint16"
61+
}
62+
return "sql.NullInt32"
63+
}
64+
if notNull {
65+
return "uint16"
66+
}
67+
return "sql.NullInt32" // database/sql doesn't have NullUint16
68+
69+
case "uint32":
70+
if useNativeDriver {
71+
if notNull {
72+
return "uint32"
73+
}
74+
if options.EmitPointersForNullTypes {
75+
return "*uint32"
76+
}
77+
return "sql.NullInt64"
78+
}
79+
if notNull {
80+
return "uint32"
81+
}
82+
return "sql.NullInt64" // database/sql doesn't have NullUint32
83+
84+
case "uint64":
85+
if useNativeDriver {
86+
if notNull {
87+
return "uint64"
88+
}
89+
if options.EmitPointersForNullTypes {
90+
return "*uint64"
91+
}
92+
return "sql.NullInt64"
93+
}
94+
if notNull {
95+
return "uint64"
96+
}
97+
return "string" // uint64 can overflow, use string for large values
98+
99+
// Integer types - Int variants (signed)
100+
case "int8":
101+
if useNativeDriver {
102+
if notNull {
103+
return "int8"
104+
}
105+
if options.EmitPointersForNullTypes {
106+
return "*int8"
107+
}
108+
return "sql.NullInt16"
109+
}
110+
if notNull {
111+
return "int8"
112+
}
113+
return "sql.NullInt16"
114+
115+
case "int16":
116+
if useNativeDriver {
117+
if notNull {
118+
return "int16"
119+
}
120+
if options.EmitPointersForNullTypes {
121+
return "*int16"
122+
}
123+
return "sql.NullInt16"
124+
}
125+
if notNull {
126+
return "int16"
127+
}
128+
return "sql.NullInt16"
129+
130+
case "int32":
131+
if useNativeDriver {
132+
if notNull {
133+
return "int32"
134+
}
135+
if options.EmitPointersForNullTypes {
136+
return "*int32"
137+
}
138+
return "sql.NullInt32"
139+
}
140+
if notNull {
141+
return "int32"
142+
}
143+
return "sql.NullInt32"
144+
145+
case "int64":
146+
if useNativeDriver {
147+
if notNull {
148+
return "int64"
149+
}
150+
if options.EmitPointersForNullTypes {
151+
return "*int64"
152+
}
153+
return "sql.NullInt64"
154+
}
155+
if notNull {
156+
return "int64"
157+
}
158+
return "sql.NullInt64"
159+
160+
// Generic "integer" type (used for LIMIT/OFFSET parameters and other integer values)
161+
case "integer":
162+
if useNativeDriver {
163+
if notNull {
164+
return "int64"
165+
}
166+
if options.EmitPointersForNullTypes {
167+
return "*int64"
168+
}
169+
return "sql.NullInt64"
170+
}
171+
if notNull {
172+
return "int64"
173+
}
174+
return "sql.NullInt64"
175+
176+
// Large integer types
177+
case "int128", "int256", "uint128", "uint256":
178+
// These are too large for standard Go integers, use string
179+
if notNull {
180+
return "string"
181+
}
182+
return "sql.NullString"
183+
184+
// Floating point types
185+
case "float32", "real":
186+
if useNativeDriver {
187+
if notNull {
188+
return "float32"
189+
}
190+
if options.EmitPointersForNullTypes {
191+
return "*float32"
192+
}
193+
return "sql.NullFloat64"
194+
}
195+
if notNull {
196+
return "float32"
197+
}
198+
return "sql.NullFloat64" // database/sql doesn't have NullFloat32
199+
200+
case "float64", "double precision", "double":
201+
if useNativeDriver {
202+
if notNull {
203+
return "float64"
204+
}
205+
if options.EmitPointersForNullTypes {
206+
return "*float64"
207+
}
208+
return "sql.NullFloat64"
209+
}
210+
if notNull {
211+
return "float64"
212+
}
213+
return "sql.NullFloat64"
214+
215+
// Decimal types
216+
case "decimal":
217+
if notNull {
218+
return "string"
219+
}
220+
return "sql.NullString"
221+
222+
// Date and time types
223+
case "date", "date32":
224+
if useNativeDriver {
225+
if notNull {
226+
return "time.Time"
227+
}
228+
if options.EmitPointersForNullTypes {
229+
return "*time.Time"
230+
}
231+
return "sql.NullTime"
232+
}
233+
if notNull {
234+
return "time.Time"
235+
}
236+
return "sql.NullTime"
237+
238+
case "datetime", "datetime64", "timestamp":
239+
if useNativeDriver {
240+
if notNull {
241+
return "time.Time"
242+
}
243+
if options.EmitPointersForNullTypes {
244+
return "*time.Time"
245+
}
246+
return "sql.NullTime"
247+
}
248+
if notNull {
249+
return "time.Time"
250+
}
251+
return "sql.NullTime"
252+
253+
// Boolean
254+
case "boolean", "bool":
255+
if useNativeDriver {
256+
if notNull {
257+
return "bool"
258+
}
259+
if options.EmitPointersForNullTypes {
260+
return "*bool"
261+
}
262+
return "sql.NullBool"
263+
}
264+
if notNull {
265+
return "bool"
266+
}
267+
return "sql.NullBool"
268+
269+
// UUID
270+
case "uuid":
271+
if notNull {
272+
return "string"
273+
}
274+
return "sql.NullString"
275+
276+
// IP address types
277+
case "ipv4", "ipv6":
278+
if notNull {
279+
return "netip.Addr"
280+
}
281+
if options.EmitPointersForNullTypes {
282+
return "*netip.Addr"
283+
}
284+
// Use a custom SQL null type for nullable IP addresses
285+
// For now, use pointer since netip.Addr doesn't have a nullable variant
286+
return "*netip.Addr"
287+
288+
// JSON types
289+
case "json":
290+
return "json.RawMessage"
291+
292+
// Arrays - ClickHouse array types
293+
case "array":
294+
if useNativeDriver {
295+
// Native driver has better array support
296+
// For now, still use generic until we have element type info
297+
return "[]interface{}"
298+
}
299+
return "[]interface{}" // Generic array type
300+
301+
// Any/Unknown type
302+
case "any":
303+
return "interface{}"
304+
305+
default:
306+
// Check if this is a map type (starts with "map[")
307+
// Map types come from the engine layer with full type information (e.g., "map[string]int64")
308+
if strings.HasPrefix(columnType, "map[") {
309+
if notNull {
310+
return columnType
311+
}
312+
// For nullable map types, wrap in pointer
313+
if options.EmitPointersForNullTypes {
314+
return "*" + columnType
315+
}
316+
// Otherwise treat as interface{} for nullable
317+
return "interface{}"
318+
}
319+
320+
// Check for custom types (enums, etc.)
321+
for _, schema := range req.Catalog.Schemas {
322+
for _, enum := range schema.Enums {
323+
if enum.Name == columnType {
324+
if notNull {
325+
if schema.Name == req.Catalog.DefaultSchema {
326+
return StructName(enum.Name, options)
327+
}
328+
return StructName(schema.Name+"_"+enum.Name, options)
329+
} else {
330+
if schema.Name == req.Catalog.DefaultSchema {
331+
return "Null" + StructName(enum.Name, options)
332+
}
333+
return "Null" + StructName(schema.Name+"_"+enum.Name, options)
334+
}
335+
}
336+
}
337+
}
338+
339+
// Default fallback for unknown types
340+
return "interface{}"
341+
}
342+
}

internal/codegen/golang/driver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ func parseDriver(sqlPackage string) opts.SQLDriver {
88
return opts.SQLDriverPGXV4
99
case opts.SQLPackagePGXV5:
1010
return opts.SQLDriverPGXV5
11+
case opts.SQLPackageClickHouseV2:
12+
return opts.SQLDriverClickHouseV2
1113
default:
1214
return opts.SQLDriverLibPQ
1315
}

internal/codegen/golang/go_type.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ func goInnerType(req *plugin.GenerateRequest, options *opts.Options, col *plugin
8989
return postgresType(req, options, col)
9090
case "sqlite":
9191
return sqliteType(req, options, col)
92+
case "clickhouse":
93+
return clickhouseType(req, options, col)
9294
default:
9395
return "interface{}"
9496
}

internal/codegen/golang/imports.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ func (i *importer) dbImports() fileImports {
132132
case opts.SQLDriverPGXV5:
133133
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5/pgconn"})
134134
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5"})
135+
case opts.SQLDriverClickHouseV2:
136+
pkg = append(pkg, ImportSpec{Path: "github.com/ClickHouse/clickhouse-go/v2/lib/driver"})
135137
default:
136138
std = append(std, ImportSpec{Path: "database/sql"})
137139
if i.Options.EmitPreparedQueries {
@@ -395,7 +397,7 @@ func (i *importer) queryImports(filename string) fileImports {
395397
}
396398

397399
sqlpkg := parseDriver(i.Options.SqlPackage)
398-
if sqlcSliceScan() && !sqlpkg.IsPGX() {
400+
if sqlcSliceScan() && !sqlpkg.IsPGX() && !sqlpkg.IsClickHouse() {
399401
std["strings"] = struct{}{}
400402
}
401403
if sliceScan() && !sqlpkg.IsPGX() {

0 commit comments

Comments
 (0)