Skip to content

Commit b016f14

Browse files
committed
all: add support for custom Go struct tags
This change adds a new type of override: go_struct_tag. When provided for a field, it adds that struct tag to the generated code. The provided struct tag is parsed according to the standard package reflect rules, and its components are updated independently. This allows struct tag overrides to be compatible with (and optionally override) autogenerated json and db struct tags. Fixes #534
1 parent c147e96 commit b016f14

File tree

29 files changed

+786
-237
lines changed

29 files changed

+786
-237
lines changed

docs/howto/structs.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ type Author struct {
4646
CreatedAt time.Time `json:"created_at"`
4747
}
4848
```
49+
50+
## More control
51+
52+
See the Type Overrides section of the Configuration File docs for fine-grained control over struct field types and tags.

docs/reference/config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ Each override document has the following keys:
9595
- The PostgreSQL or MySQL type to override. Find the full list of supported types in [postgresql_type.go](https://github.com/kyleconroy/sqlc/blob/main/internal/codegen/golang/postgresql_type.go#L12) or [mysql_type.go](https://github.com/kyleconroy/sqlc/blob/main/internal/codegen/golang/mysql_type.go#L12). Note that for Postgres you must use the pg_catalog prefixed names where available.
9696
- `go_type`:
9797
- A fully qualified name to a Go type to use in the generated code.
98+
- `go_struct_tag`:
99+
- A reflect-style struct tag to use in the generated code, e.g. `a:"b" x:"y,z"`.
100+
If you want general json/db tags for all fields, use `emit_db_tags` and/or `emit_json_tags` instead.
98101
- `nullable`:
99102
- If true, use this type when a column is nullable. Defaults to `false`.
100103

internal/cmd/shim.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func pluginGoType(o config.Override) *plugin.ParsedGoType {
106106
Package: o.GoPackage,
107107
TypeName: o.GoTypeName,
108108
BasicType: o.GoBasicType,
109+
StructTags: o.GoStructTags,
109110
}
110111
}
111112

internal/codegen/golang/go_type.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ import (
55
"github.com/kyleconroy/sqlc/internal/plugin"
66
)
77

8+
func addExtraGoStructTags(tags map[string]string, req *plugin.CodeGenRequest, col *plugin.Column) {
9+
for _, oride := range req.Settings.Overrides {
10+
if oride.GoType.StructTags == nil {
11+
continue
12+
}
13+
if !sdk.Matches(oride, col.Table, req.Catalog.DefaultSchema) {
14+
// Different table.
15+
continue
16+
}
17+
if !sdk.MatchString(oride.ColumnName, col.Name) {
18+
// Different column.
19+
continue
20+
}
21+
// Add the extra tags.
22+
for k, v := range oride.GoType.StructTags {
23+
tags[k] = v
24+
}
25+
}
26+
}
27+
828
func goType(req *plugin.CodeGenRequest, col *plugin.Column) string {
929
// Check if the column's type has been overridden
1030
for _, oride := range req.Settings.Overrides {

internal/codegen/golang/result.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func buildStructs(req *plugin.CodeGenRequest) []Struct {
7979
if req.Settings.Go.EmitJsonTags {
8080
tags["json"] = JSONTagName(column.Name, req.Settings)
8181
}
82+
addExtraGoStructTags(tags, req, column)
8283
s.Fields = append(s.Fields, Field{
8384
Name: StructName(column.Name, req.Settings),
8485
Type: goType(req, column),

internal/config/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ type Override struct {
159159
// name of the golang type to use, e.g. `github.com/segmentio/ksuid.KSUID`
160160
GoType GoType `json:"go_type" yaml:"go_type"`
161161

162+
// additional Go struct tags to add to this field, in raw Go struct tag form, e.g. `validate:"required" x:"y,z"`
163+
// see https://github.com/kyleconroy/sqlc/issues/534
164+
GoStructTag GoStructTag `json:"go_struct_tag" yaml:"go_struct_tag"`
165+
162166
// name of the python type to use, e.g. `mymodule.TypeName`
163167
PythonType PythonType `json:"python_type" yaml:"python_type"`
164168

@@ -185,6 +189,9 @@ type Override struct {
185189
GoPackage string
186190
GoTypeName string
187191
GoBasicType bool
192+
193+
// Parsed form of GoStructTag, e.g. {"validate:", "required"}
194+
GoStructTags map[string]string
188195
}
189196

190197
func (o *Override) Matches(n *ast.TableName, defaultSchema string) bool {
@@ -297,6 +304,13 @@ func (o *Override) Parse() (err error) {
297304
o.GoTypeName = parsed.TypeName
298305
o.GoBasicType = parsed.BasicType
299306

307+
// validate GoStructTag
308+
tags, err := o.GoStructTag.Parse()
309+
if err != nil {
310+
return err
311+
}
312+
o.GoStructTags = tags
313+
300314
return nil
301315
}
302316

internal/config/go_type.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type ParsedGoType struct {
2222
Package string
2323
TypeName string
2424
BasicType bool
25+
StructTag string
2526
}
2627

2728
func (o *GoType) UnmarshalJSON(data []byte) error {
@@ -154,3 +155,31 @@ func (gt GoType) Parse() (*ParsedGoType, error) {
154155
}
155156
return &o, nil
156157
}
158+
159+
// GoStructTag is a raw Go struct tag.
160+
type GoStructTag string
161+
162+
// Parse parses and validates a GoStructTag.
163+
// The output is in a form convenient for codegen.
164+
//
165+
// Sample valid inputs/outputs:
166+
//
167+
// In Out
168+
// empty string {}
169+
// `a:"b"` {"a": "b"}
170+
// `a:"b" x:"y,z"` {"a": "b", "x": "y,z"}
171+
func (s GoStructTag) Parse() (map[string]string, error) {
172+
m := make(map[string]string)
173+
fields := strings.Fields(string(s))
174+
for _, f := range fields {
175+
k, v, ok := strings.Cut(f, ":")
176+
if !ok {
177+
return nil, fmt.Errorf("Failed to parse Go struct tag: no colon in field %q", f)
178+
}
179+
if len(v) < 2 || v[0] != '"' || v[len(v)-1] != '"' {
180+
return nil, fmt.Errorf("Failed to parse Go struct tag: missing quotes around value in field %q", f)
181+
}
182+
m[k] = v[1 : len(v)-1] // trim quotes off of v
183+
}
184+
return m, nil
185+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT 1;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE foo (
2+
other text NOT NULL,
3+
tagged text NOT NULL
4+
);
5+
6+
CREATE TABLE bar (
7+
other text NOT NULL,
8+
also_tagged text NOT NULL
9+
);
10+
11+
CREATE TABLE baz (
12+
other text NOT NULL,
13+
also_tagged text NOT NULL
14+
);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"version": "1",
3+
"packages": [
4+
{
5+
"path": "go",
6+
"name": "override",
7+
"engine": "mysql",
8+
"schema": "schema.sql",
9+
"queries": "query.sql",
10+
"overrides": [
11+
{
12+
"go_struct_tag": "abc",
13+
"column": "foo.tagged"
14+
},
15+
{
16+
"go_struct_tag": "a:b",
17+
"column": "*.also_tagged"
18+
}
19+
]
20+
}
21+
]
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
error parsing sqlc.json: Failed to parse Go struct tag: no colon in field "abc"

internal/endtoend/testdata/overrides_go_struct_tags/mysql/go/db.go

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/overrides_go_struct_tags/mysql/go/models.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT 1;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE foo (
2+
other text NOT NULL,
3+
tagged text NOT NULL
4+
);
5+
6+
CREATE TABLE bar (
7+
other text NOT NULL,
8+
also_tagged text NOT NULL
9+
);
10+
11+
CREATE TABLE baz (
12+
other text NOT NULL,
13+
also_tagged text NOT NULL
14+
);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"version": "1",
3+
"packages": [
4+
{
5+
"path": "go",
6+
"name": "override",
7+
"engine": "mysql",
8+
"schema": "schema.sql",
9+
"queries": "query.sql",
10+
"overrides": [
11+
{
12+
"go_struct_tag": "a:\"b\" x:\"y,z\"",
13+
"column": "foo.tagged"
14+
},
15+
{
16+
"go_struct_tag": "also:\"tagged\"",
17+
"column": "*.also_tagged"
18+
}
19+
]
20+
}
21+
]
22+
}

internal/endtoend/testdata/overrides_go_struct_tags/postgresql/pgx/go/db.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/overrides_go_struct_tags/postgresql/pgx/go/models.go

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT 1;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CREATE TABLE foo (
2+
id text,
3+
other_id text,
4+
about text,
5+
other text
6+
);
7+
8+
CREATE TABLE bar (
9+
id text,
10+
other_id text,
11+
about text,
12+
other text
13+
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"version": "1",
3+
"packages": [
4+
{
5+
"path": "go",
6+
"engine": "postgresql",
7+
"sql_package": "pgx/v4",
8+
"name": "override",
9+
"schema": "schema.sql",
10+
"queries": "query.sql",
11+
"overrides": [
12+
{
13+
"column": "*.id",
14+
"go_struct_tag": "type:\"id\""
15+
},
16+
{
17+
"column": "*.*_id",
18+
"go_struct_tag": "type:\"other_id\""
19+
},
20+
{
21+
"column": "foo.about",
22+
"go_struct_tag": "type:\"about\""
23+
},
24+
{
25+
"column": "foo.id",
26+
"go_struct_tag": "source:\"foo\""
27+
},
28+
{
29+
"column": "*.other",
30+
"go_struct_tag": "type:\"other\""
31+
},
32+
{
33+
"column": "foo.other",
34+
"go_struct_tag": "type:\"this\""
35+
}
36+
]
37+
}
38+
]
39+
}

0 commit comments

Comments
 (0)