From fbccbe06d2aade7485979f7544efd626fd6dc9ee Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 9 Aug 2018 10:17:21 +0800 Subject: [PATCH] add bindedSQL support --- builder.go | 44 ++------------- builder_test.go | 15 ----- cond.go | 12 ---- error.go | 4 +- sql.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ sql_test.go | 57 +++++++++++++++++++ 6 files changed, 213 insertions(+), 66 deletions(-) create mode 100644 sql.go create mode 100644 sql_test.go diff --git a/builder.go b/builder.go index 07e5e10..ad1e7b3 100644 --- a/builder.go +++ b/builder.go @@ -4,10 +4,6 @@ package builder -import ( - "fmt" -) - type optype byte const ( @@ -228,40 +224,12 @@ func (b *Builder) ToSQL() (string, []interface{}, error) { return w.writer.String(), w.args, nil } -// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix -func ConvertPlaceholder(sql, prefix string) (string, error) { - buf := StringBuilder{} - var j, start = 0, 0 - for i := 0; i < len(sql); i++ { - if sql[i] == '?' { - _, err := buf.WriteString(sql[start:i]) - if err != nil { - return "", err - } - start = i + 1 - - _, err = buf.WriteString(prefix) - if err != nil { - return "", err - } - - j = j + 1 - _, err = buf.WriteString(fmt.Sprintf("%d", j)) - if err != nil { - return "", err - } - } +// ToBindedSQL +func (b *Builder) ToBindedSQL() (string, error) { + w := NewWriter() + if err := b.WriteTo(w); err != nil { + return "", err } - return buf.String(), nil -} -// ToSQL convert a builder or condtions 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 + return ConvertToBindedSQL(w.writer.String(), w.args) } diff --git a/builder_test.go b/builder_test.go index 0670fda..2213d25 100644 --- a/builder_test.go +++ b/builder_test.go @@ -460,18 +460,3 @@ func TestExprCond(t *testing.T) { assert.EqualValues(t, "SELECT id FROM table1 WHERE (a=? OR b=?) AND (c=? OR d=?)", sql) assert.EqualValues(t, []interface{}{1, 2, 3, 4}, args) } - -const placeholderConverterSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=?) AND id=? AND c=? AND d=? AND e=? AND f=?" -const placeholderConvertedSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=$1) AND id=$2 AND c=$3 AND d=$4 AND e=$5 AND f=$6" - -func TestPlaceholderConverter(t *testing.T) { - newSQL, err := ConvertPlaceholder(placeholderConverterSQL, "$") - assert.NoError(t, err) - assert.EqualValues(t, placeholderConvertedSQL, newSQL) -} - -func BenchmarkPlaceholderConverter(b *testing.B) { - for i := 0; i < b.N; i++ { - ConvertPlaceholder(placeholderConverterSQL, "$") - } -} diff --git a/cond.go b/cond.go index c0c2553..e44173b 100644 --- a/cond.go +++ b/cond.go @@ -72,15 +72,3 @@ func (condEmpty) Or(conds ...Cond) Cond { func (condEmpty) IsValid() bool { return false } - -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.writer.String(), w.args, nil -} diff --git a/error.go b/error.go index d7ac51e..dcd2d96 100644 --- a/error.go +++ b/error.go @@ -8,9 +8,11 @@ import "errors" var ( // ErrNotSupportType not supported SQL type error - ErrNotSupportType = errors.New("not supported SQL type") + ErrNotSupportType = errors.New("Not supported SQL type") // ErrNoNotInConditions no NOT IN params error ErrNoNotInConditions = errors.New("No NOT IN conditions") // ErrNoInConditions no IN params error ErrNoInConditions = errors.New("No IN conditions") + // ErrNeedMoreArguments need more arguments + ErrNeedMoreArguments = errors.New("Need more sql arguments") ) diff --git a/sql.go b/sql.go new file mode 100644 index 0000000..f1d1cdd --- /dev/null +++ b/sql.go @@ -0,0 +1,147 @@ +// 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 ( + "fmt" + "reflect" + "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.writer.String(), w.args, nil +} + +func condToBindedSQL(cond Cond) (string, error) { + if cond == nil || !cond.IsValid() { + return "", nil + } + + w := NewWriter() + if err := cond.WriteTo(w); err != nil { + return "", err + } + return ConvertToBindedSQL(w.writer.String(), w.args) +} + +// ToSQL convert a builder or condtions 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 +} + +// ToBindedSQL convert a builder or condtions to parameters binded SQL +func ToBindedSQL(cond interface{}) (string, error) { + switch cond.(type) { + case Cond: + return condToBindedSQL(cond.(Cond)) + case *Builder: + return cond.(*Builder).ToBindedSQL() + } + 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 +} + +// ConvertToBindedSQL will convert SQL and args to a binded SQL +func ConvertToBindedSQL(sql string, args []interface{}) (string, error) { + buf := StringBuilder{} + var j, start = 0, 0 + for i := 0; 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 + } + + if noSQLQuoteNeeded(args[j]) { + _, err = fmt.Fprint(&buf, args[j]) + } else { + _, err = fmt.Fprintf(&buf, "'%v'", args[j]) + } + if err != nil { + return "", err + } + j = j + 1 + } + } + return buf.String(), nil +} + +// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix +func ConvertPlaceholder(sql, prefix string) (string, error) { + buf := StringBuilder{} + var j, start = 0, 0 + for i := 0; i < len(sql); i++ { + if sql[i] == '?' { + _, err := buf.WriteString(sql[start:i]) + if err != nil { + return "", err + } + start = i + 1 + + _, err = buf.WriteString(prefix) + if err != nil { + return "", err + } + + j = j + 1 + _, err = buf.WriteString(fmt.Sprintf("%d", j)) + if err != nil { + return "", err + } + } + } + return buf.String(), nil +} diff --git a/sql_test.go b/sql_test.go new file mode 100644 index 0000000..faefbb8 --- /dev/null +++ b/sql_test.go @@ -0,0 +1,57 @@ +// 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const placeholderConverterSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=?) AND id=? AND c=? AND d=? AND e=? AND f=?" +const placeholderConvertedSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=$1) AND id=$2 AND c=$3 AND d=$4 AND e=$5 AND f=$6" +const placeholderBindedSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=1) AND id=2.1 AND c='3' AND d=4 AND e='5' AND f=true" + +func TestPlaceholderConverter(t *testing.T) { + newSQL, err := ConvertPlaceholder(placeholderConverterSQL, "$") + assert.NoError(t, err) + assert.EqualValues(t, placeholderConvertedSQL, newSQL) +} + +func BenchmarkPlaceholderConverter(b *testing.B) { + for i := 0; i < b.N; i++ { + ConvertPlaceholder(placeholderConverterSQL, "$") + } +} + +func TestBindedSQLConverter(t *testing.T) { + newSQL, err := ConvertToBindedSQL(placeholderConverterSQL, []interface{}{1, 2.1, "3", 4, "5", true}) + assert.NoError(t, err) + assert.EqualValues(t, placeholderBindedSQL, newSQL) + + newSQL, err = ConvertToBindedSQL(placeholderConverterSQL, []interface{}{1, 2.1, "3", 4, "5"}) + assert.Error(t, err) + assert.EqualValues(t, ErrNeedMoreArguments, err) + + newSQL, err = ToBindedSQL(1) + assert.Error(t, err) + assert.EqualValues(t, ErrNotSupportType, err) +} + +func TestSQL(t *testing.T) { + newSQL, args, err := ToSQL(In("a", 1, 2)) + assert.NoError(t, err) + assert.EqualValues(t, "a IN (?,?)", newSQL) + assert.EqualValues(t, []interface{}{1, 2}, args) + + newSQL, args, err = ToSQL(Select("id").From("table").Where(In("a", 1, 2))) + assert.NoError(t, err) + assert.EqualValues(t, "SELECT id FROM table WHERE a IN (?,?)", newSQL) + assert.EqualValues(t, []interface{}{1, 2}, args) + + newSQL, args, err = ToSQL(1) + assert.Error(t, err) + assert.EqualValues(t, ErrNotSupportType, err) +} -- 2.40.1