187 lines
4.4 KiB
Go
187 lines
4.4 KiB
Go
package sqlx
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jmoiron/sqlx/reflectx"
|
|
)
|
|
|
|
// Bindvar types supported by Rebind, BindMap and BindStruct.
|
|
const (
|
|
UNKNOWN = iota
|
|
QUESTION
|
|
DOLLAR
|
|
NAMED
|
|
)
|
|
|
|
// BindType returns the bindtype for a given database given a drivername.
|
|
func BindType(driverName string) int {
|
|
switch driverName {
|
|
case "postgres", "pgx":
|
|
return DOLLAR
|
|
case "mysql":
|
|
return QUESTION
|
|
case "sqlite3":
|
|
return QUESTION
|
|
case "oci8", "ora", "goracle":
|
|
return NAMED
|
|
}
|
|
return UNKNOWN
|
|
}
|
|
|
|
// FIXME: this should be able to be tolerant of escaped ?'s in queries without
|
|
// losing much speed, and should be to avoid confusion.
|
|
|
|
// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
|
|
func Rebind(bindType int, query string) string {
|
|
switch bindType {
|
|
case QUESTION, UNKNOWN:
|
|
return query
|
|
}
|
|
|
|
qb := []byte(query)
|
|
// Add space enough for 10 params before we have to allocate
|
|
rqb := make([]byte, 0, len(qb)+10)
|
|
j := 1
|
|
for _, b := range qb {
|
|
if b == '?' {
|
|
switch bindType {
|
|
case DOLLAR:
|
|
rqb = append(rqb, '$')
|
|
case NAMED:
|
|
rqb = append(rqb, ':', 'a', 'r', 'g')
|
|
}
|
|
for _, b := range strconv.Itoa(j) {
|
|
rqb = append(rqb, byte(b))
|
|
}
|
|
j++
|
|
} else {
|
|
rqb = append(rqb, b)
|
|
}
|
|
}
|
|
return string(rqb)
|
|
}
|
|
|
|
// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
|
|
// much simpler and should be more resistant to odd unicode, but it is twice as
|
|
// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
|
|
// problems arise with its somewhat naive handling of unicode.
|
|
func rebindBuff(bindType int, query string) string {
|
|
if bindType != DOLLAR {
|
|
return query
|
|
}
|
|
|
|
b := make([]byte, 0, len(query))
|
|
rqb := bytes.NewBuffer(b)
|
|
j := 1
|
|
for _, r := range query {
|
|
if r == '?' {
|
|
rqb.WriteRune('$')
|
|
rqb.WriteString(strconv.Itoa(j))
|
|
j++
|
|
} else {
|
|
rqb.WriteRune(r)
|
|
}
|
|
}
|
|
|
|
return rqb.String()
|
|
}
|
|
|
|
// In expands slice values in args, returning the modified query string
|
|
// and a new arg list that can be executed by a database. The `query` should
|
|
// use the `?` bindVar. The return value uses the `?` bindVar.
|
|
func In(query string, args ...interface{}) (string, []interface{}, error) {
|
|
// argMeta stores reflect.Value and length for slices and
|
|
// the value itself for non-slice arguments
|
|
type argMeta struct {
|
|
v reflect.Value
|
|
i interface{}
|
|
length int
|
|
}
|
|
|
|
var flatArgsCount int
|
|
var anySlices bool
|
|
|
|
meta := make([]argMeta, len(args))
|
|
|
|
for i, arg := range args {
|
|
v := reflect.ValueOf(arg)
|
|
t := reflectx.Deref(v.Type())
|
|
|
|
if t.Kind() == reflect.Slice {
|
|
meta[i].length = v.Len()
|
|
meta[i].v = v
|
|
|
|
anySlices = true
|
|
flatArgsCount += meta[i].length
|
|
|
|
if meta[i].length == 0 {
|
|
return "", nil, errors.New("empty slice passed to 'in' query")
|
|
}
|
|
} else {
|
|
meta[i].i = arg
|
|
flatArgsCount++
|
|
}
|
|
}
|
|
|
|
// don't do any parsing if there aren't any slices; note that this means
|
|
// some errors that we might have caught below will not be returned.
|
|
if !anySlices {
|
|
return query, args, nil
|
|
}
|
|
|
|
newArgs := make([]interface{}, 0, flatArgsCount)
|
|
|
|
var arg, offset int
|
|
var buf bytes.Buffer
|
|
|
|
for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
|
|
if arg >= len(meta) {
|
|
// if an argument wasn't passed, lets return an error; this is
|
|
// not actually how database/sql Exec/Query works, but since we are
|
|
// creating an argument list programmatically, we want to be able
|
|
// to catch these programmer errors earlier.
|
|
return "", nil, errors.New("number of bindVars exceeds arguments")
|
|
}
|
|
|
|
argMeta := meta[arg]
|
|
arg++
|
|
|
|
// not a slice, continue.
|
|
// our questionmark will either be written before the next expansion
|
|
// of a slice or after the loop when writing the rest of the query
|
|
if argMeta.length == 0 {
|
|
offset = offset + i + 1
|
|
newArgs = append(newArgs, argMeta.i)
|
|
continue
|
|
}
|
|
|
|
// write everything up to and including our ? character
|
|
buf.WriteString(query[:offset+i+1])
|
|
|
|
newArgs = append(newArgs, argMeta.v.Index(0).Interface())
|
|
|
|
for si := 1; si < argMeta.length; si++ {
|
|
buf.WriteString(", ?")
|
|
newArgs = append(newArgs, argMeta.v.Index(si).Interface())
|
|
}
|
|
|
|
// slice the query and reset the offset. this avoids some bookkeeping for
|
|
// the write after the loop
|
|
query = query[offset+i+1:]
|
|
offset = 0
|
|
}
|
|
|
|
buf.WriteString(query)
|
|
|
|
if arg < len(meta) {
|
|
return "", nil, errors.New("number of bindVars less than number arguments")
|
|
}
|
|
|
|
return buf.String(), newArgs, nil
|
|
}
|