Browse Source

Support use expr cond as update sets

1.Support use expr cond as update sets
2.feat: SQL injection harmless disposal
3.add CondIf interface
Unknown 6 years ago
parent
commit
0ee351fedc
12 changed files with 152 additions and 36 deletions
  1. 12 15
      README.md
  2. 5 5
      builder.go
  3. 9 2
      builder_update.go
  4. 5 0
      builder_update_test.go
  5. 3 2
      cond_eq.go
  6. 4 0
      cond_expr.go
  7. 49 0
      cond_if.go
  8. 38 0
      cond_if_test.go
  9. 11 11
      doc.go
  10. 5 0
      go.mod
  11. 5 1
      sql.go
  12. 6 0
      sql_test.go

+ 12 - 15
README.md

@@ -1,13 +1,10 @@
 # SQL builder
 # SQL builder
 
 
-[![GitCI.cn](https://gitci.cn/api/badges/go-xorm/builder/status.svg)](https://gitci.cn/go-xorm/builder)  [![codecov](https://codecov.io/gh/go-xorm/builder/branch/master/graph/badge.svg)](https://codecov.io/gh/go-xorm/builder)
-[![](https://goreportcard.com/badge/github.com/go-xorm/builder)](https://goreportcard.com/report/github.com/go-xorm/builder)
-
 Package builder is a lightweight and fast SQL builder for Go and XORM.
 Package builder is a lightweight and fast SQL builder for Go and XORM.
 
 
 Make sure you have installed Go 1.8+ and then:
 Make sure you have installed Go 1.8+ and then:
 
 
-    go get github.com/go-xorm/builder
+    go get github.com/xormplus/builder
 
 
 # Insert
 # Insert
 
 
@@ -71,7 +68,7 @@ sql, args, err := Select("*").From("a").Where(Eq{"status": "1"}).
 * `Eq` is a redefine of a map, you can give one or more conditions to `Eq`
 * `Eq` is a redefine of a map, you can give one or more conditions to `Eq`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Eq{"a":1})
 sql, args, _ := ToSQL(Eq{"a":1})
 // a=? [1]
 // a=? [1]
@@ -90,7 +87,7 @@ sql, args, _ := ToSQL(Eq{"b": 1, "c":[]int{2, 3}})
 * `Neq` is the same to `Eq`
 * `Neq` is the same to `Eq`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Neq{"a":1})
 sql, args, _ := ToSQL(Neq{"a":1})
 // a<>? [1]
 // a<>? [1]
@@ -109,7 +106,7 @@ sql, args, _ := ToSQL(Neq{"b": 1, "c":[]int{2, 3}})
 * `Gt`, `Gte`, `Lt`, `Lte`
 * `Gt`, `Gte`, `Lt`, `Lte`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
 sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
 // a>? AND b>=? [1, 2]
 // a>? AND b>=? [1, 2]
@@ -120,7 +117,7 @@ sql, args, _ := ToSQL(Lt{"a", 1}.Or(Lte{"b", 2}))
 * `Like`
 * `Like`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Like{"a", "c"})
 sql, args, _ := ToSQL(Like{"a", "c"})
 // a LIKE ? [%c%]
 // a LIKE ? [%c%]
@@ -129,7 +126,7 @@ sql, args, _ := ToSQL(Like{"a", "c"})
 * `Expr` you can customerize your sql with `Expr`
 * `Expr` you can customerize your sql with `Expr`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Expr("a = ? ", 1))
 sql, args, _ := ToSQL(Expr("a = ? ", 1))
 // a = ? [1]
 // a = ? [1]
@@ -140,7 +137,7 @@ sql, args, _ := ToSQL(Eq{"a": Expr("select id from table where c = ?", 1)})
 * `In` and `NotIn`
 * `In` and `NotIn`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(In("a", 1, 2, 3))
 sql, args, _ := ToSQL(In("a", 1, 2, 3))
 // a IN (?,?,?) [1,2,3]
 // a IN (?,?,?) [1,2,3]
@@ -153,7 +150,7 @@ sql, args, _ := ToSQL(In("a", Expr("select id from b where c = ?", 1))))
 * `IsNull` and `NotNull`
 * `IsNull` and `NotNull`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(IsNull{"a"})
 sql, args, _ := ToSQL(IsNull{"a"})
 // a IS NULL []
 // a IS NULL []
@@ -164,7 +161,7 @@ sql, args, _ := ToSQL(NotNull{"b"})
 * `And(conds ...Cond)`, And can connect one or more condtions via And
 * `And(conds ...Cond)`, And can connect one or more condtions via And
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
 // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
@@ -173,7 +170,7 @@ sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 * `Or(conds ...Cond)`, Or can connect one or more conditions via Or
 * `Or(conds ...Cond)`, Or can connect one or more conditions via Or
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
 // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
@@ -184,7 +181,7 @@ sql, args, _ := ToSQL(Or(Eq{"a":1}, And(Like{"b", "c"}, Neq{"d", 2})))
 * `Between`
 * `Between`
 
 
 ```Go
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 
 sql, args, _ := ToSQL(Between{"a", 1, 2})
 sql, args, _ := ToSQL(Between{"a", 1, 2})
 // a BETWEEN 1 AND 2
 // a BETWEEN 1 AND 2
@@ -203,4 +200,4 @@ type Cond interface {
 }
 }
 ```
 ```
 
 
-You can define yourself conditions and compose with other `Cond`.
+You can define yourself conditions and compose with other `Cond`.

+ 5 - 5
builder.go

@@ -60,7 +60,7 @@ type Builder struct {
 	limitation *limit
 	limitation *limit
 	insertCols []string
 	insertCols []string
 	insertVals []interface{}
 	insertVals []interface{}
-	updates    []Eq
+	updates    []UpdateCond
 	orderBy    string
 	orderBy    string
 	groupBy    string
 	groupBy    string
 	having     string
 	having     string
@@ -302,11 +302,11 @@ func (b *Builder) Insert(eq ...interface{}) *Builder {
 }
 }
 
 
 // Update sets update SQL
 // Update sets update SQL
-func (b *Builder) Update(updates ...Eq) *Builder {
-	b.updates = make([]Eq, 0, len(updates))
+func (b *Builder) Update(updates ...Cond) *Builder {
+	b.updates = make([]UpdateCond, 0, len(updates))
 	for _, update := range updates {
 	for _, update := range updates {
-		if update.IsValid() {
-			b.updates = append(b.updates, update)
+		if u, ok := update.(UpdateCond); ok && u.IsValid() {
+			b.updates = append(b.updates, u)
 		}
 		}
 	}
 	}
 	b.optype = updateType
 	b.optype = updateType

+ 9 - 2
builder_update.go

@@ -8,8 +8,14 @@ import (
 	"fmt"
 	"fmt"
 )
 )
 
 
+// UpdateCond defines an interface that cond could be used with update
+type UpdateCond interface {
+	IsValid() bool
+	OpWriteTo(op string, w Writer) error
+}
+
 // Update creates an update Builder
 // Update creates an update Builder
-func Update(updates ...Eq) *Builder {
+func Update(updates ...Cond) *Builder {
 	builder := &Builder{cond: NewCond()}
 	builder := &Builder{cond: NewCond()}
 	return builder.Update(updates...)
 	return builder.Update(updates...)
 }
 }
@@ -27,7 +33,8 @@ func (b *Builder) updateWriteTo(w Writer) error {
 	}
 	}
 
 
 	for i, s := range b.updates {
 	for i, s := range b.updates {
-		if err := s.opWriteTo(",", w); err != nil {
+
+		if err := s.OpWriteTo(",", w); err != nil {
 			return err
 			return err
 		}
 		}
 
 

+ 5 - 0
builder_update_test.go

@@ -49,4 +49,9 @@ func TestBuilderUpdate(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
 	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
 	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
 	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 1}, Expr("c = c+1")).From("table1").Where(Eq{"b": 2}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=?,c = c+1 WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 2}, args)
 }
 }

+ 3 - 2
cond_eq.go

@@ -20,7 +20,8 @@ type Eq map[string]interface{}
 
 
 var _ Cond = Eq{}
 var _ Cond = Eq{}
 
 
-func (eq Eq) opWriteTo(op string, w Writer) error {
+// OpWriteTo writes conditions with special operator
+func (eq Eq) OpWriteTo(op string, w Writer) error {
 	var i = 0
 	var i = 0
 	for _, k := range eq.sortedKeys() {
 	for _, k := range eq.sortedKeys() {
 		v := eq[k]
 		v := eq[k]
@@ -81,7 +82,7 @@ func (eq Eq) opWriteTo(op string, w Writer) error {
 
 
 // WriteTo writes SQL to Writer
 // WriteTo writes SQL to Writer
 func (eq Eq) WriteTo(w Writer) error {
 func (eq Eq) WriteTo(w Writer) error {
-	return eq.opWriteTo(" AND ", w)
+	return eq.OpWriteTo(" AND ", w)
 }
 }
 
 
 // And implements And with other conditions
 // And implements And with other conditions

+ 4 - 0
cond_expr.go

@@ -18,6 +18,10 @@ func Expr(sql string, args ...interface{}) Cond {
 	return expr{sql, args}
 	return expr{sql, args}
 }
 }
 
 
+func (expr expr) OpWriteTo(op string, w Writer) error {
+	return expr.WriteTo(w)
+}
+
 func (expr expr) WriteTo(w Writer) error {
 func (expr expr) WriteTo(w Writer) error {
 	if _, err := fmt.Fprint(w, expr.sql); err != nil {
 	if _, err := fmt.Fprint(w, expr.sql); err != nil {
 		return err
 		return err

+ 49 - 0
cond_if.go

@@ -0,0 +1,49 @@
+// Copyright 2019 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
+
+type condIf struct {
+	condition bool
+	condTrue  Cond
+	condFalse Cond
+}
+
+var _ Cond = condIf{}
+
+// If returns Cond via condition
+func If(condition bool, condTrue Cond, condFalse ...Cond) Cond {
+	var c = condIf{
+		condition: condition,
+		condTrue:  condTrue,
+	}
+	if len(condFalse) > 0 {
+		c.condFalse = condFalse[0]
+	}
+	return c
+}
+
+func (condIf condIf) WriteTo(w Writer) error {
+	if condIf.condition {
+		return condIf.condTrue.WriteTo(w)
+	} else if condIf.condFalse != nil {
+		return condIf.condFalse.WriteTo(w)
+	}
+	return nil
+}
+
+func (condIf condIf) And(conds ...Cond) Cond {
+	return And(condIf, And(conds...))
+}
+
+func (condIf condIf) Or(conds ...Cond) Cond {
+	return Or(condIf, Or(conds...))
+}
+
+func (condIf condIf) IsValid() bool {
+	if condIf.condition {
+		return condIf.condTrue != nil
+	}
+	return condIf.condFalse != nil
+}

+ 38 - 0
cond_if_test.go

@@ -0,0 +1,38 @@
+// Copyright 2019 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"
+)
+
+func TestCond_If(t *testing.T) {
+	cond1 := If(1 > 0, Eq{"a": 1}, Eq{"b": 1})
+	sql, err := ToBoundSQL(cond1)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "a=1", sql)
+
+	cond2 := If(1 < 0, Eq{"a": 1}, Eq{"b": 1})
+	sql, err = ToBoundSQL(cond2)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "b=1", sql)
+
+	cond3 := If(1 > 0, cond2, Eq{"c": 1})
+	sql, err = ToBoundSQL(cond3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "b=1", sql)
+
+	cond4 := If(2 < 0, Eq{"d": "a"})
+	sql, err = ToBoundSQL(cond4)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "", sql)
+
+	cond5 := And(cond1, cond2, cond3, cond4)
+	sql, err = ToBoundSQL(cond5)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "a=1 AND b=1 AND b=1", sql)
+}

+ 11 - 11
doc.go

@@ -8,13 +8,13 @@ Package builder is a simple and powerful sql builder for Go.
 
 
 Make sure you have installed Go 1.1+ and then:
 Make sure you have installed Go 1.1+ and then:
 
 
-    go get github.com/go-xorm/builder
+    go get github.com/xormplus/builder
 
 
 WARNNING: Currently, only query conditions are supported. Below is the supported conditions.
 WARNNING: Currently, only query conditions are supported. Below is the supported conditions.
 
 
 1. Eq is a redefine of a map, you can give one or more conditions to Eq
 1. Eq is a redefine of a map, you can give one or more conditions to Eq
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Eq{"a":1})
     sql, args, _ := ToSQL(Eq{"a":1})
     // a=? [1]
     // a=? [1]
@@ -31,7 +31,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 2. Neq is the same to Eq
 2. Neq is the same to Eq
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Neq{"a":1})
     sql, args, _ := ToSQL(Neq{"a":1})
     // a<>? [1]
     // a<>? [1]
@@ -48,7 +48,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 3. Gt, Gte, Lt, Lte
 3. Gt, Gte, Lt, Lte
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
     sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
     // a>? AND b>=? [1, 2]
     // a>? AND b>=? [1, 2]
@@ -57,14 +57,14 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 4. Like
 4. Like
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Like{"a", "c"})
     sql, args, _ := ToSQL(Like{"a", "c"})
     // a LIKE ? [%c%]
     // a LIKE ? [%c%]
 
 
 5. Expr you can customerize your sql with Expr
 5. Expr you can customerize your sql with Expr
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Expr("a = ? ", 1))
     sql, args, _ := ToSQL(Expr("a = ? ", 1))
     // a = ? [1]
     // a = ? [1]
@@ -73,7 +73,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 6. In and NotIn
 6. In and NotIn
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(In("a", 1, 2, 3))
     sql, args, _ := ToSQL(In("a", 1, 2, 3))
     // a IN (?,?,?) [1,2,3]
     // a IN (?,?,?) [1,2,3]
@@ -84,7 +84,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 7. IsNull and NotNull
 7. IsNull and NotNull
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(IsNull{"a"})
     sql, args, _ := ToSQL(IsNull{"a"})
     // a IS NULL []
     // a IS NULL []
@@ -93,14 +93,14 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 8. And(conds ...Cond), And can connect one or more condtions via AND
 8. And(conds ...Cond), And can connect one or more condtions via AND
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
     // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
 
 
 9. Or(conds ...Cond), Or can connect one or more conditions via Or
 9. Or(conds ...Cond), Or can connect one or more conditions via Or
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
     // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
@@ -109,7 +109,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 
 10. Between
 10. Between
 
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
 
     sql, args, _ := ToSQL(Between("a", 1, 2))
     sql, args, _ := ToSQL(Between("a", 1, 2))
     // a BETWEEN 1 AND 2
     // a BETWEEN 1 AND 2

+ 5 - 0
go.mod

@@ -1 +1,6 @@
 module "github.com/xormplus/builder"
 module "github.com/xormplus/builder"
+
+require (
+	github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a
+	github.com/stretchr/testify v1.3.0
+)

+ 5 - 1
sql.go

@@ -8,6 +8,7 @@ import (
 	sql2 "database/sql"
 	sql2 "database/sql"
 	"fmt"
 	"fmt"
 	"reflect"
 	"reflect"
+	"strings"
 	"time"
 	"time"
 )
 )
 
 
@@ -116,7 +117,10 @@ func ConvertToBoundSQL(sql string, args []interface{}) (string, error) {
 			if noSQLQuoteNeeded(arg) {
 			if noSQLQuoteNeeded(arg) {
 				_, err = fmt.Fprint(&buf, arg)
 				_, err = fmt.Fprint(&buf, arg)
 			} else {
 			} else {
-				_, err = fmt.Fprintf(&buf, "'%v'", arg)
+				// 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 {
 			if err != nil {
 				return "", err
 				return "", err

+ 6 - 0
sql_test.go

@@ -207,3 +207,9 @@ func TestToSQLInDifferentDialects(t *testing.T) {
 	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=? AND b<>?", sql)
 	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=? AND b<>?", sql)
 	assert.EqualValues(t, []interface{}{"1", "100"}, args)
 	assert.EqualValues(t, []interface{}{"1", "100"}, args)
 }
 }
+
+func TestToSQLInjectionHarmlessDisposal(t *testing.T) {
+	sql, err := MySQL().Select("*").From("table1").Where(Cond(Eq{"name": "cat';truncate table table1;"})).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE name='cat'';truncate table table1;'", sql)
+}