mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			208 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			4.9 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", "pq-timeouts":
 | 
						|
		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
 | 
						|
	}
 | 
						|
 | 
						|
	// 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')
 | 
						|
		}
 | 
						|
 | 
						|
		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 {
 | 
						|
		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)
 | 
						|
	buf := bytes.NewBuffer(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.WriteString(query[:offset+i+1])
 | 
						|
 | 
						|
		for si := 1; si < argMeta.length; si++ {
 | 
						|
			buf.WriteString(", ?")
 | 
						|
		}
 | 
						|
 | 
						|
		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.WriteString(query)
 | 
						|
 | 
						|
	if arg < len(meta) {
 | 
						|
		return "", nil, errors.New("number of bindVars less than number arguments")
 | 
						|
	}
 | 
						|
 | 
						|
	return buf.String(), 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
 | 
						|
}
 |