Jay
273278d322
1. add cron module 2. add pool module 3. add tcgplayer background job 4. add card price model
357 lines
11 KiB
Go
357 lines
11 KiB
Go
package sqlx
|
|
|
|
// Named Query Support
|
|
//
|
|
// * BindMap - bind query bindvars to map/struct args
|
|
// * NamedExec, NamedQuery - named query w/ struct or map
|
|
// * NamedStmt - a pre-compiled named query which is a prepared statement
|
|
//
|
|
// Internal Interfaces:
|
|
//
|
|
// * compileNamedQuery - rebind a named query, returning a query and list of names
|
|
// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
|
|
//
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"unicode"
|
|
|
|
"github.com/jmoiron/sqlx/reflectx"
|
|
)
|
|
|
|
// NamedStmt is a prepared statement that executes named queries. Prepare it
|
|
// how you would execute a NamedQuery, but pass in a struct or map when executing.
|
|
type NamedStmt struct {
|
|
Params []string
|
|
QueryString string
|
|
Stmt *Stmt
|
|
}
|
|
|
|
// Close closes the named statement.
|
|
func (n *NamedStmt) Close() error {
|
|
return n.Stmt.Close()
|
|
}
|
|
|
|
// Exec executes a named statement using the struct passed.
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
|
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
|
if err != nil {
|
|
return *new(sql.Result), err
|
|
}
|
|
return n.Stmt.Exec(args...)
|
|
}
|
|
|
|
// Query executes a named statement using the struct argument, returning rows.
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
|
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return n.Stmt.Query(args...)
|
|
}
|
|
|
|
// QueryRow executes a named statement against the database. Because sqlx cannot
|
|
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
|
|
// returns a *sqlx.Row instead.
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) QueryRow(arg interface{}) *Row {
|
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
|
if err != nil {
|
|
return &Row{err: err}
|
|
}
|
|
return n.Stmt.QueryRowx(args...)
|
|
}
|
|
|
|
// MustExec execs a NamedStmt, panicing on error
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
|
|
res, err := n.Exec(arg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Queryx using this NamedStmt
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
|
|
r, err := n.Query(arg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
|
|
}
|
|
|
|
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
|
|
// an alias for QueryRow.
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
|
|
return n.QueryRow(arg)
|
|
}
|
|
|
|
// Select using this NamedStmt
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
|
|
rows, err := n.Queryx(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// if something happens here, we want to make sure the rows are Closed
|
|
defer rows.Close()
|
|
return scanAll(rows, dest, false)
|
|
}
|
|
|
|
// Get using this NamedStmt
|
|
// Any named placeholder parameters are replaced with fields from arg.
|
|
func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
|
|
r := n.QueryRowx(arg)
|
|
return r.scanAny(dest, false)
|
|
}
|
|
|
|
// Unsafe creates an unsafe version of the NamedStmt
|
|
func (n *NamedStmt) Unsafe() *NamedStmt {
|
|
r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
|
|
r.Stmt.unsafe = true
|
|
return r
|
|
}
|
|
|
|
// A union interface of preparer and binder, required to be able to prepare
|
|
// named statements (as the bindtype must be determined).
|
|
type namedPreparer interface {
|
|
Preparer
|
|
binder
|
|
}
|
|
|
|
func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
|
|
bindType := BindType(p.DriverName())
|
|
q, args, err := compileNamedQuery([]byte(query), bindType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stmt, err := Preparex(p, q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &NamedStmt{
|
|
QueryString: q,
|
|
Params: args,
|
|
Stmt: stmt,
|
|
}, nil
|
|
}
|
|
|
|
func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
|
if maparg, ok := arg.(map[string]interface{}); ok {
|
|
return bindMapArgs(names, maparg)
|
|
}
|
|
return bindArgs(names, arg, m)
|
|
}
|
|
|
|
// private interface to generate a list of interfaces from a given struct
|
|
// type, given a list of names to pull out of the struct. Used by public
|
|
// BindStruct interface.
|
|
func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
|
arglist := make([]interface{}, 0, len(names))
|
|
|
|
// grab the indirected value of arg
|
|
v := reflect.ValueOf(arg)
|
|
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
|
|
v = v.Elem()
|
|
}
|
|
|
|
err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
|
|
if len(t) == 0 {
|
|
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
|
|
}
|
|
|
|
val := reflectx.FieldByIndexesReadOnly(v, t)
|
|
arglist = append(arglist, val.Interface())
|
|
|
|
return nil
|
|
})
|
|
|
|
return arglist, err
|
|
}
|
|
|
|
// like bindArgs, but for maps.
|
|
func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
|
|
arglist := make([]interface{}, 0, len(names))
|
|
|
|
for _, name := range names {
|
|
val, ok := arg[name]
|
|
if !ok {
|
|
return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
|
|
}
|
|
arglist = append(arglist, val)
|
|
}
|
|
return arglist, nil
|
|
}
|
|
|
|
// bindStruct binds a named parameter query with fields from a struct argument.
|
|
// The rules for binding field names to parameter names follow the same
|
|
// conventions as for StructScan, including obeying the `db` struct tags.
|
|
func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
|
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
|
if err != nil {
|
|
return "", []interface{}{}, err
|
|
}
|
|
|
|
arglist, err := bindArgs(names, arg, m)
|
|
if err != nil {
|
|
return "", []interface{}{}, err
|
|
}
|
|
|
|
return bound, arglist, nil
|
|
}
|
|
|
|
// bindMap binds a named parameter query with a map of arguments.
|
|
func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
|
|
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
|
if err != nil {
|
|
return "", []interface{}{}, err
|
|
}
|
|
|
|
arglist, err := bindMapArgs(names, args)
|
|
return bound, arglist, err
|
|
}
|
|
|
|
// -- Compilation of Named Queries
|
|
|
|
// Allow digits and letters in bind params; additionally runes are
|
|
// checked against underscores, meaning that bind params can have be
|
|
// alphanumeric with underscores. Mind the difference between unicode
|
|
// digits and numbers, where '5' is a digit but '五' is not.
|
|
var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
|
|
|
|
// FIXME: this function isn't safe for unicode named params, as a failing test
|
|
// can testify. This is not a regression but a failure of the original code
|
|
// as well. It should be modified to range over runes in a string rather than
|
|
// bytes, even though this is less convenient and slower. Hopefully the
|
|
// addition of the prepared NamedStmt (which will only do this once) will make
|
|
// up for the slightly slower ad-hoc NamedExec/NamedQuery.
|
|
|
|
// compile a NamedQuery into an unbound query (using the '?' bindvar) and
|
|
// a list of names.
|
|
func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
|
|
names = make([]string, 0, 10)
|
|
rebound := make([]byte, 0, len(qs))
|
|
|
|
inName := false
|
|
last := len(qs) - 1
|
|
currentVar := 1
|
|
name := make([]byte, 0, 10)
|
|
|
|
for i, b := range qs {
|
|
// a ':' while we're in a name is an error
|
|
if b == ':' {
|
|
// if this is the second ':' in a '::' escape sequence, append a ':'
|
|
if inName && i > 0 && qs[i-1] == ':' {
|
|
rebound = append(rebound, ':')
|
|
inName = false
|
|
continue
|
|
} else if inName {
|
|
err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
|
|
return query, names, err
|
|
}
|
|
inName = true
|
|
name = []byte{}
|
|
} else if inName && i > 0 && b == '=' {
|
|
rebound = append(rebound, ':', '=')
|
|
inName = false
|
|
continue
|
|
// if we're in a name, and this is an allowed character, continue
|
|
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
|
|
// append the byte to the name if we are in a name and not on the last byte
|
|
name = append(name, b)
|
|
// if we're in a name and it's not an allowed character, the name is done
|
|
} else if inName {
|
|
inName = false
|
|
// if this is the final byte of the string and it is part of the name, then
|
|
// make sure to add it to the name
|
|
if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
|
name = append(name, b)
|
|
}
|
|
// add the string representation to the names list
|
|
names = append(names, string(name))
|
|
// add a proper bindvar for the bindType
|
|
switch bindType {
|
|
// oracle only supports named type bind vars even for positional
|
|
case NAMED:
|
|
rebound = append(rebound, ':')
|
|
rebound = append(rebound, name...)
|
|
case QUESTION, UNKNOWN:
|
|
rebound = append(rebound, '?')
|
|
case DOLLAR:
|
|
rebound = append(rebound, '$')
|
|
for _, b := range strconv.Itoa(currentVar) {
|
|
rebound = append(rebound, byte(b))
|
|
}
|
|
currentVar++
|
|
case AT:
|
|
rebound = append(rebound, '@', 'p')
|
|
for _, b := range strconv.Itoa(currentVar) {
|
|
rebound = append(rebound, byte(b))
|
|
}
|
|
currentVar++
|
|
}
|
|
// add this byte to string unless it was not part of the name
|
|
if i != last {
|
|
rebound = append(rebound, b)
|
|
} else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
|
rebound = append(rebound, b)
|
|
}
|
|
} else {
|
|
// this is a normal byte and should just go onto the rebound query
|
|
rebound = append(rebound, b)
|
|
}
|
|
}
|
|
|
|
return string(rebound), names, err
|
|
}
|
|
|
|
// BindNamed binds a struct or a map to a query with named parameters.
|
|
// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
|
|
func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
|
|
return bindNamedMapper(bindType, query, arg, mapper())
|
|
}
|
|
|
|
// Named takes a query using named parameters and an argument and
|
|
// returns a new query with a list of args that can be executed by
|
|
// a database. The return value uses the `?` bindvar.
|
|
func Named(query string, arg interface{}) (string, []interface{}, error) {
|
|
return bindNamedMapper(QUESTION, query, arg, mapper())
|
|
}
|
|
|
|
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
|
if maparg, ok := arg.(map[string]interface{}); ok {
|
|
return bindMap(bindType, query, maparg)
|
|
}
|
|
return bindStruct(bindType, query, arg, m)
|
|
}
|
|
|
|
// NamedQuery binds a named query and then runs Query on the result using the
|
|
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
|
|
// map[string]interface{} types.
|
|
func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
|
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return e.Queryx(q, args...)
|
|
}
|
|
|
|
// NamedExec uses BindStruct to get a query executable by the driver and
|
|
// then runs Exec on the result. Returns an error from the binding
|
|
// or the query excution itself.
|
|
func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
|
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return e.Exec(q, args...)
|
|
}
|