| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 1 | package sqlx |
| 2 | |
| 3 | // Named Query Support |
| 4 | // |
| Jason Moiron | 16e5f49 | 2014-05-18 06:19:42 | [diff] [blame] | 5 | // * BindMap - bind query bindvars to map/struct args |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 6 | // * NamedExec, NamedQuery - named query w/ struct or map |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 7 | // * 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 Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 12 | // * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 13 | // |
| 14 | import ( |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 15 | "bytes" |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 16 | "database/sql" |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 17 | "errors" |
| 18 | "fmt" |
| 19 | "reflect" |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 20 | "regexp" |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 21 | "strconv" |
| 22 | "unicode" |
| Jason Moiron | 5bdae99 | 2014-05-17 23:59:01 | [diff] [blame] | 23 | |
| Jason Moiron | 2b1877d | 2014-05-18 00:44:49 | [diff] [blame] | 24 | "github.com/jmoiron/sqlx/reflectx" |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 25 | ) |
| 26 | |
| 27 | // NamedStmt is a prepared statement that executes named queries. Prepare it |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 28 | // how you would execute a NamedQuery, but pass in a struct or map when executing. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 29 | type NamedStmt struct { |
| 30 | Params []string |
| 31 | QueryString string |
| 32 | Stmt *Stmt |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 33 | } |
| 34 | |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 35 | // Close closes the named statement. |
| 36 | func (n *NamedStmt) Close() error { |
| 37 | return n.Stmt.Close() |
| 38 | } |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 39 | |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 40 | // Exec executes a named statement using the struct passed. |
| Richard Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 41 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 42 | func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) { |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 43 | args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 44 | 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 Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 51 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 52 | func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) { |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 53 | args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 54 | 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 Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 63 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 64 | func (n *NamedStmt) QueryRow(arg interface{}) *Row { |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 65 | args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 66 | if err != nil { |
| 67 | return &Row{err: err} |
| 68 | } |
| 69 | return n.Stmt.QueryRowx(args...) |
| 70 | } |
| 71 | |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 72 | // MustExec execs a NamedStmt, panicing on error |
| Richard Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 73 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 74 | func (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 Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 83 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 84 | func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) { |
| 85 | r, err := n.Query(arg) |
| 86 | if err != nil { |
| 87 | return nil, err |
| 88 | } |
| Jason Moiron | 92e3330 | 2015-11-14 05:49:06 | [diff] [blame] | 89 | return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 90 | } |
| 91 | |
| 92 | // QueryRowx this NamedStmt. Because of limitations with QueryRow, this is |
| 93 | // an alias for QueryRow. |
| Richard Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 94 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 95 | func (n *NamedStmt) QueryRowx(arg interface{}) *Row { |
| 96 | return n.QueryRow(arg) |
| 97 | } |
| 98 | |
| 99 | // Select using this NamedStmt |
| Richard Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 100 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 101 | func (n *NamedStmt) Select(dest interface{}, arg interface{}) error { |
| Jason Moiron | 92e3330 | 2015-11-14 05:49:06 | [diff] [blame] | 102 | rows, err := n.Queryx(arg) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 103 | 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 Moiron | c7e35f7 | 2014-09-08 15:33:03 | [diff] [blame] | 108 | return scanAll(rows, dest, false) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 109 | } |
| 110 | |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 111 | // Get using this NamedStmt |
| Richard Gibson | 71e3136 | 2016-11-14 17:42:33 | [diff] [blame] | 112 | // Any named placeholder parameters are replaced with fields from arg. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 113 | func (n *NamedStmt) Get(dest interface{}, arg interface{}) error { |
| 114 | r := n.QueryRowx(arg) |
| Jason Moiron | c7e35f7 | 2014-09-08 15:33:03 | [diff] [blame] | 115 | return r.scanAny(dest, false) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 116 | } |
| 117 | |
| Jason Moiron | 92e3330 | 2015-11-14 05:49:06 | [diff] [blame] | 118 | // Unsafe creates an unsafe version of the NamedStmt |
| 119 | func (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 Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 125 | // A union interface of preparer and binder, required to be able to prepare |
| 126 | // named statements (as the bindtype must be determined). |
| 127 | type namedPreparer interface { |
| 128 | Preparer |
| Jason Moiron | 16e5f49 | 2014-05-18 06:19:42 | [diff] [blame] | 129 | binder |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | func 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 Moiron | b42561b | 2021-01-24 21:51:42 | [diff] [blame] | 149 | // 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. |
| 152 | func 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 Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 163 | func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) { |
| Jason Moiron | b42561b | 2021-01-24 21:51:42 | [diff] [blame] | 164 | if maparg, ok := convertMapStringInterface(arg); ok { |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 165 | return bindMapArgs(names, maparg) |
| 166 | } |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 167 | return bindArgs(names, arg, m) |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 168 | } |
| 169 | |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 170 | // 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 Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 173 | func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) { |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 174 | arglist := make([]interface{}, 0, len(names)) |
| 175 | |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 176 | // 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ß | 2824d91 | 2017-09-28 18:06:34 | [diff] [blame] | 182 | err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error { |
| Jason Moiron | 5bdae99 | 2014-05-17 23:59:01 | [diff] [blame] | 183 | if len(t) == 0 { |
| Justin Nuß | 2824d91 | 2017-09-28 18:06:34 | [diff] [blame] | 184 | return fmt.Errorf("could not find name %s in %#v", names[i], arg) |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 185 | } |
| Justin Nuß | 2824d91 | 2017-09-28 18:06:34 | [diff] [blame] | 186 | |
| Jason Moiron | 5cb0c84 | 2014-06-28 14:19:42 | [diff] [blame] | 187 | val := reflectx.FieldByIndexesReadOnly(v, t) |
| Jason Moiron | 5bdae99 | 2014-05-17 23:59:01 | [diff] [blame] | 188 | arglist = append(arglist, val.Interface()) |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 189 | |
| Justin Nuß | 2824d91 | 2017-09-28 18:06:34 | [diff] [blame] | 190 | return nil |
| 191 | }) |
| 192 | |
| 193 | return arglist, err |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 194 | } |
| 195 | |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 196 | // like bindArgs, but for maps. |
| 197 | func 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 Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 203 | return arglist, fmt.Errorf("could not find name %s in %#v", name, arg) |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 204 | } |
| 205 | arglist = append(arglist, val) |
| 206 | } |
| 207 | return arglist, nil |
| 208 | } |
| 209 | |
| Jason Moiron | 16e5f49 | 2014-05-18 06:19:42 | [diff] [blame] | 210 | // bindStruct binds a named parameter query with fields from a struct argument. |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 211 | // The rules for binding field names to parameter names follow the same |
| 212 | // conventions as for StructScan, including obeying the `db` struct tags. |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 213 | func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 214 | bound, names, err := compileNamedQuery([]byte(query), bindType) |
| 215 | if err != nil { |
| 216 | return "", []interface{}{}, err |
| 217 | } |
| 218 | |
| Jason Moiron | ba0e7e7 | 2021-01-24 21:39:09 | [diff] [blame] | 219 | arglist, err := bindAnyArgs(names, arg, m) |
| Jason Moiron | 8e788cf | 2014-02-20 05:15:29 | [diff] [blame] | 220 | if err != nil { |
| 221 | return "", []interface{}{}, err |
| 222 | } |
| 223 | |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 224 | return bound, arglist, nil |
| 225 | } |
| 226 | |
| Alan Braithwaite | 6258c9a | 2021-03-31 01:40:58 | [diff] [blame] | 227 | var valueBracketReg = regexp.MustCompile(`VALUES\s+(\([^(]*.[^(]\))`) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 228 | |
| 229 | func fixBound(bound string, loop int) string { |
| Alan Braithwaite | 6258c9a | 2021-03-31 01:40:58 | [diff] [blame] | 230 | 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.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 239 | return bound |
| 240 | } |
| 241 | var buffer bytes.Buffer |
| Bret Palsson | c96cee4 | 2019-04-23 16:30:42 | [diff] [blame] | 242 | |
| Alan Braithwaite | 6258c9a | 2021-03-31 01:40:58 | [diff] [blame] | 243 | buffer.WriteString(bound[0:loc[0][1]]) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 244 | for i := 0; i < loop-1; i++ { |
| 245 | buffer.WriteString(",") |
| Alan Braithwaite | 6258c9a | 2021-03-31 01:40:58 | [diff] [blame] | 246 | buffer.WriteString(bound[loc[0][2]:loc[0][3]]) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 247 | } |
| Alan Braithwaite | 6258c9a | 2021-03-31 01:40:58 | [diff] [blame] | 248 | buffer.WriteString(bound[loc[0][1]:]) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 249 | return buffer.String() |
| 250 | } |
| 251 | |
| 252 | // bindArray binds a named parameter query with fields from an array or slice of |
| 253 | // structs argument. |
| 254 | func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { |
| Jason Moiron | fc668fe | 2019-03-19 04:35:31 | [diff] [blame] | 255 | // 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.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 258 | if err != nil { |
| 259 | return "", []interface{}{}, err |
| 260 | } |
| 261 | arrayValue := reflect.ValueOf(arg) |
| 262 | arrayLen := arrayValue.Len() |
| mingang.he | 22027ea | 2017-02-25 09:42:40 | [diff] [blame] | 263 | if arrayLen == 0 { |
| 264 | return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg) |
| 265 | } |
| Grigorii Sokolik | 056d01c | 2020-06-13 18:45:18 | [diff] [blame] | 266 | var arglist = make([]interface{}, 0, len(names)*arrayLen) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 267 | for i := 0; i < arrayLen; i++ { |
| Shunsuke Suzuki | c254cb1 | 2020-05-05 09:47:41 | [diff] [blame] | 268 | elemArglist, err := bindAnyArgs(names, arrayValue.Index(i).Interface(), m) |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 269 | 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 Moiron | fc668fe | 2019-03-19 04:35:31 | [diff] [blame] | 277 | // adjust binding type if we weren't on question |
| 278 | if bindType != QUESTION { |
| 279 | bound = Rebind(bindType, bound) |
| 280 | } |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 281 | return bound, arglist, nil |
| 282 | } |
| 283 | |
| Jason Moiron | 16e5f49 | 2014-05-18 06:19:42 | [diff] [blame] | 284 | // bindMap binds a named parameter query with a map of arguments. |
| 285 | func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) { |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 286 | bound, names, err := compileNamedQuery([]byte(query), bindType) |
| 287 | if err != nil { |
| 288 | return "", []interface{}{}, err |
| 289 | } |
| 290 | |
| Jason Moiron | f62875c | 2014-03-16 20:53:32 | [diff] [blame] | 291 | arglist, err := bindMapArgs(names, args) |
| 292 | return bound, arglist, err |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 293 | } |
| 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. |
| 301 | var 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. |
| 312 | func 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 Moiron | aa6f196 | 2014-09-28 19:31:24 | [diff] [blame] | 323 | 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 Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 333 | inName = true |
| 334 | name = []byte{} |
| elvizlai | 30e0314 | 2018-11-12 10:19:32 | [diff] [blame] | 335 | } else if inName && i > 0 && b == '=' && len(name) == 0 { |
| Prasanjit Prakash | 8ed0263 | 2018-09-27 11:45:09 | [diff] [blame] | 336 | rebound = append(rebound, ':', '=') |
| 337 | inName = false |
| 338 | continue |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 339 | // if we're in a name, and this is an allowed character, continue |
| Cameron Smith | 6029aba | 2016-05-12 18:01:55 | [diff] [blame] | 340 | } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last { |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 341 | // 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 Szwedko | 95d2fd9 | 2014-04-08 17:24:10 | [diff] [blame] | 351 | // add the string representation to the names list |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 352 | names = append(names, string(name)) |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 353 | // add a proper bindvar for the bindType |
| 354 | switch bindType { |
| Jason Moiron | 40dceae | 2014-03-15 18:17:47 | [diff] [blame] | 355 | // oracle only supports named type bind vars even for positional |
| 356 | case NAMED: |
| 357 | rebound = append(rebound, ':') |
| 358 | rebound = append(rebound, name...) |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 359 | 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++ |
| marpio | fb9af31 | 2018-04-15 18:56:12 | [diff] [blame] | 367 | case AT: |
| 368 | rebound = append(rebound, '@', 'p') |
| 369 | for _, b := range strconv.Itoa(currentVar) { |
| 370 | rebound = append(rebound, byte(b)) |
| 371 | } |
| 372 | currentVar++ |
| Jason Moiron | 4f88613 | 2014-02-19 06:01:05 | [diff] [blame] | 373 | } |
| 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 Moiron | 96e723e | 2014-03-16 15:51:10 | [diff] [blame] | 388 | |
| Jason Moiron | a88aa93 | 2015-05-03 22:57:09 | [diff] [blame] | 389 | // 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 Moiron | 27e05d7 | 2015-04-11 14:18:53 | [diff] [blame] | 391 | func 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. |
| 398 | func Named(query string, arg interface{}) (string, []interface{}, error) { |
| Jason Moiron | b0e8a20 | 2015-04-07 03:06:37 | [diff] [blame] | 399 | return bindNamedMapper(QUESTION, query, arg, mapper()) |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 400 | } |
| 401 | |
| 402 | func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { |
| Grigorii Sokolik | 056d01c | 2020-06-13 18:45:18 | [diff] [blame] | 403 | t := reflect.TypeOf(arg) |
| 404 | k := t.Kind() |
| 405 | switch { |
| 406 | case k == reflect.Map && t.Key().Kind() == reflect.String: |
| Jason Moiron | b42561b | 2021-01-24 21:51:42 | [diff] [blame] | 407 | m, ok := convertMapStringInterface(arg) |
| 408 | if !ok { |
| Martin Tournoij | c5cc0d9 | 2020-12-28 12:30:11 | [diff] [blame] | 409 | return "", nil, fmt.Errorf("sqlx.bindNamedMapper: unsupported map type: %T", arg) |
| 410 | } |
| Martin Tournoij | c5cc0d9 | 2020-12-28 12:30:11 | [diff] [blame] | 411 | return bindMap(bindType, query, m) |
| Grigorii Sokolik | 056d01c | 2020-06-13 18:45:18 | [diff] [blame] | 412 | case k == reflect.Array || k == reflect.Slice: |
| mingang.he | 018bd2d | 2017-02-25 09:19:11 | [diff] [blame] | 413 | return bindArray(bindType, query, arg, m) |
| 414 | default: |
| 415 | return bindStruct(bindType, query, arg, m) |
| 416 | } |
| Jason Moiron | 96e723e | 2014-03-16 15:51:10 | [diff] [blame] | 417 | } |
| 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. |
| 422 | func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) { |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 423 | q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) |
| Jason Moiron | 96e723e | 2014-03-16 15:51:10 | [diff] [blame] | 424 | 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 |
| tockn | 6b6805d | 2019-04-08 17:38:04 | [diff] [blame] | 432 | // or the query execution itself. |
| Jason Moiron | 96e723e | 2014-03-16 15:51:10 | [diff] [blame] | 433 | func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) { |
| Jason Moiron | 2f383ca | 2014-07-23 04:18:35 | [diff] [blame] | 434 | q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) |
| Jason Moiron | 96e723e | 2014-03-16 15:51:10 | [diff] [blame] | 435 | if err != nil { |
| 436 | return nil, err |
| 437 | } |
| 438 | return e.Exec(q, args...) |
| 439 | } |