Skip to content

Commit 0cf8ef2

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 178c36e commit 0cf8ef2

File tree

29 files changed

+797
-248
lines changed

29 files changed

+797
-248
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
@@ -103,6 +103,9 @@ Each override document has the following keys:
103103
- 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.
104104
- `go_type`:
105105
- A fully qualified name to a Go type to use in the generated code.
106+
- `go_struct_tag`:
107+
- A reflect-style struct tag to use in the generated code, e.g. `a:"b" x:"y,z"`.
108+
If you want general json/db tags for all fields, use `emit_db_tags` and/or `emit_json_tags` instead.
106109
- `nullable`:
107110
- If true, use this type when a column is nullable. Defaults to `false`.
108111

internal/cmd/shim.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func pluginGoType(o config.Override) *plugin.ParsedGoType {
109109
Package: o.GoPackage,
110110
TypeName: o.GoTypeName,
111111
BasicType: o.GoBasicType,
112+
StructTags: o.GoStructTags,
112113
}
113114
}
114115

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
@@ -167,6 +167,10 @@ type Override struct {
167167
// name of the golang type to use, e.g. `github.com/segmentio/ksuid.KSUID`
168168
GoType GoType `json:"go_type" yaml:"go_type"`
169169

170+
// additional Go struct tags to add to this field, in raw Go struct tag form, e.g. `validate:"required" x:"y,z"`
171+
// see https://github.com/kyleconroy/sqlc/issues/534
172+
GoStructTag GoStructTag `json:"go_struct_tag" yaml:"go_struct_tag"`
173+
170174
// name of the python type to use, e.g. `mymodule.TypeName`
171175
PythonType PythonType `json:"python_type" yaml:"python_type"`
172176

@@ -193,6 +197,9 @@ type Override struct {
193197
GoPackage string
194198
GoTypeName string
195199
GoBasicType bool
200+
201+
// Parsed form of GoStructTag, e.g. {"validate:", "required"}
202+
GoStructTags map[string]string
196203
}
197204

198205
func (o *Override) Matches(n *ast.TableName, defaultSchema string) bool {
@@ -305,6 +312,13 @@ func (o *Override) Parse() (err error) {
305312
o.GoTypeName = parsed.TypeName
306313
o.GoBasicType = parsed.BasicType
307314

315+
// validate GoStructTag
316+
tags, err := o.GoStructTag.Parse()
317+
if err != nil {
318+
return err
319+
}
320+
o.GoStructTags = tags
321+
308322
return nil
309323
}
310324

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)