Jay
273278d322
1. add cron module 2. add pool module 3. add tcgplayer background job 4. add card price model
218 lines
5.2 KiB
Go
218 lines
5.2 KiB
Go
package sqlx
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jmoiron/sqlx/reflectx"
|
|
)
|
|
|
|
// Bindvar types supported by Rebind, BindMap and BindStruct.
|
|
const (
|
|
UNKNOWN = iota
|
|
QUESTION
|
|
DOLLAR
|
|
NAMED
|
|
AT
|
|
)
|
|
|
|
// BindType returns the bindtype for a given database given a drivername.
|
|
func BindType(driverName string) int {
|
|
switch driverName {
|
|
case "postgres", "pgx", "pq-timeouts", "cloudsqlpostgres":
|
|
return DOLLAR
|
|
case "mysql":
|
|
return QUESTION
|
|
case "sqlite3":
|
|
return QUESTION
|
|
case "oci8", "ora", "goracle":
|
|
return NAMED
|
|
case "sqlserver":
|
|
return AT
|
|
}
|
|
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
|
|
}
|
|
|
|
// Add space enough for 10 params before we have to allocate
|
|
rqb := make([]byte, 0, len(query)+10)
|
|
|
|
var i, j int
|
|
|
|
for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") {
|
|
rqb = append(rqb, query[:i]...)
|
|
|
|
switch bindType {
|
|
case DOLLAR:
|
|
rqb = append(rqb, '$')
|
|
case NAMED:
|
|
rqb = append(rqb, ':', 'a', 'r', 'g')
|
|
case AT:
|
|
rqb = append(rqb, '@', 'p')
|
|
}
|
|
|
|
j++
|
|
rqb = strconv.AppendInt(rqb, int64(j), 10)
|
|
|
|
query = query[i+1:]
|
|
}
|
|
|
|
return string(append(rqb, query...))
|
|
}
|
|
|
|
// 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 {
|
|
if a, ok := arg.(driver.Valuer); ok {
|
|
arg, _ = a.Value()
|
|
}
|
|
v := reflect.ValueOf(arg)
|
|
t := reflectx.Deref(v.Type())
|
|
|
|
// []byte is a driver.Value type so it should not be expanded
|
|
if t.Kind() == reflect.Slice && t != reflect.TypeOf([]byte{}) {
|
|
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)
|
|
buf := make([]byte, 0, len(query)+len(", ?")*flatArgsCount)
|
|
|
|
var arg, offset int
|
|
|
|
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 = append(buf, query[:offset+i+1]...)
|
|
|
|
for si := 1; si < argMeta.length; si++ {
|
|
buf = append(buf, ", ?"...)
|
|
}
|
|
|
|
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
|
|
|
|
// 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 = append(buf, query...)
|
|
|
|
if arg < len(meta) {
|
|
return "", nil, errors.New("number of bindVars less than number arguments")
|
|
}
|
|
|
|
return string(buf), newArgs, nil
|
|
}
|
|
|
|
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
|
|
switch val := v.Interface().(type) {
|
|
case []interface{}:
|
|
args = append(args, val...)
|
|
case []int:
|
|
for i := range val {
|
|
args = append(args, val[i])
|
|
}
|
|
case []string:
|
|
for i := range val {
|
|
args = append(args, val[i])
|
|
}
|
|
default:
|
|
for si := 0; si < vlen; si++ {
|
|
args = append(args, v.Index(si).Interface())
|
|
}
|
|
}
|
|
|
|
return args
|
|
}
|