aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/jmoiron/sqlx/bind.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/jmoiron/sqlx/bind.go')
-rw-r--r--vendor/github.com/jmoiron/sqlx/bind.go186
1 files changed, 186 insertions, 0 deletions
diff --git a/vendor/github.com/jmoiron/sqlx/bind.go b/vendor/github.com/jmoiron/sqlx/bind.go
new file mode 100644
index 0000000..53659bc
--- /dev/null
+++ b/vendor/github.com/jmoiron/sqlx/bind.go
@@ -0,0 +1,186 @@
+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
+}