Skip to content

Commit 829fc0c

Browse files
authored
feat: do not report types implementing sql.Scanner (#104)
1 parent 3799ac8 commit 829fc0c

File tree

3 files changed

+102
-145
lines changed

3 files changed

+102
-145
lines changed

builtins.go

Lines changed: 51 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -3,131 +3,65 @@ package musttag
33
// builtins is a set of functions supported out of the box.
44
var builtins = []Func{
55
// https://pkg.go.dev/encoding/json
6-
{
7-
Name: "encoding/json.Marshal", Tag: "json", ArgPos: 0,
8-
ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"},
9-
},
10-
{
11-
Name: "encoding/json.MarshalIndent", Tag: "json", ArgPos: 0,
12-
ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"},
13-
},
14-
{
15-
Name: "encoding/json.Unmarshal", Tag: "json", ArgPos: 1,
16-
ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"},
17-
},
18-
{
19-
Name: "(*encoding/json.Encoder).Encode", Tag: "json", ArgPos: 0,
20-
ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"},
21-
},
22-
{
23-
Name: "(*encoding/json.Decoder).Decode", Tag: "json", ArgPos: 0,
24-
ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"},
25-
},
6+
{"encoding/json.Marshal", "json", 0, []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}},
7+
{"encoding/json.MarshalIndent", "json", 0, []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}},
8+
{"encoding/json.Unmarshal", "json", 1, []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}},
9+
{"(*encoding/json.Encoder).Encode", "json", 0, []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}},
10+
{"(*encoding/json.Decoder).Decode", "json", 0, []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}},
2611

2712
// https://pkg.go.dev/encoding/xml
28-
{
29-
Name: "encoding/xml.Marshal", Tag: "xml", ArgPos: 0,
30-
ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"},
31-
},
32-
{
33-
Name: "encoding/xml.MarshalIndent", Tag: "xml", ArgPos: 0,
34-
ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"},
35-
},
36-
{
37-
Name: "encoding/xml.Unmarshal", Tag: "xml", ArgPos: 1,
38-
ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"},
39-
},
40-
{
41-
Name: "(*encoding/xml.Encoder).Encode", Tag: "xml", ArgPos: 0,
42-
ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"},
43-
},
44-
{
45-
Name: "(*encoding/xml.Decoder).Decode", Tag: "xml", ArgPos: 0,
46-
ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"},
47-
},
48-
{
49-
Name: "(*encoding/xml.Encoder).EncodeElement", Tag: "xml", ArgPos: 0,
50-
ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"},
51-
},
52-
{
53-
Name: "(*encoding/xml.Decoder).DecodeElement", Tag: "xml", ArgPos: 0,
54-
ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"},
55-
},
13+
{"encoding/xml.Marshal", "xml", 0, []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}},
14+
{"encoding/xml.MarshalIndent", "xml", 0, []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}},
15+
{"encoding/xml.Unmarshal", "xml", 1, []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}},
16+
{"(*encoding/xml.Encoder).Encode", "xml", 0, []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}},
17+
{"(*encoding/xml.Decoder).Decode", "xml", 0, []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}},
18+
{"(*encoding/xml.Encoder).EncodeElement", "xml", 0, []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}},
19+
{"(*encoding/xml.Decoder).DecodeElement", "xml", 0, []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}},
5620

5721
// https://pkg.go.dev/gopkg.in/yaml.v3
58-
{
59-
Name: "gopkg.in/yaml.v3.Marshal", Tag: "yaml", ArgPos: 0,
60-
ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"},
61-
},
62-
{
63-
Name: "gopkg.in/yaml.v3.Unmarshal", Tag: "yaml", ArgPos: 1,
64-
ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"},
65-
},
66-
{
67-
Name: "(*gopkg.in/yaml.v3.Encoder).Encode", Tag: "yaml", ArgPos: 0,
68-
ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"},
69-
},
70-
{
71-
Name: "(*gopkg.in/yaml.v3.Decoder).Decode", Tag: "yaml", ArgPos: 0,
72-
ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"},
73-
},
22+
{"gopkg.in/yaml.v3.Marshal", "yaml", 0, []string{"gopkg.in/yaml.v3.Marshaler"}},
23+
{"gopkg.in/yaml.v3.Unmarshal", "yaml", 1, []string{"gopkg.in/yaml.v3.Unmarshaler"}},
24+
{"(*gopkg.in/yaml.v3.Encoder).Encode", "yaml", 0, []string{"gopkg.in/yaml.v3.Marshaler"}},
25+
{"(*gopkg.in/yaml.v3.Decoder).Decode", "yaml", 0, []string{"gopkg.in/yaml.v3.Unmarshaler"}},
7426

7527
// https://pkg.go.dev/github.com/BurntSushi/toml
76-
{
77-
Name: "github.com/BurntSushi/toml.Unmarshal", Tag: "toml", ArgPos: 1,
78-
ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"},
79-
},
80-
{
81-
Name: "github.com/BurntSushi/toml.Decode", Tag: "toml", ArgPos: 1,
82-
ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"},
83-
},
84-
{
85-
Name: "github.com/BurntSushi/toml.DecodeFS", Tag: "toml", ArgPos: 2,
86-
ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"},
87-
},
88-
{
89-
Name: "github.com/BurntSushi/toml.DecodeFile", Tag: "toml", ArgPos: 1,
90-
ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"},
91-
},
92-
{
93-
Name: "(*github.com/BurntSushi/toml.Encoder).Encode", Tag: "toml", ArgPos: 0,
94-
ifaceWhitelist: []string{"encoding.TextMarshaler"},
95-
},
96-
{
97-
Name: "(*github.com/BurntSushi/toml.Decoder).Decode", Tag: "toml", ArgPos: 0,
98-
ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"},
99-
},
28+
{"github.com/BurntSushi/toml.Unmarshal", "toml", 1, []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}},
29+
{"github.com/BurntSushi/toml.Decode", "toml", 1, []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}},
30+
{"github.com/BurntSushi/toml.DecodeFS", "toml", 2, []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}},
31+
{"github.com/BurntSushi/toml.DecodeFile", "toml", 1, []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}},
32+
{"(*github.com/BurntSushi/toml.Encoder).Encode", "toml", 0, []string{"encoding.TextMarshaler"}},
33+
{"(*github.com/BurntSushi/toml.Decoder).Decode", "toml", 0, []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}},
10034

10135
// https://pkg.go.dev/github.com/mitchellh/mapstructure
102-
{Name: "github.com/mitchellh/mapstructure.Decode", Tag: "mapstructure", ArgPos: 1},
103-
{Name: "github.com/mitchellh/mapstructure.DecodeMetadata", Tag: "mapstructure", ArgPos: 1},
104-
{Name: "github.com/mitchellh/mapstructure.WeakDecode", Tag: "mapstructure", ArgPos: 1},
105-
{Name: "github.com/mitchellh/mapstructure.WeakDecodeMetadata", Tag: "mapstructure", ArgPos: 1},
36+
{"github.com/mitchellh/mapstructure.Decode", "mapstructure", 1, nil},
37+
{"github.com/mitchellh/mapstructure.DecodeMetadata", "mapstructure", 1, nil},
38+
{"github.com/mitchellh/mapstructure.WeakDecode", "mapstructure", 1, nil},
39+
{"github.com/mitchellh/mapstructure.WeakDecodeMetadata", "mapstructure", 1, nil},
10640

10741
// https://pkg.go.dev/github.com/jmoiron/sqlx
108-
{Name: "github.com/jmoiron/sqlx.Get", Tag: "db", ArgPos: 1},
109-
{Name: "github.com/jmoiron/sqlx.GetContext", Tag: "db", ArgPos: 2},
110-
{Name: "github.com/jmoiron/sqlx.Select", Tag: "db", ArgPos: 1},
111-
{Name: "github.com/jmoiron/sqlx.SelectContext", Tag: "db", ArgPos: 2},
112-
{Name: "github.com/jmoiron/sqlx.StructScan", Tag: "db", ArgPos: 1},
113-
{Name: "(*github.com/jmoiron/sqlx.Conn).GetContext", Tag: "db", ArgPos: 1},
114-
{Name: "(*github.com/jmoiron/sqlx.Conn).SelectContext", Tag: "db", ArgPos: 1},
115-
{Name: "(*github.com/jmoiron/sqlx.DB).Get", Tag: "db", ArgPos: 0},
116-
{Name: "(*github.com/jmoiron/sqlx.DB).GetContext", Tag: "db", ArgPos: 1},
117-
{Name: "(*github.com/jmoiron/sqlx.DB).Select", Tag: "db", ArgPos: 0},
118-
{Name: "(*github.com/jmoiron/sqlx.DB).SelectContext", Tag: "db", ArgPos: 1},
119-
{Name: "(*github.com/jmoiron/sqlx.NamedStmt).Get", Tag: "db", ArgPos: 0},
120-
{Name: "(*github.com/jmoiron/sqlx.NamedStmt).GetContext", Tag: "db", ArgPos: 1},
121-
{Name: "(*github.com/jmoiron/sqlx.NamedStmt).Select", Tag: "db", ArgPos: 0},
122-
{Name: "(*github.com/jmoiron/sqlx.NamedStmt).SelectContext", Tag: "db", ArgPos: 1},
123-
{Name: "(*github.com/jmoiron/sqlx.Row).StructScan", Tag: "db", ArgPos: 0},
124-
{Name: "(*github.com/jmoiron/sqlx.Rows).StructScan", Tag: "db", ArgPos: 0},
125-
{Name: "(*github.com/jmoiron/sqlx.Stmt).Get", Tag: "db", ArgPos: 0},
126-
{Name: "(*github.com/jmoiron/sqlx.Stmt).GetContext", Tag: "db", ArgPos: 1},
127-
{Name: "(*github.com/jmoiron/sqlx.Stmt).Select", Tag: "db", ArgPos: 0},
128-
{Name: "(*github.com/jmoiron/sqlx.Stmt).SelectContext", Tag: "db", ArgPos: 1},
129-
{Name: "(*github.com/jmoiron/sqlx.Tx).Get", Tag: "db", ArgPos: 0},
130-
{Name: "(*github.com/jmoiron/sqlx.Tx).GetContext", Tag: "db", ArgPos: 1},
131-
{Name: "(*github.com/jmoiron/sqlx.Tx).Select", Tag: "db", ArgPos: 0},
132-
{Name: "(*github.com/jmoiron/sqlx.Tx).SelectContext", Tag: "db", ArgPos: 1},
42+
{"github.com/jmoiron/sqlx.Get", "db", 1, []string{"database/sql.Scanner"}},
43+
{"github.com/jmoiron/sqlx.GetContext", "db", 2, []string{"database/sql.Scanner"}},
44+
{"github.com/jmoiron/sqlx.Select", "db", 1, []string{"database/sql.Scanner"}},
45+
{"github.com/jmoiron/sqlx.SelectContext", "db", 2, []string{"database/sql.Scanner"}},
46+
{"github.com/jmoiron/sqlx.StructScan", "db", 1, []string{"database/sql.Scanner"}},
47+
{"(*github.com/jmoiron/sqlx.Conn).GetContext", "db", 1, []string{"database/sql.Scanner"}},
48+
{"(*github.com/jmoiron/sqlx.Conn).SelectContext", "db", 1, []string{"database/sql.Scanner"}},
49+
{"(*github.com/jmoiron/sqlx.DB).Get", "db", 0, []string{"database/sql.Scanner"}},
50+
{"(*github.com/jmoiron/sqlx.DB).GetContext", "db", 1, []string{"database/sql.Scanner"}},
51+
{"(*github.com/jmoiron/sqlx.DB).Select", "db", 0, []string{"database/sql.Scanner"}},
52+
{"(*github.com/jmoiron/sqlx.DB).SelectContext", "db", 1, []string{"database/sql.Scanner"}},
53+
{"(*github.com/jmoiron/sqlx.NamedStmt).Get", "db", 0, []string{"database/sql.Scanner"}},
54+
{"(*github.com/jmoiron/sqlx.NamedStmt).GetContext", "db", 1, []string{"database/sql.Scanner"}},
55+
{"(*github.com/jmoiron/sqlx.NamedStmt).Select", "db", 0, []string{"database/sql.Scanner"}},
56+
{"(*github.com/jmoiron/sqlx.NamedStmt).SelectContext", "db", 1, []string{"database/sql.Scanner"}},
57+
{"(*github.com/jmoiron/sqlx.Row).StructScan", "db", 0, []string{"database/sql.Scanner"}},
58+
{"(*github.com/jmoiron/sqlx.Rows).StructScan", "db", 0, []string{"database/sql.Scanner"}},
59+
{"(*github.com/jmoiron/sqlx.Stmt).Get", "db", 0, []string{"database/sql.Scanner"}},
60+
{"(*github.com/jmoiron/sqlx.Stmt).GetContext", "db", 1, []string{"database/sql.Scanner"}},
61+
{"(*github.com/jmoiron/sqlx.Stmt).Select", "db", 0, []string{"database/sql.Scanner"}},
62+
{"(*github.com/jmoiron/sqlx.Stmt).SelectContext", "db", 1, []string{"database/sql.Scanner"}},
63+
{"(*github.com/jmoiron/sqlx.Tx).Get", "db", 0, []string{"database/sql.Scanner"}},
64+
{"(*github.com/jmoiron/sqlx.Tx).GetContext", "db", 1, []string{"database/sql.Scanner"}},
65+
{"(*github.com/jmoiron/sqlx.Tx).Select", "db", 0, []string{"database/sql.Scanner"}},
66+
{"(*github.com/jmoiron/sqlx.Tx).SelectContext", "db", 1, []string{"database/sql.Scanner"}},
13367
}

musttag.go

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any,
9191

9292
call, ok := node.(*ast.CallExpr)
9393
if !ok {
94-
return // not a function call.
94+
return
9595
}
9696

9797
callee := typeutil.StaticCallee(pass.TypesInfo, call)
9898
if callee == nil {
99-
return // not a static call.
99+
return
100100
}
101101

102102
fn, ok := funcs[cutVendor(callee.FullName())]
103103
if !ok {
104-
return // unsupported function.
104+
return
105105
}
106106

107107
if len(call.Args) <= fn.ArgPos {
@@ -116,7 +116,7 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any,
116116

117117
typ := pass.TypesInfo.TypeOf(arg)
118118
if typ == nil {
119-
return // no type info found.
119+
return
120120
}
121121

122122
checker := checker{
@@ -125,9 +125,8 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any,
125125
ifaceWhitelist: fn.ifaceWhitelist,
126126
imports: pass.Pkg.Imports(),
127127
}
128-
129-
if valid := checker.checkType(typ, fn.Tag); valid {
130-
return // nothing to report.
128+
if checker.isValidType(typ, fn.Tag) {
129+
return
131130
}
132131

133132
pass.Reportf(arg.Pos(), "the given struct should be annotated with the `%s` tag", fn.Tag)
@@ -143,43 +142,32 @@ type checker struct {
143142
imports []*types.Package
144143
}
145144

146-
func (c *checker) checkType(typ types.Type, tag string) bool {
145+
func (c *checker) isValidType(typ types.Type, tag string) bool {
147146
if _, ok := c.seenTypes[typ.String()]; ok {
148-
return true // already checked.
147+
return true
149148
}
150149
c.seenTypes[typ.String()] = struct{}{}
151150

152151
styp, ok := c.parseStruct(typ)
153152
if !ok {
154-
return true // not a struct.
153+
return true
155154
}
156155

157-
return c.checkStruct(styp, tag)
156+
return c.isValidStruct(styp, tag)
158157
}
159158

160-
// recursively unwrap a type until we get to an underlying
161-
// raw struct type that should have its fields checked
162-
//
163-
// SomeStruct -> struct{SomeStructField: ... }
164-
// []*SomeStruct -> struct{SomeStructField: ... }
165-
// ...
166-
//
167-
// exits early if it hits a type that implements a whitelisted interface
168159
func (c *checker) parseStruct(typ types.Type) (*types.Struct, bool) {
169160
if implementsInterface(typ, c.ifaceWhitelist, c.imports) {
170-
return nil, false // the type implements a Marshaler interface; see issue #64.
161+
return nil, false
171162
}
172163

173164
switch typ := typ.(type) {
174165
case *types.Pointer:
175166
return c.parseStruct(typ.Elem())
176-
177167
case *types.Array:
178168
return c.parseStruct(typ.Elem())
179-
180169
case *types.Slice:
181170
return c.parseStruct(typ.Elem())
182-
183171
case *types.Map:
184172
return c.parseStruct(typ.Elem())
185173

@@ -205,7 +193,7 @@ func (c *checker) parseStruct(typ types.Type) (*types.Struct, bool) {
205193
}
206194
}
207195

208-
func (c *checker) checkStruct(styp *types.Struct, tag string) (valid bool) {
196+
func (c *checker) isValidStruct(styp *types.Struct, tag string) bool {
209197
for i := 0; i < styp.NumFields(); i++ {
210198
field := styp.Field(i)
211199
if !field.Exported() {
@@ -214,18 +202,18 @@ func (c *checker) checkStruct(styp *types.Struct, tag string) (valid bool) {
214202

215203
tagValue, ok := reflect.StructTag(styp.Tag(i)).Lookup(tag)
216204
if !ok {
217-
// tag is not required for embedded types; see issue #12.
205+
// tag is not required for embedded types.
218206
if !field.Embedded() {
219207
return false
220208
}
221209
}
222210

223-
// Do not recurse into ignored fields.
211+
// the field is explicitly ignored.
224212
if tagValue == "-" {
225213
continue
226214
}
227215

228-
if valid := c.checkType(field.Type(), tag); !valid {
216+
if !c.isValidType(field.Type(), tag) {
229217
return false
230218
}
231219
}
@@ -254,25 +242,29 @@ func implementsInterface(typ types.Type, ifaces []string, imports []*types.Packa
254242
}
255243

256244
for _, ifacePath := range ifaces {
257-
// "encoding/json.Marshaler" -> "encoding/json" + "Marshaler"
245+
// e.g. "encoding/json.Marshaler" -> "encoding/json" + "Marshaler".
258246
idx := strings.LastIndex(ifacePath, ".")
259247
if idx == -1 {
260248
continue
261249
}
250+
262251
pkgName, ifaceName := ifacePath[:idx], ifacePath[idx+1:]
263252

264253
scope, ok := findScope(pkgName)
265254
if !ok {
266255
continue
267256
}
257+
268258
obj := scope.Lookup(ifaceName)
269259
if obj == nil {
270260
continue
271261
}
262+
272263
iface, ok := obj.Type().Underlying().(*types.Interface)
273264
if !ok {
274265
continue
275266
}
267+
276268
if types.Implements(typ, iface) || types.Implements(types.NewPointer(typ), iface) {
277269
return true
278270
}

testdata/src/tests/builtins.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type TextMarshaler struct{ NoTag string }
2828
func (TextMarshaler) MarshalText() ([]byte, error) { return nil, nil }
2929
func (*TextMarshaler) UnmarshalText([]byte) error { return nil }
3030

31+
type Scanner struct{ NotTag string }
32+
33+
func (*Scanner) Scan(any) error { return nil }
34+
3135
func testJSON() {
3236
var st Struct
3337
json.Marshal(st) // want "the given struct should be annotated with the `json` tag"
@@ -154,6 +158,33 @@ func testSQLX() {
154158
new(sqlx.Tx).GetContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag"
155159
new(sqlx.Tx).Select(&st, "") // want "the given struct should be annotated with the `db` tag"
156160
new(sqlx.Tx).SelectContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag"
161+
162+
var sc Scanner
163+
sqlx.Get(nil, &sc, "")
164+
sqlx.GetContext(nil, nil, &sc, "")
165+
sqlx.Select(nil, &sc, "")
166+
sqlx.SelectContext(nil, nil, &sc, "")
167+
sqlx.StructScan(nil, &sc)
168+
new(sqlx.Conn).GetContext(nil, &sc, "")
169+
new(sqlx.Conn).SelectContext(nil, &sc, "")
170+
new(sqlx.DB).Get(&sc, "")
171+
new(sqlx.DB).GetContext(nil, &sc, "")
172+
new(sqlx.DB).Select(&sc, "")
173+
new(sqlx.DB).SelectContext(nil, &sc, "")
174+
new(sqlx.NamedStmt).Get(&sc, nil)
175+
new(sqlx.NamedStmt).GetContext(nil, &sc, nil)
176+
new(sqlx.NamedStmt).Select(&sc, nil)
177+
new(sqlx.NamedStmt).SelectContext(nil, &sc, nil)
178+
new(sqlx.Row).StructScan(&sc)
179+
new(sqlx.Rows).StructScan(&sc)
180+
new(sqlx.Stmt).Get(&sc)
181+
new(sqlx.Stmt).GetContext(nil, &sc)
182+
new(sqlx.Stmt).Select(&sc)
183+
new(sqlx.Stmt).SelectContext(nil, &sc)
184+
new(sqlx.Tx).Get(&sc, "")
185+
new(sqlx.Tx).GetContext(nil, &sc, "")
186+
new(sqlx.Tx).Select(&sc, "")
187+
new(sqlx.Tx).SelectContext(nil, &sc, "")
157188
}
158189

159190
func testCustom() {

0 commit comments

Comments
 (0)