🌐 AI搜索 & 代理 主页
blob: 11a68e0c374c027ab21ea7c55b83f671752058a2 [file] [log] [blame]
Jason Moiron4f886132014-02-19 06:01:051package sqlx
2
3// Named Query Support
4//
Jason Moiron16e5f492014-05-18 06:19:425// * BindMap - bind query bindvars to map/struct args
Jason Moironf62875c2014-03-16 20:53:326// * NamedExec, NamedQuery - named query w/ struct or map
Jason Moiron4f886132014-02-19 06:01:057// * NamedStmt - a pre-compiled named query which is a prepared statement
8//
9// Internal Interfaces:
10//
11// * compileNamedQuery - rebind a named query, returning a query and list of names
Jason Moironf62875c2014-03-16 20:53:3212// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
Jason Moiron4f886132014-02-19 06:01:0513//
14import (
mingang.he018bd2d2017-02-25 09:19:1115 "bytes"
Jason Moiron8e788cf2014-02-20 05:15:2916 "database/sql"
Jason Moiron4f886132014-02-19 06:01:0517 "errors"
18 "fmt"
19 "reflect"
mingang.he018bd2d2017-02-25 09:19:1120 "regexp"
Jason Moiron4f886132014-02-19 06:01:0521 "strconv"
22 "unicode"
Jason Moiron5bdae992014-05-17 23:59:0123
Jason Moiron2b1877d2014-05-18 00:44:4924 "github.com/jmoiron/sqlx/reflectx"
Jason Moiron4f886132014-02-19 06:01:0525)
26
27// NamedStmt is a prepared statement that executes named queries. Prepare it
Jason Moironf62875c2014-03-16 20:53:3228// how you would execute a NamedQuery, but pass in a struct or map when executing.
Jason Moiron8e788cf2014-02-20 05:15:2929type NamedStmt struct {
30 Params []string
31 QueryString string
32 Stmt *Stmt
Jason Moiron4f886132014-02-19 06:01:0533}
34
Jason Moiron8e788cf2014-02-20 05:15:2935// Close closes the named statement.
36func (n *NamedStmt) Close() error {
37 return n.Stmt.Close()
38}
Jason Moiron4f886132014-02-19 06:01:0539
Jason Moiron8e788cf2014-02-20 05:15:2940// Exec executes a named statement using the struct passed.
Richard Gibson71e31362016-11-14 17:42:3341// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2942func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
Jason Moiron2f383ca2014-07-23 04:18:3543 args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
Jason Moiron8e788cf2014-02-20 05:15:2944 if err != nil {
45 return *new(sql.Result), err
46 }
47 return n.Stmt.Exec(args...)
48}
49
50// Query executes a named statement using the struct argument, returning rows.
Richard Gibson71e31362016-11-14 17:42:3351// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2952func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
Jason Moiron2f383ca2014-07-23 04:18:3553 args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
Jason Moiron8e788cf2014-02-20 05:15:2954 if err != nil {
55 return nil, err
56 }
57 return n.Stmt.Query(args...)
58}
59
60// QueryRow executes a named statement against the database. Because sqlx cannot
61// create a *sql.Row with an error condition pre-set for binding errors, sqlx
62// returns a *sqlx.Row instead.
Richard Gibson71e31362016-11-14 17:42:3363// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2964func (n *NamedStmt) QueryRow(arg interface{}) *Row {
Jason Moiron2f383ca2014-07-23 04:18:3565 args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
Jason Moiron8e788cf2014-02-20 05:15:2966 if err != nil {
67 return &Row{err: err}
68 }
69 return n.Stmt.QueryRowx(args...)
70}
71
Jason Moiron8e788cf2014-02-20 05:15:2972// MustExec execs a NamedStmt, panicing on error
Richard Gibson71e31362016-11-14 17:42:3373// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2974func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
75 res, err := n.Exec(arg)
76 if err != nil {
77 panic(err)
78 }
79 return res
80}
81
82// Queryx using this NamedStmt
Richard Gibson71e31362016-11-14 17:42:3383// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2984func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
85 r, err := n.Query(arg)
86 if err != nil {
87 return nil, err
88 }
Jason Moiron92e33302015-11-14 05:49:0689 return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
Jason Moiron8e788cf2014-02-20 05:15:2990}
91
92// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
93// an alias for QueryRow.
Richard Gibson71e31362016-11-14 17:42:3394// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:2995func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
96 return n.QueryRow(arg)
97}
98
99// Select using this NamedStmt
Richard Gibson71e31362016-11-14 17:42:33100// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:29101func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
Jason Moiron92e33302015-11-14 05:49:06102 rows, err := n.Queryx(arg)
Jason Moiron8e788cf2014-02-20 05:15:29103 if err != nil {
104 return err
105 }
106 // if something happens here, we want to make sure the rows are Closed
107 defer rows.Close()
Jason Moironc7e35f72014-09-08 15:33:03108 return scanAll(rows, dest, false)
Jason Moiron8e788cf2014-02-20 05:15:29109}
110
Jason Moiron8e788cf2014-02-20 05:15:29111// Get using this NamedStmt
Richard Gibson71e31362016-11-14 17:42:33112// Any named placeholder parameters are replaced with fields from arg.
Jason Moiron8e788cf2014-02-20 05:15:29113func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
114 r := n.QueryRowx(arg)
Jason Moironc7e35f72014-09-08 15:33:03115 return r.scanAny(dest, false)
Jason Moiron8e788cf2014-02-20 05:15:29116}
117
Jason Moiron92e33302015-11-14 05:49:06118// Unsafe creates an unsafe version of the NamedStmt
119func (n *NamedStmt) Unsafe() *NamedStmt {
120 r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
121 r.Stmt.unsafe = true
122 return r
123}
124
Jason Moiron8e788cf2014-02-20 05:15:29125// A union interface of preparer and binder, required to be able to prepare
126// named statements (as the bindtype must be determined).
127type namedPreparer interface {
128 Preparer
Jason Moiron16e5f492014-05-18 06:19:42129 binder
Jason Moiron8e788cf2014-02-20 05:15:29130}
131
132func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
133 bindType := BindType(p.DriverName())
134 q, args, err := compileNamedQuery([]byte(query), bindType)
135 if err != nil {
136 return nil, err
137 }
138 stmt, err := Preparex(p, q)
139 if err != nil {
140 return nil, err
141 }
142 return &NamedStmt{
143 QueryString: q,
144 Params: args,
145 Stmt: stmt,
146 }, nil
147}
148
Jason Moironb42561b2021-01-24 21:51:42149// convertMapStringInterface attempts to convert v to map[string]interface{}.
150// Unlike v.(map[string]interface{}), this function works on named types that
151// are convertible to map[string]interface{} as well.
152func convertMapStringInterface(v interface{}) (map[string]interface{}, bool) {
153 var m map[string]interface{}
154 mtype := reflect.TypeOf(m)
155 t := reflect.TypeOf(v)
156 if !t.ConvertibleTo(mtype) {
157 return nil, false
158 }
159 return reflect.ValueOf(v).Convert(mtype).Interface().(map[string]interface{}), true
160
161}
162
Jason Moiron2f383ca2014-07-23 04:18:35163func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
Jason Moironb42561b2021-01-24 21:51:42164 if maparg, ok := convertMapStringInterface(arg); ok {
Jason Moironf62875c2014-03-16 20:53:32165 return bindMapArgs(names, maparg)
166 }
Jason Moiron2f383ca2014-07-23 04:18:35167 return bindArgs(names, arg, m)
Jason Moironf62875c2014-03-16 20:53:32168}
169
Jason Moiron8e788cf2014-02-20 05:15:29170// private interface to generate a list of interfaces from a given struct
171// type, given a list of names to pull out of the struct. Used by public
172// BindStruct interface.
Jason Moiron2f383ca2014-07-23 04:18:35173func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
Jason Moiron4f886132014-02-19 06:01:05174 arglist := make([]interface{}, 0, len(names))
175
Jason Moiron4f886132014-02-19 06:01:05176 // grab the indirected value of arg
177 v := reflect.ValueOf(arg)
178 for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
179 v = v.Elem()
180 }
181
Justin Nuß2824d912017-09-28 18:06:34182 err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
Jason Moiron5bdae992014-05-17 23:59:01183 if len(t) == 0 {
Justin Nuß2824d912017-09-28 18:06:34184 return fmt.Errorf("could not find name %s in %#v", names[i], arg)
Jason Moiron4f886132014-02-19 06:01:05185 }
Justin Nuß2824d912017-09-28 18:06:34186
Jason Moiron5cb0c842014-06-28 14:19:42187 val := reflectx.FieldByIndexesReadOnly(v, t)
Jason Moiron5bdae992014-05-17 23:59:01188 arglist = append(arglist, val.Interface())
Jason Moiron4f886132014-02-19 06:01:05189
Justin Nuß2824d912017-09-28 18:06:34190 return nil
191 })
192
193 return arglist, err
Jason Moiron8e788cf2014-02-20 05:15:29194}
195
Jason Moironf62875c2014-03-16 20:53:32196// like bindArgs, but for maps.
197func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
198 arglist := make([]interface{}, 0, len(names))
199
200 for _, name := range names {
201 val, ok := arg[name]
202 if !ok {
Jason Moiron2f383ca2014-07-23 04:18:35203 return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
Jason Moironf62875c2014-03-16 20:53:32204 }
205 arglist = append(arglist, val)
206 }
207 return arglist, nil
208}
209
Jason Moiron16e5f492014-05-18 06:19:42210// bindStruct binds a named parameter query with fields from a struct argument.
Jason Moiron8e788cf2014-02-20 05:15:29211// The rules for binding field names to parameter names follow the same
212// conventions as for StructScan, including obeying the `db` struct tags.
Jason Moiron2f383ca2014-07-23 04:18:35213func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
Jason Moiron8e788cf2014-02-20 05:15:29214 bound, names, err := compileNamedQuery([]byte(query), bindType)
215 if err != nil {
216 return "", []interface{}{}, err
217 }
218
Jason Moironba0e7e72021-01-24 21:39:09219 arglist, err := bindAnyArgs(names, arg, m)
Jason Moiron8e788cf2014-02-20 05:15:29220 if err != nil {
221 return "", []interface{}{}, err
222 }
223
Jason Moiron4f886132014-02-19 06:01:05224 return bound, arglist, nil
225}
226
Alan Braithwaite6258c9a2021-03-31 01:40:58227var valueBracketReg = regexp.MustCompile(`VALUES\s+(\([^(]*.[^(]\))`)
mingang.he018bd2d2017-02-25 09:19:11228
229func fixBound(bound string, loop int) string {
Alan Braithwaite6258c9a2021-03-31 01:40:58230 loc := valueBracketReg.FindAllStringSubmatchIndex(bound, -1)
231 // Either no VALUES () found or more than one found??
232 if len(loc) != 1 {
233 return bound
234 }
235 // defensive guard. loc should be len 4 representing the starting and
236 // ending index for the whole regex match and the starting + ending
237 // index for the single inside group
238 if len(loc[0]) != 4 {
mingang.he018bd2d2017-02-25 09:19:11239 return bound
240 }
241 var buffer bytes.Buffer
Bret Palssonc96cee42019-04-23 16:30:42242
Alan Braithwaite6258c9a2021-03-31 01:40:58243 buffer.WriteString(bound[0:loc[0][1]])
mingang.he018bd2d2017-02-25 09:19:11244 for i := 0; i < loop-1; i++ {
245 buffer.WriteString(",")
Alan Braithwaite6258c9a2021-03-31 01:40:58246 buffer.WriteString(bound[loc[0][2]:loc[0][3]])
mingang.he018bd2d2017-02-25 09:19:11247 }
Alan Braithwaite6258c9a2021-03-31 01:40:58248 buffer.WriteString(bound[loc[0][1]:])
mingang.he018bd2d2017-02-25 09:19:11249 return buffer.String()
250}
251
252// bindArray binds a named parameter query with fields from an array or slice of
253// structs argument.
254func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
Jason Moironfc668fe2019-03-19 04:35:31255 // do the initial binding with QUESTION; if bindType is not question,
256 // we can rebind it at the end.
257 bound, names, err := compileNamedQuery([]byte(query), QUESTION)
mingang.he018bd2d2017-02-25 09:19:11258 if err != nil {
259 return "", []interface{}{}, err
260 }
261 arrayValue := reflect.ValueOf(arg)
262 arrayLen := arrayValue.Len()
mingang.he22027ea2017-02-25 09:42:40263 if arrayLen == 0 {
264 return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg)
265 }
Grigorii Sokolik056d01c2020-06-13 18:45:18266 var arglist = make([]interface{}, 0, len(names)*arrayLen)
mingang.he018bd2d2017-02-25 09:19:11267 for i := 0; i < arrayLen; i++ {
Shunsuke Suzukic254cb12020-05-05 09:47:41268 elemArglist, err := bindAnyArgs(names, arrayValue.Index(i).Interface(), m)
mingang.he018bd2d2017-02-25 09:19:11269 if err != nil {
270 return "", []interface{}{}, err
271 }
272 arglist = append(arglist, elemArglist...)
273 }
274 if arrayLen > 1 {
275 bound = fixBound(bound, arrayLen)
276 }
Jason Moironfc668fe2019-03-19 04:35:31277 // adjust binding type if we weren't on question
278 if bindType != QUESTION {
279 bound = Rebind(bindType, bound)
280 }
mingang.he018bd2d2017-02-25 09:19:11281 return bound, arglist, nil
282}
283
Jason Moiron16e5f492014-05-18 06:19:42284// bindMap binds a named parameter query with a map of arguments.
285func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
Jason Moiron4f886132014-02-19 06:01:05286 bound, names, err := compileNamedQuery([]byte(query), bindType)
287 if err != nil {
288 return "", []interface{}{}, err
289 }
290
Jason Moironf62875c2014-03-16 20:53:32291 arglist, err := bindMapArgs(names, args)
292 return bound, arglist, err
Jason Moiron4f886132014-02-19 06:01:05293}
294
295// -- Compilation of Named Queries
296
297// Allow digits and letters in bind params; additionally runes are
298// checked against underscores, meaning that bind params can have be
299// alphanumeric with underscores. Mind the difference between unicode
300// digits and numbers, where '5' is a digit but '五' is not.
301var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
302
303// FIXME: this function isn't safe for unicode named params, as a failing test
304// can testify. This is not a regression but a failure of the original code
305// as well. It should be modified to range over runes in a string rather than
306// bytes, even though this is less convenient and slower. Hopefully the
307// addition of the prepared NamedStmt (which will only do this once) will make
308// up for the slightly slower ad-hoc NamedExec/NamedQuery.
309
310// compile a NamedQuery into an unbound query (using the '?' bindvar) and
311// a list of names.
312func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
313 names = make([]string, 0, 10)
314 rebound := make([]byte, 0, len(qs))
315
316 inName := false
317 last := len(qs) - 1
318 currentVar := 1
319 name := make([]byte, 0, 10)
320
321 for i, b := range qs {
322 // a ':' while we're in a name is an error
Jason Moironaa6f1962014-09-28 19:31:24323 if b == ':' {
324 // if this is the second ':' in a '::' escape sequence, append a ':'
325 if inName && i > 0 && qs[i-1] == ':' {
326 rebound = append(rebound, ':')
327 inName = false
328 continue
329 } else if inName {
330 err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
331 return query, names, err
332 }
Jason Moiron4f886132014-02-19 06:01:05333 inName = true
334 name = []byte{}
elvizlai30e03142018-11-12 10:19:32335 } else if inName && i > 0 && b == '=' && len(name) == 0 {
Prasanjit Prakash8ed02632018-09-27 11:45:09336 rebound = append(rebound, ':', '=')
337 inName = false
338 continue
Jason Moiron4f886132014-02-19 06:01:05339 // if we're in a name, and this is an allowed character, continue
Cameron Smith6029aba2016-05-12 18:01:55340 } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
Jason Moiron4f886132014-02-19 06:01:05341 // append the byte to the name if we are in a name and not on the last byte
342 name = append(name, b)
343 // if we're in a name and it's not an allowed character, the name is done
344 } else if inName {
345 inName = false
346 // if this is the final byte of the string and it is part of the name, then
347 // make sure to add it to the name
348 if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
349 name = append(name, b)
350 }
Jesse Szwedko95d2fd92014-04-08 17:24:10351 // add the string representation to the names list
Jason Moiron4f886132014-02-19 06:01:05352 names = append(names, string(name))
Jason Moiron4f886132014-02-19 06:01:05353 // add a proper bindvar for the bindType
354 switch bindType {
Jason Moiron40dceae2014-03-15 18:17:47355 // oracle only supports named type bind vars even for positional
356 case NAMED:
357 rebound = append(rebound, ':')
358 rebound = append(rebound, name...)
Jason Moiron4f886132014-02-19 06:01:05359 case QUESTION, UNKNOWN:
360 rebound = append(rebound, '?')
361 case DOLLAR:
362 rebound = append(rebound, '$')
363 for _, b := range strconv.Itoa(currentVar) {
364 rebound = append(rebound, byte(b))
365 }
366 currentVar++
marpiofb9af312018-04-15 18:56:12367 case AT:
368 rebound = append(rebound, '@', 'p')
369 for _, b := range strconv.Itoa(currentVar) {
370 rebound = append(rebound, byte(b))
371 }
372 currentVar++
Jason Moiron4f886132014-02-19 06:01:05373 }
374 // add this byte to string unless it was not part of the name
375 if i != last {
376 rebound = append(rebound, b)
377 } else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
378 rebound = append(rebound, b)
379 }
380 } else {
381 // this is a normal byte and should just go onto the rebound query
382 rebound = append(rebound, b)
383 }
384 }
385
386 return string(rebound), names, err
387}
Jason Moiron96e723e2014-03-16 15:51:10388
Jason Moirona88aa932015-05-03 22:57:09389// BindNamed binds a struct or a map to a query with named parameters.
390// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
Jason Moiron27e05d72015-04-11 14:18:53391func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
392 return bindNamedMapper(bindType, query, arg, mapper())
393}
394
395// Named takes a query using named parameters and an argument and
396// returns a new query with a list of args that can be executed by
397// a database. The return value uses the `?` bindvar.
398func Named(query string, arg interface{}) (string, []interface{}, error) {
Jason Moironb0e8a202015-04-07 03:06:37399 return bindNamedMapper(QUESTION, query, arg, mapper())
Jason Moiron2f383ca2014-07-23 04:18:35400}
401
402func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
Grigorii Sokolik056d01c2020-06-13 18:45:18403 t := reflect.TypeOf(arg)
404 k := t.Kind()
405 switch {
406 case k == reflect.Map && t.Key().Kind() == reflect.String:
Jason Moironb42561b2021-01-24 21:51:42407 m, ok := convertMapStringInterface(arg)
408 if !ok {
Martin Tournoijc5cc0d92020-12-28 12:30:11409 return "", nil, fmt.Errorf("sqlx.bindNamedMapper: unsupported map type: %T", arg)
410 }
Martin Tournoijc5cc0d92020-12-28 12:30:11411 return bindMap(bindType, query, m)
Grigorii Sokolik056d01c2020-06-13 18:45:18412 case k == reflect.Array || k == reflect.Slice:
mingang.he018bd2d2017-02-25 09:19:11413 return bindArray(bindType, query, arg, m)
414 default:
415 return bindStruct(bindType, query, arg, m)
416 }
Jason Moiron96e723e2014-03-16 15:51:10417}
418
419// NamedQuery binds a named query and then runs Query on the result using the
420// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
421// map[string]interface{} types.
422func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
Jason Moiron2f383ca2014-07-23 04:18:35423 q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
Jason Moiron96e723e2014-03-16 15:51:10424 if err != nil {
425 return nil, err
426 }
427 return e.Queryx(q, args...)
428}
429
430// NamedExec uses BindStruct to get a query executable by the driver and
431// then runs Exec on the result. Returns an error from the binding
tockn6b6805d2019-04-08 17:38:04432// or the query execution itself.
Jason Moiron96e723e2014-03-16 15:51:10433func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
Jason Moiron2f383ca2014-07-23 04:18:35434 q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
Jason Moiron96e723e2014-03-16 15:51:10435 if err != nil {
436 return nil, err
437 }
438 return e.Exec(q, args...)
439}