// Copyright 2018 The Xorm Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package builder import ( sql2 "database/sql" "fmt" "reflect" "strings" "time" ) func condToSQL(cond Cond) (string, []interface{}, error) { if cond == nil || !cond.IsValid() { return "", nil, nil } w := NewWriter() if err := cond.WriteTo(w); err != nil { return "", nil, err } return w.String(), w.args, nil } func condToBoundSQL(cond Cond) (string, error) { if cond == nil || !cond.IsValid() { return "", nil } w := NewWriter() if err := cond.WriteTo(w); err != nil { return "", err } return ConvertToBoundSQL(w.String(), w.args) } // ToSQL convert a builder or conditions to SQL and args func ToSQL(cond interface{}) (string, []interface{}, error) { switch cond.(type) { case Cond: return condToSQL(cond.(Cond)) case *Builder: return cond.(*Builder).ToSQL() } return "", nil, ErrNotSupportType } // ToBoundSQL convert a builder or conditions to parameters bound SQL func ToBoundSQL(cond interface{}) (string, error) { switch cond.(type) { case Cond: return condToBoundSQL(cond.(Cond)) case *Builder: return cond.(*Builder).ToBoundSQL() } return "", ErrNotSupportType } func noSQLQuoteNeeded(a interface{}) bool { switch a.(type) { case int, int8, int16, int32, int64: return true case uint, uint8, uint16, uint32, uint64: return true case float32, float64: return true case bool: return true case string: return false case time.Time, *time.Time: return false } t := reflect.TypeOf(a) switch t.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return true case reflect.Float32, reflect.Float64: return true case reflect.Bool: return true case reflect.String: return false } return false } // ConvertToBoundSQL will convert SQL and args to a bound SQL func ConvertToBoundSQL(sql string, args []interface{}) (string, error) { buf := strings.Builder{} var i, j, start int for ; i < len(sql); i++ { if sql[i] == '?' { _, err := buf.WriteString(sql[start:i]) if err != nil { return "", err } start = i + 1 if len(args) == j { return "", ErrNeedMoreArguments } arg := args[j] if namedArg, ok := arg.(sql2.NamedArg); ok { arg = namedArg.Value } if noSQLQuoteNeeded(arg) { _, err = fmt.Fprint(&buf, arg) } else { // replace ' -> '' (standard replacement) to avoid critical SQL injection, // NOTICE: may allow some injection like % (or _) in LIKE query _, err = fmt.Fprintf(&buf, "'%v'", strings.Replace(fmt.Sprintf("%v", arg), "'", "''", -1)) } if err != nil { return "", err } j = j + 1 } } _, err := buf.WriteString(sql[start:]) if err != nil { return "", err } return buf.String(), nil } // ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix func ConvertPlaceholder(sql, prefix string) (string, error) { buf := strings.Builder{} var i, j, start int for ; i < len(sql); i++ { if sql[i] == '?' { if _, err := buf.WriteString(sql[start:i]); err != nil { return "", err } start = i + 1 j = j + 1 if _, err := buf.WriteString(fmt.Sprintf("%v%d", prefix, j)); err != nil { return "", err } } } if _, err := buf.WriteString(sql[start:]); err != nil { return "", err } return buf.String(), nil }