| 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. |
| 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. |
| 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. |
| 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 |
| func (n *NamedStmt) MustExec(arg interface{}) sql.Result { |
| res, err := n.Exec(arg) |
| if err != nil { |
| panic(err) |
| } |
| return res |
| } |
| |
| // Queryx using this NamedStmt |
| 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. |
| func (n *NamedStmt) QueryRowx(arg interface{}) *Row { |
| return n.QueryRow(arg) |
| } |
| |
| // Select using this NamedStmt |
| 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 |
| 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() |
| } |
| |
| fields := m.TraversalsByName(v.Type(), names) |
| for i, t := range fields { |
| if len(t) == 0 { |
| return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg) |
| } |
| val := reflectx.FieldByIndexesReadOnly(v, t) |
| arglist = append(arglist, val.Interface()) |
| } |
| |
| return arglist, nil |
| } |
| |
| // 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{} |
| // if we're in a name, and this is an allowed character, continue |
| } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(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++ |
| } |
| // 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...) |
| } |