xormplus 7 rokov pred
commit
0185e89d0c
42 zmenil súbory, kde vykonal 5245 pridanie a 0 odobranie
  1. 37 0
      .drone.yml
  2. 27 0
      LICENSE
  3. 206 0
      README.md
  4. 394 0
      builder.go
  5. 299 0
      builder_b_test.go
  6. 27 0
      builder_delete.go
  7. 24 0
      builder_delete_test.go
  8. 89 0
      builder_insert.go
  9. 45 0
      builder_insert_test.go
  10. 100 0
      builder_limit.go
  11. 124 0
      builder_limit_test.go
  12. 145 0
      builder_select.go
  13. 143 0
      builder_select_test.go
  14. 657 0
      builder_test.go
  15. 47 0
      builder_union.go
  16. 74 0
      builder_union_test.go
  17. 46 0
      builder_update.go
  18. 52 0
      builder_update_test.go
  19. 74 0
      cond.go
  20. 61 0
      cond_and.go
  21. 65 0
      cond_between.go
  22. 160 0
      cond_compare.go
  23. 112 0
      cond_eq.go
  24. 39 0
      cond_expr.go
  25. 237 0
      cond_in.go
  26. 41 0
      cond_like.go
  27. 94 0
      cond_neq.go
  28. 77 0
      cond_not.go
  29. 234 0
      cond_notin.go
  30. 59 0
      cond_null.go
  31. 69 0
      cond_or.go
  32. 11 0
      cond_test.go
  33. 120 0
      doc.go
  34. 40 0
      error.go
  35. 1 0
      go.mod
  36. 203 0
      sql.go
  37. 209 0
      sql_test.go
  38. 119 0
      string_builder.go
  39. 44 0
      string_builder_test.go
  40. 213 0
      testdata/mssql_fiddle_data.sql
  41. 213 0
      testdata/mysql_fiddle_data.sql
  42. 214 0
      testdata/oracle_fiddle_data.sql

+ 37 - 0
.drone.yml

@@ -0,0 +1,37 @@
+workspace:
+  base: /go
+  path: src/github.com/go-xorm/builder
+
+clone:
+  git:
+    image: plugins/git:next
+    depth: 50
+    tags: true
+
+matrix:
+  GO_VERSION:
+    - 1.8
+    - 1.9
+    - 1.10
+    - 1.11
+
+pipeline:
+  test:
+    image: golang:${GO_VERSION}
+    commands:
+      - go get -u github.com/golang/lint/golint
+      - go get -u github.com/stretchr/testify/assert
+      - go get -u github.com/go-xorm/sqlfiddle
+      - golint ./...
+      - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+    when:
+      event: [ push, tag, pull_request ]
+
+codecov:
+    image: robertstettner/drone-codecov
+    group: build
+    secrets: [ codecov_token ]
+    files:
+      - coverage.txt
+    when:
+      event: [ push, pull_request ]

+ 27 - 0
LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2016 The Xorm Authors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the {organization} nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 206 - 0
README.md

@@ -0,0 +1,206 @@
+# 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.
+
+Make sure you have installed Go 1.8+ and then:
+
+    go get github.com/go-xorm/builder
+
+# Insert
+
+```Go
+sql, args, err := builder.Insert(Eq{"c": 1, "d": 2}).Into("table1").ToSQL()
+
+// INSERT INTO table1 SELECT * FROM table2
+sql, err := builder.Insert().Into("table1").Select().From("table2").ToBoundSQL()
+
+// INSERT INTO table1 (a, b) SELECT b, c FROM table2
+sql, err = builder.Insert("a, b").Into("table1").Select("b, c").From("table2").ToBoundSQL()
+```
+
+# Select
+
+```Go
+// Simple Query
+sql, args, err := Select("c, d").From("table1").Where(Eq{"a": 1}).ToSQL()
+// With join
+sql, args, err = Select("c, d").From("table1").LeftJoin("table2", Eq{"table1.id": 1}.And(Lt{"table2.id": 3})).
+		RightJoin("table3", "table2.id = table3.tid").Where(Eq{"a": 1}).ToSQL()
+// From sub query
+sql, args, err := Select("sub.id").From(Select("c").From("table1").Where(Eq{"a": 1}), "sub").Where(Eq{"b": 1}).ToSQL()
+// From union query
+sql, args, err = Select("sub.id").From(
+	Select("id").From("table1").Where(Eq{"a": 1}).Union("all", Select("id").From("table1").Where(Eq{"a": 2})),"sub").
+	Where(Eq{"b": 1}).ToSQL()
+// With order by
+sql, args, err = Select("a", "b", "c").From("table1").Where(Eq{"f1": "v1", "f2": "v2"}).
+		OrderBy("a ASC").ToSQL()
+// With limit.
+// Be careful! You should set up specific dialect for builder before performing a query with LIMIT
+sql, args, err = Dialect(MYSQL).Select("a", "b", "c").From("table1").OrderBy("a ASC").
+		Limit(5, 10).ToSQL()
+```
+
+# Update
+
+```Go
+sql, args, err := Update(Eq{"a": 2}).From("table1").Where(Eq{"a": 1}).ToSQL()
+```
+
+# Delete
+
+```Go
+sql, args, err := Delete(Eq{"a": 1}).From("table1").ToSQL()
+```
+
+# Union
+
+```Go
+sql, args, err := Select("*").From("a").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("a").Where(Eq{"status": "2"})).
+		Union("distinct", Select("*").From("a").Where(Eq{"status": "3"})).
+		Union("", Select("*").From("a").Where(Eq{"status": "4"})).
+		ToSQL()
+```
+
+# Conditions
+
+* `Eq` is a redefine of a map, you can give one or more conditions to `Eq`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Eq{"a":1})
+// a=? [1]
+sql, args, _ := ToSQL(Eq{"b":"c"}.And(Eq{"c": 0}))
+// b=? AND c=? ["c", 0]
+sql, args, _ := ToSQL(Eq{"b":"c", "c":0})
+// b=? AND c=? ["c", 0]
+sql, args, _ := ToSQL(Eq{"b":"c"}.Or(Eq{"b":"d"}))
+// b=? OR b=? ["c", "d"]
+sql, args, _ := ToSQL(Eq{"b": []string{"c", "d"}})
+// b IN (?,?) ["c", "d"]
+sql, args, _ := ToSQL(Eq{"b": 1, "c":[]int{2, 3}})
+// b=? AND c IN (?,?) [1, 2, 3]
+```
+
+* `Neq` is the same to `Eq`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Neq{"a":1})
+// a<>? [1]
+sql, args, _ := ToSQL(Neq{"b":"c"}.And(Neq{"c": 0}))
+// b<>? AND c<>? ["c", 0]
+sql, args, _ := ToSQL(Neq{"b":"c", "c":0})
+// b<>? AND c<>? ["c", 0]
+sql, args, _ := ToSQL(Neq{"b":"c"}.Or(Neq{"b":"d"}))
+// b<>? OR b<>? ["c", "d"]
+sql, args, _ := ToSQL(Neq{"b": []string{"c", "d"}})
+// b NOT IN (?,?) ["c", "d"]
+sql, args, _ := ToSQL(Neq{"b": 1, "c":[]int{2, 3}})
+// b<>? AND c NOT IN (?,?) [1, 2, 3]
+```
+
+* `Gt`, `Gte`, `Lt`, `Lte`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
+// a>? AND b>=? [1, 2]
+sql, args, _ := ToSQL(Lt{"a", 1}.Or(Lte{"b", 2}))
+// a<? OR b<=? [1, 2]
+```
+
+* `Like`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Like{"a", "c"})
+// a LIKE ? [%c%]
+```
+
+* `Expr` you can customerize your sql with `Expr`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Expr("a = ? ", 1))
+// a = ? [1]
+sql, args, _ := ToSQL(Eq{"a": Expr("select id from table where c = ?", 1)})
+// a=(select id from table where c = ?) [1]
+```
+
+* `In` and `NotIn`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(In("a", 1, 2, 3))
+// a IN (?,?,?) [1,2,3]
+sql, args, _ := ToSQL(In("a", []int{1, 2, 3}))
+// a IN (?,?,?) [1,2,3]
+sql, args, _ := ToSQL(In("a", Expr("select id from b where c = ?", 1))))
+// a IN (select id from b where c = ?) [1]
+```
+
+* `IsNull` and `NotNull`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(IsNull{"a"})
+// a IS NULL []
+sql, args, _ := ToSQL(NotNull{"b"})
+	// b IS NOT NULL []
+```
+
+* `And(conds ...Cond)`, And can connect one or more condtions via And
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
+// a=? AND b LIKE ? AND d<>? [1, %c%, 2]
+```
+
+* `Or(conds ...Cond)`, Or can connect one or more conditions via Or
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
+// a=? OR b LIKE ? OR d<>? [1, %c%, 2]
+sql, args, _ := ToSQL(Or(Eq{"a":1}, And(Like{"b", "c"}, Neq{"d", 2})))
+// a=? OR (b LIKE ? AND d<>?) [1, %c%, 2]
+```
+
+* `Between`
+
+```Go
+import . "github.com/go-xorm/builder"
+
+sql, args, _ := ToSQL(Between{"a", 1, 2})
+// a BETWEEN 1 AND 2
+```
+
+* Define yourself conditions
+
+Since `Cond` is an interface.
+
+```Go
+type Cond interface {
+	WriteTo(Writer) error
+	And(...Cond) Cond
+	Or(...Cond) Cond
+	IsValid() bool
+}
+```
+
+You can define yourself conditions and compose with other `Cond`.

+ 394 - 0
builder.go

@@ -0,0 +1,394 @@
+// Copyright 2016 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"
+	"sort"
+)
+
+type optype byte
+
+const (
+	condType   optype = iota // only conditions
+	selectType               // select
+	insertType               // insert
+	updateType               // update
+	deleteType               // delete
+	unionType                // union
+)
+
+const (
+	POSTGRES = "postgres"
+	SQLITE   = "sqlite3"
+	MYSQL    = "mysql"
+	MSSQL    = "mssql"
+	ORACLE   = "oracle"
+)
+
+type join struct {
+	joinType  string
+	joinTable string
+	joinCond  Cond
+}
+
+type union struct {
+	unionType string
+	builder   *Builder
+}
+
+type limit struct {
+	limitN int
+	offset int
+}
+
+// Builder describes a SQL statement
+type Builder struct {
+	optype
+	dialect    string
+	isNested   bool
+	into       string
+	from       string
+	subQuery   *Builder
+	cond       Cond
+	selects    []string
+	joins      []join
+	unions     []union
+	limitation *limit
+	insertCols []string
+	insertVals []interface{}
+	updates    []Eq
+	orderBy    string
+	groupBy    string
+	having     string
+}
+
+// Dialect sets the db dialect of Builder.
+func Dialect(dialect string) *Builder {
+	builder := &Builder{cond: NewCond(), dialect: dialect}
+	return builder
+}
+
+// MySQL is shortcut of Dialect(MySQL)
+func MySQL() *Builder {
+	return Dialect(MYSQL)
+}
+
+// MsSQL is shortcut of Dialect(MsSQL)
+func MsSQL() *Builder {
+	return Dialect(MSSQL)
+}
+
+// Oracle is shortcut of Dialect(Oracle)
+func Oracle() *Builder {
+	return Dialect(ORACLE)
+}
+
+// Postgres is shortcut of Dialect(Postgres)
+func Postgres() *Builder {
+	return Dialect(POSTGRES)
+}
+
+// SQLite is shortcut of Dialect(SQLITE)
+func SQLite() *Builder {
+	return Dialect(SQLITE)
+}
+
+// Where sets where SQL
+func (b *Builder) Where(cond Cond) *Builder {
+	if b.cond.IsValid() {
+		b.cond = b.cond.And(cond)
+	} else {
+		b.cond = cond
+	}
+	return b
+}
+
+// From sets from subject(can be a table name in string or a builder pointer) and its alias
+func (b *Builder) From(subject interface{}, alias ...string) *Builder {
+	switch subject.(type) {
+	case *Builder:
+		b.subQuery = subject.(*Builder)
+
+		if len(alias) > 0 {
+			b.from = alias[0]
+		} else {
+			b.isNested = true
+		}
+	case string:
+		b.from = subject.(string)
+
+		if len(alias) > 0 {
+			b.from = b.from + " " + alias[0]
+		}
+	}
+
+	return b
+}
+
+// TableName returns the table name
+func (b *Builder) TableName() string {
+	if b.optype == insertType {
+		return b.into
+	}
+	return b.from
+}
+
+// Into sets insert table name
+func (b *Builder) Into(tableName string) *Builder {
+	b.into = tableName
+	return b
+}
+
+// Join sets join table and conditions
+func (b *Builder) Join(joinType, joinTable string, joinCond interface{}) *Builder {
+	switch joinCond.(type) {
+	case Cond:
+		b.joins = append(b.joins, join{joinType, joinTable, joinCond.(Cond)})
+	case string:
+		b.joins = append(b.joins, join{joinType, joinTable, Expr(joinCond.(string))})
+	}
+
+	return b
+}
+
+// Union sets union conditions
+func (b *Builder) Union(unionTp string, unionCond *Builder) *Builder {
+	var builder *Builder
+	if b.optype != unionType {
+		builder = &Builder{cond: NewCond()}
+		builder.optype = unionType
+		builder.dialect = b.dialect
+		builder.selects = b.selects
+
+		currentUnions := b.unions
+		// erase sub unions (actually append to new Builder.unions)
+		b.unions = nil
+
+		for e := range currentUnions {
+			currentUnions[e].builder.dialect = b.dialect
+		}
+
+		builder.unions = append(append(builder.unions, union{"", b}), currentUnions...)
+	} else {
+		builder = b
+	}
+
+	if unionCond != nil {
+		if unionCond.dialect == "" && builder.dialect != "" {
+			unionCond.dialect = builder.dialect
+		}
+
+		builder.unions = append(builder.unions, union{unionTp, unionCond})
+	}
+
+	return builder
+}
+
+// Limit sets limitN condition
+func (b *Builder) Limit(limitN int, offset ...int) *Builder {
+	b.limitation = &limit{limitN: limitN}
+
+	if len(offset) > 0 {
+		b.limitation.offset = offset[0]
+	}
+
+	return b
+}
+
+// InnerJoin sets inner join
+func (b *Builder) InnerJoin(joinTable string, joinCond interface{}) *Builder {
+	return b.Join("INNER", joinTable, joinCond)
+}
+
+// LeftJoin sets left join SQL
+func (b *Builder) LeftJoin(joinTable string, joinCond interface{}) *Builder {
+	return b.Join("LEFT", joinTable, joinCond)
+}
+
+// RightJoin sets right join SQL
+func (b *Builder) RightJoin(joinTable string, joinCond interface{}) *Builder {
+	return b.Join("RIGHT", joinTable, joinCond)
+}
+
+// CrossJoin sets cross join SQL
+func (b *Builder) CrossJoin(joinTable string, joinCond interface{}) *Builder {
+	return b.Join("CROSS", joinTable, joinCond)
+}
+
+// FullJoin sets full join SQL
+func (b *Builder) FullJoin(joinTable string, joinCond interface{}) *Builder {
+	return b.Join("FULL", joinTable, joinCond)
+}
+
+// Select sets select SQL
+func (b *Builder) Select(cols ...string) *Builder {
+	b.selects = cols
+	if b.optype == condType {
+		b.optype = selectType
+	}
+	return b
+}
+
+// And sets AND condition
+func (b *Builder) And(cond Cond) *Builder {
+	b.cond = And(b.cond, cond)
+	return b
+}
+
+// Or sets OR condition
+func (b *Builder) Or(cond Cond) *Builder {
+	b.cond = Or(b.cond, cond)
+	return b
+}
+
+type insertColsSorter struct {
+	cols []string
+	vals []interface{}
+}
+
+func (s insertColsSorter) Len() int {
+	return len(s.cols)
+}
+func (s insertColsSorter) Swap(i, j int) {
+	s.cols[i], s.cols[j] = s.cols[j], s.cols[i]
+	s.vals[i], s.vals[j] = s.vals[j], s.vals[i]
+}
+
+func (s insertColsSorter) Less(i, j int) bool {
+	return s.cols[i] < s.cols[j]
+}
+
+// Insert sets insert SQL
+func (b *Builder) Insert(eq ...interface{}) *Builder {
+	if len(eq) > 0 {
+		var paramType = -1
+		for _, e := range eq {
+			switch t := e.(type) {
+			case Eq:
+				if paramType == -1 {
+					paramType = 0
+				}
+				if paramType != 0 {
+					break
+				}
+				for k, v := range t {
+					b.insertCols = append(b.insertCols, k)
+					b.insertVals = append(b.insertVals, v)
+				}
+			case string:
+				if paramType == -1 {
+					paramType = 1
+				}
+				if paramType != 1 {
+					break
+				}
+				b.insertCols = append(b.insertCols, t)
+			}
+		}
+	}
+
+	if len(b.insertCols) == len(b.insertVals) {
+		sort.Sort(insertColsSorter{
+			cols: b.insertCols,
+			vals: b.insertVals,
+		})
+	}
+	b.optype = insertType
+	return b
+}
+
+// Update sets update SQL
+func (b *Builder) Update(updates ...Eq) *Builder {
+	b.updates = make([]Eq, 0, len(updates))
+	for _, update := range updates {
+		if update.IsValid() {
+			b.updates = append(b.updates, update)
+		}
+	}
+	b.optype = updateType
+	return b
+}
+
+// Delete sets delete SQL
+func (b *Builder) Delete(conds ...Cond) *Builder {
+	b.cond = b.cond.And(conds...)
+	b.optype = deleteType
+	return b
+}
+
+// WriteTo implements Writer interface
+func (b *Builder) WriteTo(w Writer) error {
+	switch b.optype {
+	/*case condType:
+	return b.cond.WriteTo(w)*/
+	case selectType:
+		return b.selectWriteTo(w)
+	case insertType:
+		return b.insertWriteTo(w)
+	case updateType:
+		return b.updateWriteTo(w)
+	case deleteType:
+		return b.deleteWriteTo(w)
+	case unionType:
+		return b.unionWriteTo(w)
+	}
+
+	return ErrNotSupportType
+}
+
+// ToSQL convert a builder to SQL and args
+func (b *Builder) ToSQL() (string, []interface{}, error) {
+	w := NewWriter()
+	if err := b.WriteTo(w); err != nil {
+		return "", nil, err
+	}
+
+	// in case of sql.NamedArg in args
+	for e := range w.args {
+		if namedArg, ok := w.args[e].(sql2.NamedArg); ok {
+			w.args[e] = namedArg.Value
+		}
+	}
+
+	var sql = w.writer.String()
+	var err error
+
+	switch b.dialect {
+	case ORACLE, MSSQL:
+		// This is for compatibility with different sql drivers
+		for e := range w.args {
+			w.args[e] = sql2.Named(fmt.Sprintf("p%d", e+1), w.args[e])
+		}
+
+		var prefix string
+		if b.dialect == ORACLE {
+			prefix = ":p"
+		} else {
+			prefix = "@p"
+		}
+
+		if sql, err = ConvertPlaceholder(sql, prefix); err != nil {
+			return "", nil, err
+		}
+	case POSTGRES:
+		if sql, err = ConvertPlaceholder(sql, "$"); err != nil {
+			return "", nil, err
+		}
+	}
+
+	return sql, w.args, nil
+}
+
+// ToBoundSQL
+func (b *Builder) ToBoundSQL() (string, error) {
+	w := NewWriter()
+	if err := b.WriteTo(w); err != nil {
+		return "", err
+	}
+
+	return ConvertToBoundSQL(w.writer.String(), w.args)
+}

+ 299 - 0
builder_b_test.go

@@ -0,0 +1,299 @@
+// 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"
+	"math/rand"
+	"testing"
+)
+
+type randGenConf struct {
+	allowCond     bool
+	allowJoin     bool
+	allowLimit    bool
+	allowUnion    bool
+	allowHaving   bool
+	allowGroupBy  bool
+	allowOrderBy  bool
+	allowSubQuery bool
+}
+
+var expectedValues = []interface{}{
+	"dangerous", "fun", "degree", "hospital", "horseshoe", "summit", "parallel", "height", "recommend", "invite",
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+
+var queryFields = []string{"f1", "f2", "f2", "f4", "f5", "f6", "f7", "f8", "f9"}
+
+func BenchmarkSelect_Simple(b *testing.B) {
+	rgc := randGenConf{allowCond: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery("", &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SubQuery(b *testing.B) {
+	rgc := randGenConf{allowSubQuery: true, allowCond: true, allowGroupBy: true, allowHaving: true, allowOrderBy: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery("", &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectConditional4Oracle(b *testing.B) {
+	rgc := randGenConf{allowLimit: true, allowCond: true, allowGroupBy: true, allowHaving: true, allowOrderBy: true}
+	for i := 0; i < b.N; i++ {
+		randQuery(ORACLE, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectConditional4Mssql(b *testing.B) {
+	rgc := randGenConf{allowLimit: true, allowCond: true, allowGroupBy: true, allowHaving: true, allowOrderBy: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(MSSQL, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectConditional4MysqlLike(b *testing.B) {
+	rgc := randGenConf{allowLimit: true, allowCond: true, allowGroupBy: true, allowHaving: true, allowOrderBy: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(MYSQL, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectConditional4Mixed(b *testing.B) {
+	rgc := randGenConf{allowLimit: true, allowCond: true, allowGroupBy: true, allowHaving: true, allowOrderBy: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(randDialect(), &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectComplex4Oracle(b *testing.B) {
+	rgc := randGenConf{
+		allowLimit: true, allowCond: true,
+		allowGroupBy: true, allowHaving: true,
+		allowOrderBy: true, allowSubQuery: true,
+	}
+	for i := 0; i < b.N; i++ {
+		randQuery(ORACLE, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectComplex4Mssql(b *testing.B) {
+	rgc := randGenConf{
+		allowLimit: true, allowCond: true,
+		allowGroupBy: true, allowHaving: true,
+		allowOrderBy: true, allowSubQuery: true,
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(MSSQL, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectComplex4MysqlLike(b *testing.B) {
+	rgc := randGenConf{
+		allowLimit: true, allowCond: true,
+		allowGroupBy: true, allowHaving: true,
+		allowOrderBy: true, allowSubQuery: true,
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(MYSQL, &rgc).ToSQL()
+	}
+}
+
+func BenchmarkSelect_SelectComplex4MysqlMixed(b *testing.B) {
+	rgc := randGenConf{
+		allowLimit: true, allowCond: true,
+		allowGroupBy: true, allowHaving: true,
+		allowOrderBy: true, allowSubQuery: true,
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randQuery(randDialect(), &rgc).ToSQL()
+	}
+}
+
+func BenchmarkInsert(b *testing.B) {
+	rgc := randGenConf{allowCond: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randInsertByCondition(&rgc).ToSQL()
+	}
+}
+
+func BenchmarkUpdate(b *testing.B) {
+	rgc := randGenConf{allowCond: true}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		randUpdateByCondition(&rgc).ToSQL()
+	}
+}
+
+// randQuery Generate a basic query for benchmark test. But be careful it's not a executable SQL in real db.
+func randQuery(dialect string, rgc *randGenConf) *Builder {
+	b := randSelectByCondition(dialect, rgc)
+	isUnionized := rgc.allowUnion && rand.Intn(1000) >= 500
+	if isUnionized {
+		r := rand.Intn(3) + 1
+		for i := r; i < r; i++ {
+			b = b.Union("all", randSelectByCondition(dialect, rgc))
+		}
+	}
+
+	if isUnionized && rgc.allowLimit && rand.Intn(1000) >= 500 {
+		b = randLimit(Dialect(dialect).Select().From(b, "t"))
+	}
+
+	return b
+}
+
+func randInsertByCondition(rgc *randGenConf) *Builder {
+	fields := randSelects()
+
+	times := rand.Intn(10) + 1
+
+	eqs := Eq{}
+	for i := 0; i < times; i++ {
+		eqs[fields[rand.Intn(len(fields))]] = "expected"
+	}
+
+	b := Insert(eqs).From("table1")
+
+	if rgc.allowCond && rand.Intn(1000) >= 500 {
+		b = b.Where(randCond(b.selects, 3))
+	}
+
+	return b
+}
+
+func randUpdateByCondition(rgc *randGenConf) *Builder {
+	fields := randSelects()
+
+	times := rand.Intn(10) + 1
+
+	eqs := Eq{}
+	for i := 0; i < times; i++ {
+		eqs[fields[rand.Intn(len(fields))]] = randVal()
+	}
+
+	b := Update(eqs).From("table1")
+
+	if rgc.allowCond && rand.Intn(1000) >= 500 {
+		b.Where(randCond(fields, 3))
+	}
+
+	return b
+}
+
+func randSelectByCondition(dialect string, rgc *randGenConf) *Builder {
+	var b *Builder
+	if rgc.allowSubQuery {
+		cpRgc := *rgc
+		cpRgc.allowSubQuery = false
+		b = Dialect(dialect).Select(randSelects()...).From(randQuery(dialect, &cpRgc), randTableName(0))
+	} else {
+		b = Dialect(dialect).Select(randSelects()...).From(randTableName(0))
+	}
+	if rgc.allowJoin {
+		b = randJoin(b, 3)
+	}
+	if rgc.allowCond && rand.Intn(1000) >= 500 {
+		b = b.Where(randCond(b.selects, 3))
+	}
+	if rgc.allowLimit && rand.Intn(1000) >= 500 {
+		b = randLimit(b)
+	}
+	if rgc.allowOrderBy && rand.Intn(1000) >= 500 {
+		b = randOrderBy(b)
+	}
+	if rgc.allowHaving && rand.Intn(1000) >= 500 {
+		b = randHaving(b)
+	}
+	if rgc.allowGroupBy && rand.Intn(1000) >= 500 {
+		b = randGroupBy(b)
+	}
+
+	return b
+}
+
+func randDialect() string {
+	dialects := []string{MYSQL, ORACLE, MSSQL, SQLITE, POSTGRES}
+
+	return dialects[rand.Intn(len(dialects))]
+}
+
+func randSelects() []string {
+	if rand.Intn(1000) > 900 {
+		return []string{"*"}
+	}
+
+	rdx := rand.Intn(len(queryFields) / 2)
+	return queryFields[rdx:]
+}
+
+func randTableName(offset int) string {
+	return fmt.Sprintf("table%v", rand.Intn(10)+offset)
+}
+
+func randJoin(b *Builder, lessThan int) *Builder {
+	if lessThan <= 0 {
+		return b
+	}
+
+	times := rand.Intn(lessThan)
+
+	for i := 0; i < times; i++ {
+		tableName := randTableName(i * 10)
+		b = b.Join("", tableName, fmt.Sprintf("%v.id = %v.id", b.TableName(), tableName))
+	}
+
+	return b
+}
+
+func randCond(selects []string, lessThan int) Cond {
+	if len(selects) <= 0 {
+		return nil
+	}
+
+	cond := NewCond()
+
+	times := rand.Intn(lessThan)
+	for i := 0; i < times; i++ {
+		cond = cond.And(Eq{selects[rand.Intn(len(selects))]: randVal()})
+	}
+
+	return cond
+}
+
+func randLimit(b *Builder) *Builder {
+	r := rand.Intn(1000) + 1
+	if r > 500 {
+		return b.Limit(r, 1000)
+	} else {
+		return b.Limit(r)
+	}
+}
+
+func randOrderBy(b *Builder) *Builder {
+	return b.OrderBy(fmt.Sprintf("%v ASC", b.selects[rand.Intn(len(b.selects))]))
+}
+
+func randHaving(b *Builder) *Builder {
+	return b.OrderBy(fmt.Sprintf("%v = %v", b.selects[rand.Intn(len(b.selects))], randVal()))
+}
+
+func randGroupBy(b *Builder) *Builder {
+	return b.GroupBy(fmt.Sprintf("%v = %v", b.selects[rand.Intn(len(b.selects))], randVal()))
+}
+
+func randVal() interface{} {
+	return expectedValues[rand.Intn(len(expectedValues))]
+}

+ 27 - 0
builder_delete.go

@@ -0,0 +1,27 @@
+// Copyright 2016 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"
+)
+
+// Delete creates a delete Builder
+func Delete(conds ...Cond) *Builder {
+	builder := &Builder{cond: NewCond()}
+	return builder.Delete(conds...)
+}
+
+func (b *Builder) deleteWriteTo(w Writer) error {
+	if len(b.from) <= 0 {
+		return ErrNoTableName
+	}
+
+	if _, err := fmt.Fprintf(w, "DELETE FROM %s WHERE ", b.from); err != nil {
+		return err
+	}
+
+	return b.cond.WriteTo(w)
+}

+ 24 - 0
builder_delete_test.go

@@ -0,0 +1,24 @@
+// 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"
+)
+
+func TestBuilderDelete(t *testing.T) {
+	sql, args, err := Delete(Eq{"a": 1}).From("table1").ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "DELETE FROM table1 WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{1}, args)
+}
+
+func TestDeleteNoTable(t *testing.T) {
+	_, _, err := Delete(Eq{"b": "0"}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoTableName, err)
+}

+ 89 - 0
builder_insert.go

@@ -0,0 +1,89 @@
+// Copyright 2016 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 (
+	"bytes"
+	"fmt"
+)
+
+// Insert creates an insert Builder
+func Insert(eq ...interface{}) *Builder {
+	builder := &Builder{cond: NewCond()}
+	return builder.Insert(eq...)
+}
+
+func (b *Builder) insertSelectWriteTo(w Writer) error {
+	if _, err := fmt.Fprintf(w, "INSERT INTO %s ", b.into); err != nil {
+		return err
+	}
+
+	if len(b.insertCols) > 0 {
+		fmt.Fprintf(w, "(")
+		for _, col := range b.insertCols {
+			fmt.Fprintf(w, col)
+		}
+		fmt.Fprintf(w, ") ")
+	}
+
+	return b.selectWriteTo(w)
+}
+
+func (b *Builder) insertWriteTo(w Writer) error {
+	if len(b.into) <= 0 {
+		return ErrNoTableName
+	}
+	if len(b.insertCols) <= 0 && b.from == "" {
+		return ErrNoColumnToInsert
+	}
+
+	if b.into != "" && b.from != "" {
+		return b.insertSelectWriteTo(w)
+	}
+
+	if _, err := fmt.Fprintf(w, "INSERT INTO %s (", b.into); err != nil {
+		return err
+	}
+
+	var args = make([]interface{}, 0)
+	var bs []byte
+	var valBuffer = bytes.NewBuffer(bs)
+
+	for i, col := range b.insertCols {
+		value := b.insertVals[i]
+		fmt.Fprint(w, col)
+		if e, ok := value.(expr); ok {
+			fmt.Fprintf(valBuffer, "(%s)", e.sql)
+			args = append(args, e.args...)
+		} else {
+			fmt.Fprint(valBuffer, "?")
+			args = append(args, value)
+		}
+
+		if i != len(b.insertCols)-1 {
+			if _, err := fmt.Fprint(w, ","); err != nil {
+				return err
+			}
+			if _, err := fmt.Fprint(valBuffer, ","); err != nil {
+				return err
+			}
+		}
+	}
+
+	if _, err := fmt.Fprint(w, ") Values ("); err != nil {
+		return err
+	}
+
+	if _, err := w.Write(valBuffer.Bytes()); err != nil {
+		return err
+	}
+	if _, err := fmt.Fprint(w, ")"); err != nil {
+		return err
+	}
+
+	w.Append(args...)
+
+	return nil
+}

+ 45 - 0
builder_insert_test.go

@@ -0,0 +1,45 @@
+// 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"
+)
+
+func TestBuilderInsert(t *testing.T) {
+	sql, err := Insert(Eq{"c": 1, "d": 2}).Into("table1").ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "INSERT INTO table1 (c,d) Values (1,2)", sql)
+
+	sql, err = Insert(Eq{"e": 3}, Eq{"c": 1}, Eq{"d": 2}).Into("table1").ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "INSERT INTO table1 (c,d,e) Values (1,2,3)", sql)
+
+	sql, err = Insert(Eq{"c": 1, "d": Expr("SELECT b FROM t WHERE d=? LIMIT 1", 2)}).Into("table1").ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "INSERT INTO table1 (c,d) Values (1,(SELECT b FROM t WHERE d=2 LIMIT 1))", sql)
+
+	sql, err = Insert(Eq{"c": 1, "d": 2}).ToBoundSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoTableName, err)
+	assert.EqualValues(t, "", sql)
+
+	sql, err = Insert(Eq{}).Into("table1").ToBoundSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoColumnToInsert, err)
+	assert.EqualValues(t, "", sql)
+}
+
+func TestBuidlerInsert_Select(t *testing.T) {
+	sql, err := Insert().Into("table1").Select().From("table2").ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "INSERT INTO table1 SELECT * FROM table2", sql)
+
+	sql, err = Insert("a, b").Into("table1").Select("b, c").From("table2").ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "INSERT INTO table1 (a, b) SELECT b, c FROM table2", sql)
+}

+ 100 - 0
builder_limit.go

@@ -0,0 +1,100 @@
+// 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"
+	"strings"
+)
+
+func (b *Builder) limitWriteTo(w Writer) error {
+	if strings.TrimSpace(b.dialect) == "" {
+		return ErrDialectNotSetUp
+	}
+
+	if b.limitation != nil {
+		limit := b.limitation
+		if limit.offset < 0 || limit.limitN <= 0 {
+			return ErrInvalidLimitation
+		}
+		// erase limit condition
+		b.limitation = nil
+		ow := w.(*BytesWriter)
+
+		switch strings.ToLower(strings.TrimSpace(b.dialect)) {
+		case ORACLE:
+			if len(b.selects) == 0 {
+				b.selects = append(b.selects, "*")
+			}
+
+			var final *Builder
+			selects := b.selects
+			b.selects = append(selects, "ROWNUM RN")
+
+			var wb *Builder
+			if b.optype == unionType {
+				wb = Dialect(b.dialect).Select("at.*", "ROWNUM RN").
+					From(b, "at")
+			} else {
+				wb = b
+			}
+
+			if limit.offset == 0 {
+				final = Dialect(b.dialect).Select(selects...).From(wb, "at").
+					Where(Lte{"at.RN": limit.limitN})
+			} else {
+				sub := Dialect(b.dialect).Select("*").
+					From(b, "at").Where(Lte{"at.RN": limit.offset + limit.limitN})
+
+				final = Dialect(b.dialect).Select(selects...).From(sub, "att").
+					Where(Gt{"att.RN": limit.offset})
+			}
+
+			return final.WriteTo(ow)
+		case SQLITE, MYSQL, POSTGRES:
+			// if type UNION, we need to write previous content back to current writer
+			if b.optype == unionType {
+				if err := b.WriteTo(ow); err != nil {
+					return err
+				}
+			}
+
+			if limit.offset == 0 {
+				fmt.Fprint(ow, " LIMIT ", limit.limitN)
+			} else {
+				fmt.Fprintf(ow, " LIMIT %v OFFSET %v", limit.limitN, limit.offset)
+			}
+		case MSSQL:
+			if len(b.selects) == 0 {
+				b.selects = append(b.selects, "*")
+			}
+
+			var final *Builder
+			selects := b.selects
+			b.selects = append(append([]string{fmt.Sprintf("TOP %d %v", limit.limitN+limit.offset, b.selects[0])},
+				b.selects[1:]...), "ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN")
+
+			var wb *Builder
+			if b.optype == unionType {
+				wb = Dialect(b.dialect).Select("*", "ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN").
+					From(b, "at")
+			} else {
+				wb = b
+			}
+
+			if limit.offset == 0 {
+				final = Dialect(b.dialect).Select(selects...).From(wb, "at")
+			} else {
+				final = Dialect(b.dialect).Select(selects...).From(wb, "at").Where(Gt{"at.RN": limit.offset})
+			}
+
+			return final.WriteTo(ow)
+		default:
+			return ErrNotSupportType
+		}
+	}
+
+	return nil
+}

+ 124 - 0
builder_limit_test.go

@@ -0,0 +1,124 @@
+// 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
+
+/*
+func TestBuilder_Limit4Mssql(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/mssql_fiddle_data.sql")
+	assert.NoError(t, err)
+	f, err := newFiddler("", MSSQL, sqlFromFile)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, f.sessionCode)
+
+	// simple -- MsSQL style
+	sql, err := Dialect(MSSQL).Select("a", "b", "c").From("table1").
+		OrderBy("a ASC").Limit(5).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT TOP 5 a,b,c,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN FROM table1 ORDER BY a ASC) at", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple with where -- MsSQL style
+	sql, err = Dialect(MSSQL).Select("a", "b", "c").From("table1").
+		Where(Neq{"a": "3"}).OrderBy("a ASC").Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT TOP 15 a,b,c,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN FROM table1 WHERE a<>'3' ORDER BY a ASC) at WHERE at.RN>10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// union with limit -- MsSQL style
+	sql, err = Dialect(MSSQL).Select("a", "b", "c").From(
+		Dialect(MSSQL).Select("a", "b", "c").From("table1").Where(Neq{"a": "1"}).
+			OrderBy("a ASC").Limit(5, 6).Union("ALL",
+			Select("a", "b", "c").From("table1").Where(Neq{"b": "2"}).OrderBy("a DESC").Limit(10)), "at").
+		OrderBy("b DESC").Limit(7, 9).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT TOP 16 a,b,c,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN FROM ((SELECT a,b,c FROM (SELECT TOP 11 a,b,c,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN FROM table1 WHERE a<>'1' ORDER BY a ASC) at WHERE at.RN>6) UNION ALL (SELECT a,b,c FROM (SELECT TOP 10 a,b,c,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RN FROM table1 WHERE b<>'2' ORDER BY a DESC) at)) at ORDER BY b DESC) at WHERE at.RN>9", sql)
+	assert.NoError(t, f.executableCheck(sql))
+}
+
+func TestBuilder_Limit4MysqlLike(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/mysql_fiddle_data.sql")
+	assert.NoError(t, err)
+	f, err := newFiddler("", MYSQL, sqlFromFile)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, f.sessionCode)
+
+	// simple -- MySQL/SQLite/PostgreSQL style
+	sql, err := Dialect(MYSQL).Select("a", "b", "c").From("table1").OrderBy("a ASC").
+		Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM table1 ORDER BY a ASC LIMIT 5 OFFSET 10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple -- MySQL/SQLite/PostgreSQL style
+	sql, err = Dialect(MYSQL).Select("a", "b", "c").From("table1").
+		OrderBy("a ASC").Limit(5).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM table1 ORDER BY a ASC LIMIT 5", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple with where -- MySQL/SQLite/PostgreSQL style
+	sql, err = Dialect(MYSQL).Select("a", "b", "c").From("table1").
+		Where(Eq{"a": "1", "b": "1"}).OrderBy("a ASC").Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM table1 WHERE a='1' AND b='1' ORDER BY a ASC LIMIT 5 OFFSET 10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// union -- MySQL/SQLite/PostgreSQL style
+	sql, err = Dialect(MYSQL).Select("a", "b", "c").From(
+		Dialect(MYSQL).Select("a", "b", "c").From("table1").Where(Eq{"a": "1"}).OrderBy("a ASC").
+			Limit(5, 9).Union("ALL",
+			Select("a", "b", "c").From("table1").Where(Eq{"a": "2"}).OrderBy("a DESC").Limit(10)), "at").
+		Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM ((SELECT a,b,c FROM table1 WHERE a='1' ORDER BY a ASC LIMIT 5 OFFSET 9) UNION ALL (SELECT a,b,c FROM table1 WHERE a='2' ORDER BY a DESC LIMIT 10)) at LIMIT 5 OFFSET 10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+}
+
+func TestBuilder_Limit4Oracle(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/oracle_fiddle_data.sql")
+	assert.NoError(t, err)
+	f, err := newFiddler("", ORACLE, sqlFromFile)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, f.sessionCode)
+
+	// simple -- OracleSQL style
+	sql, err := Dialect(ORACLE).Select("a", "b", "c").From("table1").OrderBy("a ASC").
+		Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT * FROM (SELECT a,b,c,ROWNUM RN FROM table1 ORDER BY a ASC) at WHERE at.RN<=15) att WHERE att.RN>10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple with join -- OracleSQL style
+	sql, err = Dialect(ORACLE).Select("a", "b", "c", "d").From("table1 t1").
+		InnerJoin("table2 t2", "t1.id = t2.ref_id").OrderBy("a ASC").Limit(5, 10).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c,d FROM (SELECT * FROM (SELECT a,b,c,d,ROWNUM RN FROM table1 t1 INNER JOIN table2 t2 ON t1.id = t2.ref_id ORDER BY a ASC) at WHERE at.RN<=15) att WHERE att.RN>10", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple -- OracleSQL style
+	sql, err = Dialect(ORACLE).Select("a", "b", "c").From("table1").
+		OrderBy("a ASC").Limit(5).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT a,b,c,ROWNUM RN FROM table1 ORDER BY a ASC) at WHERE at.RN<=5", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// simple with where -- OracleSQL style
+	sql, err = Dialect(ORACLE).Select("a", "b", "c").From("table1").Where(Neq{"a": "10", "b": "20"}).
+		OrderBy("a ASC").Limit(5, 1).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT * FROM (SELECT a,b,c,ROWNUM RN FROM table1 WHERE a<>'10' AND b<>'20' ORDER BY a ASC) at WHERE at.RN<=6) att WHERE att.RN>1", sql)
+	assert.NoError(t, f.executableCheck(sql))
+
+	// union with limit -- OracleSQL style
+	sql, err = Dialect(ORACLE).Select("a", "b", "c").From(
+		Dialect(ORACLE).Select("a", "b", "c").From("table1").
+			Where(Neq{"a": "0"}).OrderBy("a ASC").Limit(5, 10).Union("ALL",
+			Select("a", "b", "c").From("table1").Where(Neq{"b": "48"}).
+				OrderBy("a DESC").Limit(10)), "at").
+		Limit(3).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a,b,c FROM (SELECT a,b,c,ROWNUM RN FROM ((SELECT a,b,c FROM (SELECT * FROM (SELECT a,b,c,ROWNUM RN FROM table1 WHERE a<>'0' ORDER BY a ASC) at WHERE at.RN<=15) att WHERE att.RN>10) UNION ALL (SELECT a,b,c FROM (SELECT a,b,c,ROWNUM RN FROM table1 WHERE b<>'48' ORDER BY a DESC) at WHERE at.RN<=10)) at) at WHERE at.RN<=3", sql)
+	assert.NoError(t, f.executableCheck(sql))
+}*/

+ 145 - 0
builder_select.go

@@ -0,0 +1,145 @@
+// Copyright 2016 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"
+)
+
+// Select creates a select Builder
+func Select(cols ...string) *Builder {
+	builder := &Builder{cond: NewCond()}
+	return builder.Select(cols...)
+}
+
+func (b *Builder) selectWriteTo(w Writer) error {
+	if len(b.from) <= 0 && !b.isNested {
+		return ErrNoTableName
+	}
+
+	// perform limit before writing to writer when b.dialect between ORACLE and MSSQL
+	// this avoid a duplicate writing problem in simple limit query
+	if b.limitation != nil && (b.dialect == ORACLE || b.dialect == MSSQL) {
+		return b.limitWriteTo(w)
+	}
+
+	if _, err := fmt.Fprint(w, "SELECT "); err != nil {
+		return err
+	}
+	if len(b.selects) > 0 {
+		for i, s := range b.selects {
+			if _, err := fmt.Fprint(w, s); err != nil {
+				return err
+			}
+			if i != len(b.selects)-1 {
+				if _, err := fmt.Fprint(w, ","); err != nil {
+					return err
+				}
+			}
+		}
+	} else {
+		if _, err := fmt.Fprint(w, "*"); err != nil {
+			return err
+		}
+	}
+
+	if b.subQuery == nil {
+		if _, err := fmt.Fprint(w, " FROM ", b.from); err != nil {
+			return err
+		}
+	} else {
+		if b.cond.IsValid() && len(b.from) <= 0 {
+			return ErrUnnamedDerivedTable
+		}
+		if b.subQuery.dialect != "" && b.dialect != b.subQuery.dialect {
+			return ErrInconsistentDialect
+		}
+
+		// dialect of sub-query will inherit from the main one (if not set up)
+		if b.dialect != "" && b.subQuery.dialect == "" {
+			b.subQuery.dialect = b.dialect
+		}
+
+		switch b.subQuery.optype {
+		case selectType, unionType:
+			fmt.Fprint(w, " FROM (")
+			if err := b.subQuery.WriteTo(w); err != nil {
+				return err
+			}
+
+			if len(b.from) == 0 {
+				fmt.Fprintf(w, ")")
+			} else {
+				fmt.Fprintf(w, ") %v", b.from)
+			}
+		default:
+			return ErrUnexpectedSubQuery
+		}
+	}
+
+	for _, v := range b.joins {
+		if _, err := fmt.Fprintf(w, " %s JOIN %s ON ", v.joinType, v.joinTable); err != nil {
+			return err
+		}
+
+		if err := v.joinCond.WriteTo(w); err != nil {
+			return err
+		}
+	}
+
+	if b.cond.IsValid() {
+		if _, err := fmt.Fprint(w, " WHERE "); err != nil {
+			return err
+		}
+
+		if err := b.cond.WriteTo(w); err != nil {
+			return err
+		}
+	}
+
+	if len(b.groupBy) > 0 {
+		if _, err := fmt.Fprint(w, " GROUP BY ", b.groupBy); err != nil {
+			return err
+		}
+	}
+
+	if len(b.having) > 0 {
+		if _, err := fmt.Fprint(w, " HAVING ", b.having); err != nil {
+			return err
+		}
+	}
+
+	if len(b.orderBy) > 0 {
+		if _, err := fmt.Fprint(w, " ORDER BY ", b.orderBy); err != nil {
+			return err
+		}
+	}
+
+	if b.limitation != nil {
+		if err := b.limitWriteTo(w); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// OrderBy orderBy SQL
+func (b *Builder) OrderBy(orderBy string) *Builder {
+	b.orderBy = orderBy
+	return b
+}
+
+// GroupBy groupby SQL
+func (b *Builder) GroupBy(groupby string) *Builder {
+	b.groupBy = groupby
+	return b
+}
+
+// Having having SQL
+func (b *Builder) Having(having string) *Builder {
+	b.having = having
+	return b
+}

+ 143 - 0
builder_select_test.go

@@ -0,0 +1,143 @@
+// 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"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuilder_Select(t *testing.T) {
+	sql, args, err := Select("c, d").From("table1").ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1", sql)
+	assert.EqualValues(t, []interface{}(nil), args)
+
+	sql, args, err = Select("c, d").From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1 WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{1}, args)
+
+	sql, args, err = Select("c, d").From("table1").LeftJoin("table2", Eq{"table1.id": 1}.And(Lt{"table2.id": 3})).
+		RightJoin("table3", "table2.id = table3.tid").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1 LEFT JOIN table2 ON table1.id=? AND table2.id<? RIGHT JOIN table3 ON table2.id = table3.tid WHERE a=?",
+		sql)
+	assert.EqualValues(t, []interface{}{1, 3, 1}, args)
+
+	sql, args, err = Select("c, d").From("table1").LeftJoin("table2", Eq{"table1.id": 1}.And(Lt{"table2.id": 3})).
+		FullJoin("table3", "table2.id = table3.tid").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1 LEFT JOIN table2 ON table1.id=? AND table2.id<? FULL JOIN table3 ON table2.id = table3.tid WHERE a=?",
+		sql)
+	assert.EqualValues(t, []interface{}{1, 3, 1}, args)
+
+	sql, args, err = Select("c, d").From("table1").LeftJoin("table2", Eq{"table1.id": 1}.And(Lt{"table2.id": 3})).
+		CrossJoin("table3", "table2.id = table3.tid").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1 LEFT JOIN table2 ON table1.id=? AND table2.id<? CROSS JOIN table3 ON table2.id = table3.tid WHERE a=?",
+		sql)
+	assert.EqualValues(t, []interface{}{1, 3, 1}, args)
+
+	sql, args, err = Select("c, d").From("table1").LeftJoin("table2", Eq{"table1.id": 1}.And(Lt{"table2.id": 3})).
+		InnerJoin("table3", "table2.id = table3.tid").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c, d FROM table1 LEFT JOIN table2 ON table1.id=? AND table2.id<? INNER JOIN table3 ON table2.id = table3.tid WHERE a=?",
+		sql)
+	assert.EqualValues(t, []interface{}{1, 3, 1}, args)
+
+	_, _, err = Select("c, d").ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoTableName, err)
+}
+
+func TestBuilderSelectGroupBy(t *testing.T) {
+	sql, args, err := Select("c").From("table1").GroupBy("c").Having("count(c)=1").ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c FROM table1 GROUP BY c HAVING count(c)=1", sql)
+	assert.EqualValues(t, 0, len(args))
+	fmt.Println(sql, args)
+}
+
+func TestBuilderSelectOrderBy(t *testing.T) {
+	sql, args, err := Select("c").From("table1").OrderBy("c DESC").ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c FROM table1 ORDER BY c DESC", sql)
+	assert.EqualValues(t, 0, len(args))
+	fmt.Println(sql, args)
+}
+
+func TestBuilder_From(t *testing.T) {
+	// simple one
+	sql, args, err := Select("c").From("table1").ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT c FROM table1", sql)
+	assert.EqualValues(t, 0, len(args))
+
+	// from sub with alias
+	sql, args, err = Select("sub.id").From(Select("id").From("table1").Where(Eq{"a": 1}),
+		"sub").Where(Eq{"b": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT sub.id FROM (SELECT id FROM table1 WHERE a=?) sub WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 1}, args)
+
+	// from sub without alias and with conditions
+	sql, args, err = Select("sub.id").From(Select("id").From("table1").Where(Eq{"a": 1})).Where(Eq{"b": 1}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrUnnamedDerivedTable, err)
+
+	// from sub without alias and conditions
+	sql, args, err = Select("sub.id").From(Select("id").From("table1").Where(Eq{"a": 1})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT sub.id FROM (SELECT id FROM table1 WHERE a=?)", sql)
+	assert.EqualValues(t, []interface{}{1}, args)
+
+	// from union with alias
+	sql, args, err = Select("sub.id").From(
+		Select("id").From("table1").Where(Eq{"a": 1}).Union(
+			"all", Select("id").From("table1").Where(Eq{"a": 2})), "sub").Where(Eq{"b": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT sub.id FROM ((SELECT id FROM table1 WHERE a=?) UNION ALL (SELECT id FROM table1 WHERE a=?)) sub WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 2, 1}, args)
+
+	// from union without alias
+	_, _, err = Select("sub.id").From(
+		Select("id").From("table1").Where(Eq{"a": 1}).Union(
+			"all", Select("id").From("table1").Where(Eq{"a": 2}))).Where(Eq{"b": 1}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrUnnamedDerivedTable, err)
+
+	// will raise error
+	_, _, err = Select("c").From(Insert(Eq{"a": 1}).From("table1"), "table1").ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrUnexpectedSubQuery, err)
+
+	// will raise error
+	_, _, err = Select("c").From(Delete(Eq{"a": 1}).From("table1"), "table1").ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrUnexpectedSubQuery, err)
+
+	// from a sub-query in different dialect
+	_, _, err = MySQL().Select("sub.id").From(
+		Oracle().Select("id").From("table1").Where(Eq{"a": 1}), "sub").Where(Eq{"b": 1}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrInconsistentDialect, err)
+
+	// from a sub-query (dialect set up)
+	sql, args, err = MySQL().Select("sub.id").From(
+		MySQL().Select("id").From("table1").Where(Eq{"a": 1}), "sub").Where(Eq{"b": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT sub.id FROM (SELECT id FROM table1 WHERE a=?) sub WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 1}, args)
+
+	// from a sub-query (dialect not set up)
+	sql, args, err = MySQL().Select("sub.id").From(
+		Select("id").From("table1").Where(Eq{"a": 1}), "sub").Where(Eq{"b": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT sub.id FROM (SELECT id FROM table1 WHERE a=?) sub WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 1}, args)
+}

+ 657 - 0
builder_test.go

@@ -0,0 +1,657 @@
+// Copyright 2016 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"
+)
+
+type MyInt int
+
+func TestBuilderCond(t *testing.T) {
+	var cases = []struct {
+		cond Cond
+		sql  string
+		args []interface{}
+	}{
+		{
+			Eq{"a": 1}.And(Like{"b", "c"}).Or(Eq{"a": 2}.And(Like{"b", "g"})),
+			"(a=? AND b LIKE ?) OR (a=? AND b LIKE ?)",
+			[]interface{}{1, "%c%", 2, "%g%"},
+		},
+		{
+			Eq{"a": 1}.Or(Like{"b", "c"}).And(Eq{"a": 2}.Or(Like{"b", "g"})),
+			"(a=? OR b LIKE ?) AND (a=? OR b LIKE ?)",
+			[]interface{}{1, "%c%", 2, "%g%"},
+		},
+		{
+			Eq{"d": []string{"e", "f"}},
+			"d IN (?,?)",
+			[]interface{}{"e", "f"},
+		},
+		{
+			Eq{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Eq{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Like{"a", "%1"}.And(Like{"b", "%2"}),
+			"a LIKE ? AND b LIKE ?",
+			[]interface{}{"%1", "%2"},
+		},
+		{
+			Like{"a", "%1"}.Or(Like{"b", "%2"}),
+			"a LIKE ? OR b LIKE ?",
+			[]interface{}{"%1", "%2"},
+		},
+		{
+			Neq{"d": "e"}.Or(Neq{"f": "g"}),
+			"d<>? OR f<>?",
+			[]interface{}{"e", "g"},
+		},
+		{
+			Neq{"d": []string{"e", "f"}},
+			"d NOT IN (?,?)",
+			[]interface{}{"e", "f"},
+		},
+		{
+			Neq{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e<>(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Neq{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e<>(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Lt{"d": 3},
+			"d<?",
+			[]interface{}{3},
+		},
+		{
+			Lt{"d": 3}.And(Lt{"e": 4}),
+			"d<? AND e<?",
+			[]interface{}{3, 4},
+		},
+		{
+			Lt{"d": 3}.Or(Lt{"e": 4}),
+			"d<? OR e<?",
+			[]interface{}{3, 4},
+		},
+		{
+			Lt{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e<(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Lt{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e<(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Lte{"d": 3},
+			"d<=?",
+			[]interface{}{3},
+		},
+		{
+			Lte{"d": 3}.And(Lte{"e": 4}),
+			"d<=? AND e<=?",
+			[]interface{}{3, 4},
+		},
+		{
+			Lte{"d": 3}.Or(Lte{"e": 4}),
+			"d<=? OR e<=?",
+			[]interface{}{3, 4},
+		},
+		{
+			Lte{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e<=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Lte{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e<=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Gt{"d": 3},
+			"d>?",
+			[]interface{}{3},
+		},
+		{
+			Gt{"d": 3}.And(Gt{"e": 4}),
+			"d>? AND e>?",
+			[]interface{}{3, 4},
+		},
+		{
+			Gt{"d": 3}.Or(Gt{"e": 4}),
+			"d>? OR e>?",
+			[]interface{}{3, 4},
+		},
+		{
+			Gt{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e>(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Gt{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e>(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Gte{"d": 3},
+			"d>=?",
+			[]interface{}{3},
+		},
+		{
+			Gte{"d": 3}.And(Gte{"e": 4}),
+			"d>=? AND e>=?",
+			[]interface{}{3, 4},
+		},
+		{
+			Gte{"d": 3}.Or(Gte{"e": 4}),
+			"d>=? OR e>=?",
+			[]interface{}{3, 4},
+		},
+		{
+			Gte{"e": Select("id").From("f").Where(Eq{"g": 1})},
+			"e>=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Gte{"e": Expr("SELECT id FROM f WHERE g=?", 1)},
+			"e>=(SELECT id FROM f WHERE g=?)",
+			[]interface{}{1},
+		},
+		{
+			Between{"d", 0, 2},
+			"d BETWEEN ? AND ?",
+			[]interface{}{0, 2},
+		},
+		{
+			Between{"d", 0, Expr("CAST('2003-01-01' AS DATE)")},
+			"d BETWEEN ? AND CAST('2003-01-01' AS DATE)",
+			[]interface{}{0},
+		},
+		{
+			Between{"d", Expr("CAST('2003-01-01' AS DATE)"), 2},
+			"d BETWEEN CAST('2003-01-01' AS DATE) AND ?",
+			[]interface{}{2},
+		},
+		{
+			Between{"d", Expr("CAST('2003-01-01' AS DATE)"), Expr("CAST('2003-01-01' AS DATE)")},
+			"d BETWEEN CAST('2003-01-01' AS DATE) AND CAST('2003-01-01' AS DATE)",
+			[]interface{}{},
+		},
+		{
+			Between{"d", 0, 2}.And(Between{"e", 3, 4}),
+			"d BETWEEN ? AND ? AND e BETWEEN ? AND ?",
+			[]interface{}{0, 2, 3, 4},
+		},
+		{
+			Between{"d", 0, 2}.Or(Between{"e", 3, 4}),
+			"d BETWEEN ? AND ? OR e BETWEEN ? AND ?",
+			[]interface{}{0, 2, 3, 4},
+		},
+		{
+			Expr("a < ?", 1),
+			"a < ?",
+			[]interface{}{1},
+		},
+		{
+			Expr("a < ?", 1).And(Eq{"b": 2}),
+			"(a < ?) AND b=?",
+			[]interface{}{1, 2},
+		},
+		{
+			Expr("a < ?", 1).Or(Neq{"b": 2}),
+			"(a < ?) OR b<>?",
+			[]interface{}{1, 2},
+		},
+		{
+			IsNull{"d"},
+			"d IS NULL",
+			[]interface{}{},
+		},
+		{
+			IsNull{"d"}.And(IsNull{"e"}),
+			"d IS NULL AND e IS NULL",
+			[]interface{}{},
+		},
+		{
+			IsNull{"d"}.Or(IsNull{"e"}),
+			"d IS NULL OR e IS NULL",
+			[]interface{}{},
+		},
+		{
+			NotNull{"d"},
+			"d IS NOT NULL",
+			[]interface{}{},
+		},
+		{
+			NotNull{"d"}.And(NotNull{"e"}),
+			"d IS NOT NULL AND e IS NOT NULL",
+			[]interface{}{},
+		},
+		{
+			NotNull{"d"}.Or(NotNull{"e"}),
+			"d IS NOT NULL OR e IS NOT NULL",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", 1, 2).And(NotIn("b", "c", "d")),
+			"a NOT IN (?,?) AND b NOT IN (?,?)",
+			[]interface{}{1, 2, "c", "d"},
+		},
+		{
+			In("a", 1, 2).Or(In("b", "c", "d")),
+			"a IN (?,?) OR b IN (?,?)",
+			[]interface{}{1, 2, "c", "d"},
+		},
+		{
+			In("a", []int{1, 2}).Or(In("b", []string{"c", "d"})),
+			"a IN (?,?) OR b IN (?,?)",
+			[]interface{}{1, 2, "c", "d"},
+		},
+		{
+			In("a", Expr("select id from x where name > ?", "b")),
+			"a IN (select id from x where name > ?)",
+			[]interface{}{"b"},
+		},
+		{
+			In("a", []MyInt{1, 2}).Or(In("b", []string{"c", "d"})),
+			"a IN (?,?) OR b IN (?,?)",
+			[]interface{}{MyInt(1), MyInt(2), "c", "d"},
+		},
+		{
+			In("a", []int{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []int{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []int8{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []int8{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []int16{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []int16{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []int32{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []int32{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []int64{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []int64{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []uint{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []uint{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []uint8{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []uint8{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []uint16{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []uint16{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []uint32{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []uint32{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []uint64{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []uint64{1}),
+			"a IN (?)",
+			[]interface{}{1},
+		},
+		{
+			In("a", []string{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []interface{}{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []MyInt{}),
+			"0=1",
+			[]interface{}{},
+		},
+		{
+			In("a", []interface{}{1, 2, 3}).And(Eq{"b": "c"}),
+			"a IN (?,?,?) AND b=?",
+			[]interface{}{1, 2, 3, "c"},
+		},
+		{
+			In("a", Select("id").From("b").Where(Eq{"c": 1})),
+			"a IN (SELECT id FROM b WHERE c=?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", Expr("select id from x where name > ?", "b")),
+			"a NOT IN (select id from x where name > ?)",
+			[]interface{}{"b"},
+		},
+		{
+			NotIn("a", []int{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []int{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []int8{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []int8{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []int16{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []int16{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []int32{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []int32{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []int64{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []int64{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []uint{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []uint{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []uint8{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []uint8{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []uint16{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []uint16{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []uint32{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []uint32{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []uint64{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []uint64{1}),
+			"a NOT IN (?)",
+			[]interface{}{1},
+		},
+		{
+			NotIn("a", []interface{}{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []string{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []MyInt{}),
+			"0=0",
+			[]interface{}{},
+		},
+		{
+			NotIn("a", []MyInt{1, 2}),
+			"a NOT IN (?,?)",
+			[]interface{}{1, 2},
+		},
+		{
+			NotIn("a", []interface{}{1, 2, 3}).And(Eq{"b": "c"}),
+			"a NOT IN (?,?,?) AND b=?",
+			[]interface{}{1, 2, 3, "c"},
+		},
+		{
+			NotIn("a", []interface{}{1, 2, 3}).Or(Eq{"b": "c"}),
+			"a NOT IN (?,?,?) OR b=?",
+			[]interface{}{1, 2, 3, "c"},
+		},
+		{
+			NotIn("a", Select("id").From("b").Where(Eq{"c": 1})),
+			"a NOT IN (SELECT id FROM b WHERE c=?)",
+			[]interface{}{1},
+		},
+		{
+			Or(Eq{"a": 1, "b": 2}, Eq{"c": 3, "d": 4}),
+			"(a=? AND b=?) OR (c=? AND d=?)",
+			[]interface{}{1, 2, 3, 4},
+		},
+		{
+			Not{Eq{"a": 1, "b": 2}},
+			"NOT (a=? AND b=?)",
+			[]interface{}{1, 2},
+		},
+		{
+			Not{Neq{"a": 1, "b": 2}},
+			"NOT (a<>? AND b<>?)",
+			[]interface{}{1, 2},
+		},
+		{
+			Not{Eq{"a": 1}.And(Eq{"b": 2})},
+			"NOT (a=? AND b=?)",
+			[]interface{}{1, 2},
+		},
+		{
+			Not{Neq{"a": 1}.And(Neq{"b": 2})},
+			"NOT (a<>? AND b<>?)",
+			[]interface{}{1, 2},
+		},
+		{
+			Not{Eq{"a": 1}}.And(Neq{"b": 2}),
+			"NOT a=? AND b<>?",
+			[]interface{}{1, 2},
+		},
+		{
+			Not{Eq{"a": 1}}.Or(Neq{"b": 2}),
+			"NOT a=? OR b<>?",
+			[]interface{}{1, 2},
+		},
+	}
+
+	for _, k := range cases {
+		sql, args, err := ToSQL(k.cond)
+		assert.NoError(t, err)
+		assert.EqualValues(t, k.sql, sql)
+
+		for i := 0; i < 10; i++ {
+			sql2, _, err := ToSQL(k.cond)
+			assert.NoError(t, err)
+			assert.EqualValues(t, sql, sql2)
+		}
+
+		assert.EqualValues(t, len(args), len(k.args))
+
+		if len(args) > 0 {
+			for i := 0; i < len(args); i++ {
+				assert.EqualValues(t, k.args[i], args[i])
+			}
+		}
+	}
+}
+
+func TestSubquery(t *testing.T) {
+	subb := Select("id").From("table_b").Where(Eq{"b": "a"})
+	b := Select("a, b").From("table_a").Where(
+		Eq{
+			"b_id": subb,
+			"id":   23,
+		},
+	)
+	sql, args, err := b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=?) AND id=?", sql)
+	assert.EqualValues(t, []interface{}{"a", 23}, args)
+}
+
+// https://github.com/go-xorm/xorm/issues/820
+func TestExprCond(t *testing.T) {
+	b := Select("id").From("table1").Where(expr{sql: "a=? OR b=?", args: []interface{}{1, 2}}).Where(Or(Eq{"c": 3}, Eq{"d": 4}))
+	sql, args, err := b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "table1", b.TableName())
+	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)
+}
+
+func TestBuilder_ToBoundSQL(t *testing.T) {
+	newSQL, err := Select("id").From("table").Where(In("a", 1, 2)).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT id FROM table WHERE a IN (1,2)", newSQL)
+}
+
+func TestBuilder_From2(t *testing.T) {
+	b := Select("id").From("table_b", "tb").Where(Eq{"b": "a"})
+	sql, args, err := b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT id FROM table_b tb WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{"a"}, args)
+
+	b = Select().From("table_b", "tb").Where(Eq{"b": "a"})
+	sql, args, err = b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table_b tb WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{"a"}, args)
+}
+
+func TestBuilder_And(t *testing.T) {
+	b := Select("id").From("table_b", "tb").Where(Eq{"b": "a"}).And(Neq{"c": "d"})
+	sql, args, err := b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT id FROM table_b tb WHERE b=? AND c<>?", sql)
+	assert.EqualValues(t, []interface{}{"a", "d"}, args)
+}
+
+func TestBuilder_Or(t *testing.T) {
+	b := Select("id").From("table_b", "tb").Where(Eq{"b": "a"}).Or(Neq{"c": "d"})
+	sql, args, err := b.ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT id FROM table_b tb WHERE b=? OR c<>?", sql)
+	assert.EqualValues(t, []interface{}{"a", "d"}, args)
+}

+ 47 - 0
builder_union.go

@@ -0,0 +1,47 @@
+// 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"
+	"strings"
+)
+
+func (b *Builder) unionWriteTo(w Writer) error {
+	if b.limitation != nil || b.cond.IsValid() ||
+		b.orderBy != "" || b.having != "" || b.groupBy != "" {
+		return ErrNotUnexpectedUnionConditions
+	}
+
+	for idx, u := range b.unions {
+		current := u.builder
+		if current.optype != selectType {
+			return ErrUnsupportedUnionMembers
+		}
+
+		if len(b.unions) == 1 {
+			if err := current.selectWriteTo(w); err != nil {
+				return err
+			}
+		} else {
+			if b.dialect != "" && b.dialect != current.dialect {
+				return ErrInconsistentDialect
+			}
+
+			if idx != 0 {
+				fmt.Fprint(w, fmt.Sprintf(" UNION %v ", strings.ToUpper(u.unionType)))
+			}
+			fmt.Fprint(w, "(")
+
+			if err := current.selectWriteTo(w); err != nil {
+				return err
+			}
+
+			fmt.Fprint(w, ")")
+		}
+	}
+
+	return nil
+}

+ 74 - 0
builder_union_test.go

@@ -0,0 +1,74 @@
+// 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"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuilder_Union(t *testing.T) {
+	sql, args, err := Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"})).
+		Union("distinct", Select("*").From("t2").Where(Eq{"status": "3"})).
+		Union("", Select("*").From("t2").Where(Eq{"status": "3"})).
+		ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) UNION ALL (SELECT * FROM t2 WHERE status=?) UNION DISTINCT (SELECT * FROM t2 WHERE status=?) UNION  (SELECT * FROM t2 WHERE status=?)", sql)
+	assert.EqualValues(t, []interface{}{"1", "2", "3", "3"}, args)
+
+	// sub-query will inherit dialect from the main one
+	sql, args, err = MySQL().Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)).
+		Union("", Select("*").From("t2").Where(Eq{"status": "3"})).
+		ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) UNION ALL (SELECT * FROM t2 WHERE status=? LIMIT 10) UNION  (SELECT * FROM t2 WHERE status=?)", sql)
+	assert.EqualValues(t, []interface{}{"1", "2", "3"}, args)
+
+	// will raise error
+	sql, args, err = MySQL().Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Oracle().Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)).
+		ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrInconsistentDialect, err)
+
+	// will raise error
+	sql, args, err = Select("*").From("table1").Where(Eq{"a": "1"}).
+		Union("all", Select("*").From("table2").Where(Eq{"a": "2"})).
+		Where(Eq{"a": 2}).Limit(5, 10).
+		ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNotUnexpectedUnionConditions, err)
+
+	// will raise error
+	sql, args, err = Delete(Eq{"a": 1}).From("t1").
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"})).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrUnsupportedUnionMembers, err)
+
+	// will be overwrote by SELECT op
+	sql, args, err = Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"})).
+		Select("*").From("t2").ToSQL()
+	assert.NoError(t, err)
+	fmt.Println(sql, args)
+
+	// will be overwrote by DELETE op
+	sql, args, err = Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"})).
+		Delete(Eq{"status": "1"}).From("t2").ToSQL()
+	assert.NoError(t, err)
+	fmt.Println(sql, args)
+
+	// will be overwrote by INSERT op
+	sql, args, err = Select("*").From("t1").Where(Eq{"status": "1"}).
+		Union("all", Select("*").From("t2").Where(Eq{"status": "2"})).
+		Insert(Eq{"status": "1"}).Into("t2").ToSQL()
+	assert.NoError(t, err)
+	fmt.Println(sql, args)
+}

+ 46 - 0
builder_update.go

@@ -0,0 +1,46 @@
+// Copyright 2016 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"
+)
+
+// Update creates an update Builder
+func Update(updates ...Eq) *Builder {
+	builder := &Builder{cond: NewCond()}
+	return builder.Update(updates...)
+}
+
+func (b *Builder) updateWriteTo(w Writer) error {
+	if len(b.from) <= 0 {
+		return ErrNoTableName
+	}
+	if len(b.updates) <= 0 {
+		return ErrNoColumnToUpdate
+	}
+
+	if _, err := fmt.Fprintf(w, "UPDATE %s SET ", b.from); err != nil {
+		return err
+	}
+
+	for i, s := range b.updates {
+		if err := s.opWriteTo(",", w); err != nil {
+			return err
+		}
+
+		if i != len(b.updates)-1 {
+			if _, err := fmt.Fprint(w, ","); err != nil {
+				return err
+			}
+		}
+	}
+
+	if _, err := fmt.Fprint(w, " WHERE "); err != nil {
+		return err
+	}
+
+	return b.cond.WriteTo(w)
+}

+ 52 - 0
builder_update_test.go

@@ -0,0 +1,52 @@
+// 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"
+)
+
+func TestBuilderUpdate(t *testing.T) {
+	sql, args, err := Update(Eq{"a": 2}).From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=? WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 2, "b": 1}).From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 2}, Eq{"b": 1}).From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 2, "b": Incr(1)}).From("table2").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table2 SET a=?,b=b+? WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 2, "b": Incr(1), "c": Decr(1), "d": Expr("select count(*) from table2")}).From("table2").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table2 SET a=?,b=b+?,c=c-?,d=(select count(*) from table2) WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 2}).Where(Eq{"a": 1}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoTableName, err)
+
+	sql, args, err = Update(Eq{}).From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNoColumnToUpdate, err)
+
+	var builder = Builder{cond: NewCond()}
+	sql, args, err = builder.Update(Eq{"a": 2, "b": 1}).From("table1").Where(Eq{"a": 1}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
+	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+}

+ 74 - 0
cond.go

@@ -0,0 +1,74 @@
+// Copyright 2016 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 (
+	"io"
+)
+
+// Writer defines the interface
+type Writer interface {
+	io.Writer
+	Append(...interface{})
+}
+
+var _ Writer = NewWriter()
+
+// BytesWriter implments Writer and save SQL in bytes.Buffer
+type BytesWriter struct {
+	writer *StringBuilder
+	args   []interface{}
+}
+
+// NewWriter creates a new string writer
+func NewWriter() *BytesWriter {
+	w := &BytesWriter{
+		writer: &StringBuilder{},
+	}
+	return w
+}
+
+// Write writes data to Writer
+func (s *BytesWriter) Write(buf []byte) (int, error) {
+	return s.writer.Write(buf)
+}
+
+// Append appends args to Writer
+func (s *BytesWriter) Append(args ...interface{}) {
+	s.args = append(s.args, args...)
+}
+
+// Cond defines an interface
+type Cond interface {
+	WriteTo(Writer) error
+	And(...Cond) Cond
+	Or(...Cond) Cond
+	IsValid() bool
+}
+
+type condEmpty struct{}
+
+var _ Cond = condEmpty{}
+
+// NewCond creates an empty condition
+func NewCond() Cond {
+	return condEmpty{}
+}
+
+func (condEmpty) WriteTo(w Writer) error {
+	return nil
+}
+
+func (condEmpty) And(conds ...Cond) Cond {
+	return And(conds...)
+}
+
+func (condEmpty) Or(conds ...Cond) Cond {
+	return Or(conds...)
+}
+
+func (condEmpty) IsValid() bool {
+	return false
+}

+ 61 - 0
cond_and.go

@@ -0,0 +1,61 @@
+// Copyright 2016 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"
+
+type condAnd []Cond
+
+var _ Cond = condAnd{}
+
+// And generates AND conditions
+func And(conds ...Cond) Cond {
+	var result = make(condAnd, 0, len(conds))
+	for _, cond := range conds {
+		if cond == nil || !cond.IsValid() {
+			continue
+		}
+		result = append(result, cond)
+	}
+	return result
+}
+
+func (and condAnd) WriteTo(w Writer) error {
+	for i, cond := range and {
+		_, isOr := cond.(condOr)
+		_, isExpr := cond.(expr)
+		wrap := isOr || isExpr
+		if wrap {
+			fmt.Fprint(w, "(")
+		}
+
+		err := cond.WriteTo(w)
+		if err != nil {
+			return err
+		}
+
+		if wrap {
+			fmt.Fprint(w, ")")
+		}
+
+		if i != len(and)-1 {
+			fmt.Fprint(w, " AND ")
+		}
+	}
+
+	return nil
+}
+
+func (and condAnd) And(conds ...Cond) Cond {
+	return And(and, And(conds...))
+}
+
+func (and condAnd) Or(conds ...Cond) Cond {
+	return Or(and, Or(conds...))
+}
+
+func (and condAnd) IsValid() bool {
+	return len(and) > 0
+}

+ 65 - 0
cond_between.go

@@ -0,0 +1,65 @@
+// Copyright 2016 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"
+
+// Between implmentes between condition
+type Between struct {
+	Col     string
+	LessVal interface{}
+	MoreVal interface{}
+}
+
+var _ Cond = Between{}
+
+// WriteTo write data to Writer
+func (between Between) WriteTo(w Writer) error {
+	if _, err := fmt.Fprintf(w, "%s BETWEEN ", between.Col); err != nil {
+		return err
+	}
+	if lv, ok := between.LessVal.(expr); ok {
+		if err := lv.WriteTo(w); err != nil {
+			return err
+		}
+	} else {
+		if _, err := fmt.Fprint(w, "?"); err != nil {
+			return err
+		}
+		w.Append(between.LessVal)
+	}
+
+	if _, err := fmt.Fprint(w, " AND "); err != nil {
+		return err
+	}
+
+	if mv, ok := between.MoreVal.(expr); ok {
+		if err := mv.WriteTo(w); err != nil {
+			return err
+		}
+	} else {
+		if _, err := fmt.Fprint(w, "?"); err != nil {
+			return err
+		}
+		w.Append(between.MoreVal)
+	}
+
+	return nil
+}
+
+// And implments And with other conditions
+func (between Between) And(conds ...Cond) Cond {
+	return And(between, And(conds...))
+}
+
+// Or implments Or with other conditions
+func (between Between) Or(conds ...Cond) Cond {
+	return Or(between, Or(conds...))
+}
+
+// IsValid tests if the condition is valid
+func (between Between) IsValid() bool {
+	return len(between.Col) > 0
+}

+ 160 - 0
cond_compare.go

@@ -0,0 +1,160 @@
+// Copyright 2016 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"
+
+// WriteMap writes conditions' SQL to Writer, op could be =, <>, >, <, <=, >= and etc.
+func WriteMap(w Writer, data map[string]interface{}, op string) error {
+	var args = make([]interface{}, 0, len(data))
+	var i = 0
+	keys := make([]string, 0, len(data))
+	for k := range data {
+		keys = append(keys, k)
+	}
+
+	for _, k := range keys {
+		v := data[k]
+		switch v.(type) {
+		case expr:
+			if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil {
+				return err
+			}
+
+			if err := v.(expr).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		case *Builder:
+			if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil {
+				return err
+			}
+
+			if err := v.(*Builder).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		default:
+			if _, err := fmt.Fprintf(w, "%s%s?", k, op); err != nil {
+				return err
+			}
+			args = append(args, v)
+		}
+		if i != len(data)-1 {
+			if _, err := fmt.Fprint(w, " AND "); err != nil {
+				return err
+			}
+		}
+		i = i + 1
+	}
+	w.Append(args...)
+	return nil
+}
+
+// Lt defines < condition
+type Lt map[string]interface{}
+
+var _ Cond = Lt{}
+
+// WriteTo write SQL to Writer
+func (lt Lt) WriteTo(w Writer) error {
+	return WriteMap(w, lt, "<")
+}
+
+// And implements And with other conditions
+func (lt Lt) And(conds ...Cond) Cond {
+	return condAnd{lt, And(conds...)}
+}
+
+// Or implements Or with other conditions
+func (lt Lt) Or(conds ...Cond) Cond {
+	return condOr{lt, Or(conds...)}
+}
+
+// IsValid tests if this Eq is valid
+func (lt Lt) IsValid() bool {
+	return len(lt) > 0
+}
+
+// Lte defines <= condition
+type Lte map[string]interface{}
+
+var _ Cond = Lte{}
+
+// WriteTo write SQL to Writer
+func (lte Lte) WriteTo(w Writer) error {
+	return WriteMap(w, lte, "<=")
+}
+
+// And implements And with other conditions
+func (lte Lte) And(conds ...Cond) Cond {
+	return And(lte, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (lte Lte) Or(conds ...Cond) Cond {
+	return Or(lte, Or(conds...))
+}
+
+// IsValid tests if this Eq is valid
+func (lte Lte) IsValid() bool {
+	return len(lte) > 0
+}
+
+// Gt defines > condition
+type Gt map[string]interface{}
+
+var _ Cond = Gt{}
+
+// WriteTo write SQL to Writer
+func (gt Gt) WriteTo(w Writer) error {
+	return WriteMap(w, gt, ">")
+}
+
+// And implements And with other conditions
+func (gt Gt) And(conds ...Cond) Cond {
+	return And(gt, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (gt Gt) Or(conds ...Cond) Cond {
+	return Or(gt, Or(conds...))
+}
+
+// IsValid tests if this Eq is valid
+func (gt Gt) IsValid() bool {
+	return len(gt) > 0
+}
+
+// Gte defines >= condition
+type Gte map[string]interface{}
+
+var _ Cond = Gte{}
+
+// WriteTo write SQL to Writer
+func (gte Gte) WriteTo(w Writer) error {
+	return WriteMap(w, gte, ">=")
+}
+
+// And implements And with other conditions
+func (gte Gte) And(conds ...Cond) Cond {
+	return And(gte, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (gte Gte) Or(conds ...Cond) Cond {
+	return Or(gte, Or(conds...))
+}
+
+// IsValid tests if this Eq is valid
+func (gte Gte) IsValid() bool {
+	return len(gte) > 0
+}

+ 112 - 0
cond_eq.go

@@ -0,0 +1,112 @@
+// Copyright 2016 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"
+	"sort"
+)
+
+// Incr implements a type used by Eq
+type Incr int
+
+// Decr implements a type used by Eq
+type Decr int
+
+// Eq defines equals conditions
+type Eq map[string]interface{}
+
+var _ Cond = Eq{}
+
+func (eq Eq) opWriteTo(op string, w Writer) error {
+	var i = 0
+	for _, k := range eq.sortedKeys() {
+		v := eq[k]
+		switch v.(type) {
+		case []int, []int64, []string, []int32, []int16, []int8, []uint, []uint64, []uint32, []uint16, []interface{}:
+			if err := In(k, v).WriteTo(w); err != nil {
+				return err
+			}
+		case expr:
+			if _, err := fmt.Fprintf(w, "%s=(", k); err != nil {
+				return err
+			}
+
+			if err := v.(expr).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		case *Builder:
+			if _, err := fmt.Fprintf(w, "%s=(", k); err != nil {
+				return err
+			}
+
+			if err := v.(*Builder).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		case Incr:
+			if _, err := fmt.Fprintf(w, "%s=%s+?", k, k); err != nil {
+				return err
+			}
+			w.Append(int(v.(Incr)))
+		case Decr:
+			if _, err := fmt.Fprintf(w, "%s=%s-?", k, k); err != nil {
+				return err
+			}
+			w.Append(int(v.(Decr)))
+		default:
+			if _, err := fmt.Fprintf(w, "%s=?", k); err != nil {
+				return err
+			}
+			w.Append(v)
+		}
+		if i != len(eq)-1 {
+			if _, err := fmt.Fprint(w, op); err != nil {
+				return err
+			}
+		}
+		i = i + 1
+	}
+	return nil
+}
+
+// WriteTo writes SQL to Writer
+func (eq Eq) WriteTo(w Writer) error {
+	return eq.opWriteTo(" AND ", w)
+}
+
+// And implements And with other conditions
+func (eq Eq) And(conds ...Cond) Cond {
+	return And(eq, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (eq Eq) Or(conds ...Cond) Cond {
+	return Or(eq, Or(conds...))
+}
+
+// IsValid tests if this Eq is valid
+func (eq Eq) IsValid() bool {
+	return len(eq) > 0
+}
+
+// sortedKeys returns all keys of this Eq sorted with sort.Strings.
+// It is used internally for consistent ordering when generating
+// SQL, see https://github.com/go-xorm/builder/issues/10
+func (eq Eq) sortedKeys() []string {
+	keys := make([]string, 0, len(eq))
+	for key := range eq {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+	return keys
+}

+ 39 - 0
cond_expr.go

@@ -0,0 +1,39 @@
+// Copyright 2016 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"
+
+type expr struct {
+	sql  string
+	args []interface{}
+}
+
+var _ Cond = expr{}
+
+// Expr generate customerize SQL
+func Expr(sql string, args ...interface{}) Cond {
+	return expr{sql, args}
+}
+
+func (expr expr) WriteTo(w Writer) error {
+	if _, err := fmt.Fprint(w, expr.sql); err != nil {
+		return err
+	}
+	w.Append(expr.args...)
+	return nil
+}
+
+func (expr expr) And(conds ...Cond) Cond {
+	return And(expr, And(conds...))
+}
+
+func (expr expr) Or(conds ...Cond) Cond {
+	return Or(expr, Or(conds...))
+}
+
+func (expr expr) IsValid() bool {
+	return len(expr.sql) > 0
+}

+ 237 - 0
cond_in.go

@@ -0,0 +1,237 @@
+// Copyright 2016 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"
+	"strings"
+)
+
+type condIn struct {
+	col  string
+	vals []interface{}
+}
+
+var _ Cond = condIn{}
+
+// In generates IN condition
+func In(col string, values ...interface{}) Cond {
+	return condIn{col, values}
+}
+
+func (condIn condIn) handleBlank(w Writer) error {
+	_, err := fmt.Fprint(w, "0=1")
+	return err
+}
+
+func (condIn condIn) WriteTo(w Writer) error {
+	if len(condIn.vals) <= 0 {
+		return condIn.handleBlank(w)
+	}
+
+	switch condIn.vals[0].(type) {
+	case []int8:
+		vals := condIn.vals[0].([]int8)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int16:
+		vals := condIn.vals[0].([]int16)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int:
+		vals := condIn.vals[0].([]int)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int32:
+		vals := condIn.vals[0].([]int32)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int64:
+		vals := condIn.vals[0].([]int64)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint8:
+		vals := condIn.vals[0].([]uint8)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint16:
+		vals := condIn.vals[0].([]uint16)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint:
+		vals := condIn.vals[0].([]uint)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint32:
+		vals := condIn.vals[0].([]uint32)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint64:
+		vals := condIn.vals[0].([]uint64)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []string:
+		vals := condIn.vals[0].([]string)
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []interface{}:
+		vals := condIn.vals[0].([]interface{})
+		if len(vals) <= 0 {
+			return condIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		w.Append(vals...)
+	case expr:
+		val := condIn.vals[0].(expr)
+		if _, err := fmt.Fprintf(w, "%s IN (", condIn.col); err != nil {
+			return err
+		}
+		if err := val.WriteTo(w); err != nil {
+			return err
+		}
+		if _, err := fmt.Fprintf(w, ")"); err != nil {
+			return err
+		}
+	case *Builder:
+		bd := condIn.vals[0].(*Builder)
+		if _, err := fmt.Fprintf(w, "%s IN (", condIn.col); err != nil {
+			return err
+		}
+		if err := bd.WriteTo(w); err != nil {
+			return err
+		}
+		if _, err := fmt.Fprintf(w, ")"); err != nil {
+			return err
+		}
+	default:
+		v := reflect.ValueOf(condIn.vals[0])
+		if v.Kind() == reflect.Slice {
+			l := v.Len()
+			if l == 0 {
+				return condIn.handleBlank(w)
+			}
+
+			questionMark := strings.Repeat("?,", l)
+			if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+				return err
+			}
+
+			for i := 0; i < l; i++ {
+				w.Append(v.Index(i).Interface())
+			}
+		} else {
+			questionMark := strings.Repeat("?,", len(condIn.vals))
+			if _, err := fmt.Fprintf(w, "%s IN (%s)", condIn.col, questionMark[:len(questionMark)-1]); err != nil {
+				return err
+			}
+			w.Append(condIn.vals...)
+		}
+	}
+	return nil
+}
+
+func (condIn condIn) And(conds ...Cond) Cond {
+	return And(condIn, And(conds...))
+}
+
+func (condIn condIn) Or(conds ...Cond) Cond {
+	return Or(condIn, Or(conds...))
+}
+
+func (condIn condIn) IsValid() bool {
+	return len(condIn.col) > 0 && len(condIn.vals) > 0
+}

+ 41 - 0
cond_like.go

@@ -0,0 +1,41 @@
+// Copyright 2016 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"
+
+// Like defines like condition
+type Like [2]string
+
+var _ Cond = Like{"", ""}
+
+// WriteTo write SQL to Writer
+func (like Like) WriteTo(w Writer) error {
+	if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil {
+		return err
+	}
+	// FIXME: if use other regular express, this will be failed. but for compatible, keep this
+	if like[1][0] == '%' || like[1][len(like[1])-1] == '%' {
+		w.Append(like[1])
+	} else {
+		w.Append("%" + like[1] + "%")
+	}
+	return nil
+}
+
+// And implements And with other conditions
+func (like Like) And(conds ...Cond) Cond {
+	return And(like, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (like Like) Or(conds ...Cond) Cond {
+	return Or(like, Or(conds...))
+}
+
+// IsValid tests if this condition is valid
+func (like Like) IsValid() bool {
+	return len(like[0]) > 0 && len(like[1]) > 0
+}

+ 94 - 0
cond_neq.go

@@ -0,0 +1,94 @@
+// Copyright 2016 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"
+	"sort"
+)
+
+// Neq defines not equal conditions
+type Neq map[string]interface{}
+
+var _ Cond = Neq{}
+
+// WriteTo writes SQL to Writer
+func (neq Neq) WriteTo(w Writer) error {
+	var args = make([]interface{}, 0, len(neq))
+	var i = 0
+	for _, k := range neq.sortedKeys() {
+		v := neq[k]
+		switch v.(type) {
+		case []int, []int64, []string, []int32, []int16, []int8:
+			if err := NotIn(k, v).WriteTo(w); err != nil {
+				return err
+			}
+		case expr:
+			if _, err := fmt.Fprintf(w, "%s<>(", k); err != nil {
+				return err
+			}
+
+			if err := v.(expr).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		case *Builder:
+			if _, err := fmt.Fprintf(w, "%s<>(", k); err != nil {
+				return err
+			}
+
+			if err := v.(*Builder).WriteTo(w); err != nil {
+				return err
+			}
+
+			if _, err := fmt.Fprintf(w, ")"); err != nil {
+				return err
+			}
+		default:
+			if _, err := fmt.Fprintf(w, "%s<>?", k); err != nil {
+				return err
+			}
+			args = append(args, v)
+		}
+		if i != len(neq)-1 {
+			if _, err := fmt.Fprint(w, " AND "); err != nil {
+				return err
+			}
+		}
+		i = i + 1
+	}
+	w.Append(args...)
+	return nil
+}
+
+// And implements And with other conditions
+func (neq Neq) And(conds ...Cond) Cond {
+	return And(neq, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (neq Neq) Or(conds ...Cond) Cond {
+	return Or(neq, Or(conds...))
+}
+
+// IsValid tests if this condition is valid
+func (neq Neq) IsValid() bool {
+	return len(neq) > 0
+}
+
+// sortedKeys returns all keys of this Neq sorted with sort.Strings.
+// It is used internally for consistent ordering when generating
+// SQL, see https://github.com/go-xorm/builder/issues/10
+func (neq Neq) sortedKeys() []string {
+	keys := make([]string, 0, len(neq))
+	for key := range neq {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+	return keys
+}

+ 77 - 0
cond_not.go

@@ -0,0 +1,77 @@
+// Copyright 2016 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"
+
+// Not defines NOT condition
+type Not [1]Cond
+
+var _ Cond = Not{}
+
+// WriteTo writes SQL to Writer
+func (not Not) WriteTo(w Writer) error {
+	if _, err := fmt.Fprint(w, "NOT "); err != nil {
+		return err
+	}
+	switch not[0].(type) {
+	case condAnd, condOr:
+		if _, err := fmt.Fprint(w, "("); err != nil {
+			return err
+		}
+	case Eq:
+		if len(not[0].(Eq)) > 1 {
+			if _, err := fmt.Fprint(w, "("); err != nil {
+				return err
+			}
+		}
+	case Neq:
+		if len(not[0].(Neq)) > 1 {
+			if _, err := fmt.Fprint(w, "("); err != nil {
+				return err
+			}
+		}
+	}
+
+	if err := not[0].WriteTo(w); err != nil {
+		return err
+	}
+
+	switch not[0].(type) {
+	case condAnd, condOr:
+		if _, err := fmt.Fprint(w, ")"); err != nil {
+			return err
+		}
+	case Eq:
+		if len(not[0].(Eq)) > 1 {
+			if _, err := fmt.Fprint(w, ")"); err != nil {
+				return err
+			}
+		}
+	case Neq:
+		if len(not[0].(Neq)) > 1 {
+			if _, err := fmt.Fprint(w, ")"); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// And implements And with other conditions
+func (not Not) And(conds ...Cond) Cond {
+	return And(not, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (not Not) Or(conds ...Cond) Cond {
+	return Or(not, Or(conds...))
+}
+
+// IsValid tests if this condition is valid
+func (not Not) IsValid() bool {
+	return not[0] != nil && not[0].IsValid()
+}

+ 234 - 0
cond_notin.go

@@ -0,0 +1,234 @@
+// Copyright 2016 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"
+	"strings"
+)
+
+type condNotIn condIn
+
+var _ Cond = condNotIn{}
+
+// NotIn generate NOT IN condition
+func NotIn(col string, values ...interface{}) Cond {
+	return condNotIn{col, values}
+}
+
+func (condNotIn condNotIn) handleBlank(w Writer) error {
+	_, err := fmt.Fprint(w, "0=0")
+	return err
+}
+
+func (condNotIn condNotIn) WriteTo(w Writer) error {
+	if len(condNotIn.vals) <= 0 {
+		return condNotIn.handleBlank(w)
+	}
+
+	switch condNotIn.vals[0].(type) {
+	case []int8:
+		vals := condNotIn.vals[0].([]int8)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int16:
+		vals := condNotIn.vals[0].([]int16)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int:
+		vals := condNotIn.vals[0].([]int)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int32:
+		vals := condNotIn.vals[0].([]int32)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []int64:
+		vals := condNotIn.vals[0].([]int64)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint8:
+		vals := condNotIn.vals[0].([]uint8)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint16:
+		vals := condNotIn.vals[0].([]uint16)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint:
+		vals := condNotIn.vals[0].([]uint)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint32:
+		vals := condNotIn.vals[0].([]uint32)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []uint64:
+		vals := condNotIn.vals[0].([]uint64)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []string:
+		vals := condNotIn.vals[0].([]string)
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		for _, val := range vals {
+			w.Append(val)
+		}
+	case []interface{}:
+		vals := condNotIn.vals[0].([]interface{})
+		if len(vals) <= 0 {
+			return condNotIn.handleBlank(w)
+		}
+		questionMark := strings.Repeat("?,", len(vals))
+		if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+			return err
+		}
+		w.Append(vals...)
+	case expr:
+		val := condNotIn.vals[0].(expr)
+		if _, err := fmt.Fprintf(w, "%s NOT IN (", condNotIn.col); err != nil {
+			return err
+		}
+		if err := val.WriteTo(w); err != nil {
+			return err
+		}
+		if _, err := fmt.Fprintf(w, ")"); err != nil {
+			return err
+		}
+	case *Builder:
+		val := condNotIn.vals[0].(*Builder)
+		if _, err := fmt.Fprintf(w, "%s NOT IN (", condNotIn.col); err != nil {
+			return err
+		}
+		if err := val.WriteTo(w); err != nil {
+			return err
+		}
+		if _, err := fmt.Fprintf(w, ")"); err != nil {
+			return err
+		}
+	default:
+		v := reflect.ValueOf(condNotIn.vals[0])
+		if v.Kind() == reflect.Slice {
+			l := v.Len()
+			if l == 0 {
+				return condNotIn.handleBlank(w)
+			}
+
+			questionMark := strings.Repeat("?,", l)
+			if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+				return err
+			}
+
+			for i := 0; i < l; i++ {
+				w.Append(v.Index(i).Interface())
+			}
+		} else {
+			questionMark := strings.Repeat("?,", len(condNotIn.vals))
+			if _, err := fmt.Fprintf(w, "%s NOT IN (%s)", condNotIn.col, questionMark[:len(questionMark)-1]); err != nil {
+				return err
+			}
+			w.Append(condNotIn.vals...)
+		}
+	}
+	return nil
+}
+
+func (condNotIn condNotIn) And(conds ...Cond) Cond {
+	return And(condNotIn, And(conds...))
+}
+
+func (condNotIn condNotIn) Or(conds ...Cond) Cond {
+	return Or(condNotIn, Or(conds...))
+}
+
+func (condNotIn condNotIn) IsValid() bool {
+	return len(condNotIn.col) > 0 && len(condNotIn.vals) > 0
+}

+ 59 - 0
cond_null.go

@@ -0,0 +1,59 @@
+// Copyright 2016 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"
+
+// IsNull defines IS NULL condition
+type IsNull [1]string
+
+var _ Cond = IsNull{""}
+
+// WriteTo write SQL to Writer
+func (isNull IsNull) WriteTo(w Writer) error {
+	_, err := fmt.Fprintf(w, "%s IS NULL", isNull[0])
+	return err
+}
+
+// And implements And with other conditions
+func (isNull IsNull) And(conds ...Cond) Cond {
+	return And(isNull, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (isNull IsNull) Or(conds ...Cond) Cond {
+	return Or(isNull, Or(conds...))
+}
+
+// IsValid tests if this condition is valid
+func (isNull IsNull) IsValid() bool {
+	return len(isNull[0]) > 0
+}
+
+// NotNull defines NOT NULL condition
+type NotNull [1]string
+
+var _ Cond = NotNull{""}
+
+// WriteTo write SQL to Writer
+func (notNull NotNull) WriteTo(w Writer) error {
+	_, err := fmt.Fprintf(w, "%s IS NOT NULL", notNull[0])
+	return err
+}
+
+// And implements And with other conditions
+func (notNull NotNull) And(conds ...Cond) Cond {
+	return And(notNull, And(conds...))
+}
+
+// Or implements Or with other conditions
+func (notNull NotNull) Or(conds ...Cond) Cond {
+	return Or(notNull, Or(conds...))
+}
+
+// IsValid tests if this condition is valid
+func (notNull NotNull) IsValid() bool {
+	return len(notNull[0]) > 0
+}

+ 69 - 0
cond_or.go

@@ -0,0 +1,69 @@
+// Copyright 2016 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"
+
+type condOr []Cond
+
+var _ Cond = condOr{}
+
+// Or sets OR conditions
+func Or(conds ...Cond) Cond {
+	var result = make(condOr, 0, len(conds))
+	for _, cond := range conds {
+		if cond == nil || !cond.IsValid() {
+			continue
+		}
+		result = append(result, cond)
+	}
+	return result
+}
+
+// WriteTo implments Cond
+func (o condOr) WriteTo(w Writer) error {
+	for i, cond := range o {
+		var needQuote bool
+		switch cond.(type) {
+		case condAnd, expr:
+			needQuote = true
+		case Eq:
+			needQuote = (len(cond.(Eq)) > 1)
+		case Neq:
+			needQuote = (len(cond.(Neq)) > 1)
+		}
+
+		if needQuote {
+			fmt.Fprint(w, "(")
+		}
+
+		err := cond.WriteTo(w)
+		if err != nil {
+			return err
+		}
+
+		if needQuote {
+			fmt.Fprint(w, ")")
+		}
+
+		if i != len(o)-1 {
+			fmt.Fprint(w, " OR ")
+		}
+	}
+
+	return nil
+}
+
+func (o condOr) And(conds ...Cond) Cond {
+	return And(o, And(conds...))
+}
+
+func (o condOr) Or(conds ...Cond) Cond {
+	return Or(o, Or(conds...))
+}
+
+func (o condOr) IsValid() bool {
+	return len(o) > 0
+}

+ 11 - 0
cond_test.go

@@ -0,0 +1,11 @@
+// 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"
+
+func TestCond_NotIn(t *testing.T) {
+
+}

+ 120 - 0
doc.go

@@ -0,0 +1,120 @@
+// Copyright 2016 The XORM Authors. All rights reserved.
+// Use of this source code is governed by a BSD
+// license that can be found in the LICENSE file.
+
+/*
+
+Package builder is a simple and powerful sql builder for Go.
+
+Make sure you have installed Go 1.1+ and then:
+
+    go get github.com/go-xorm/builder
+
+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
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Eq{"a":1})
+    // a=? [1]
+    sql, args, _ := ToSQL(Eq{"b":"c"}.And(Eq{"c": 0}))
+    // b=? AND c=? ["c", 0]
+    sql, args, _ := ToSQL(Eq{"b":"c", "c":0})
+    // b=? AND c=? ["c", 0]
+    sql, args, _ := ToSQL(Eq{"b":"c"}.Or(Eq{"b":"d"}))
+    // b=? OR b=? ["c", "d"]
+    sql, args, _ := ToSQL(Eq{"b": []string{"c", "d"}})
+    // b IN (?,?) ["c", "d"]
+    sql, args, _ := ToSQL(Eq{"b": 1, "c":[]int{2, 3}})
+    // b=? AND c IN (?,?) [1, 2, 3]
+
+2. Neq is the same to Eq
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Neq{"a":1})
+    // a<>? [1]
+    sql, args, _ := ToSQL(Neq{"b":"c"}.And(Neq{"c": 0}))
+    // b<>? AND c<>? ["c", 0]
+    sql, args, _ := ToSQL(Neq{"b":"c", "c":0})
+    // b<>? AND c<>? ["c", 0]
+    sql, args, _ := ToSQL(Neq{"b":"c"}.Or(Neq{"b":"d"}))
+    // b<>? OR b<>? ["c", "d"]
+    sql, args, _ := ToSQL(Neq{"b": []string{"c", "d"}})
+    // b NOT IN (?,?) ["c", "d"]
+    sql, args, _ := ToSQL(Neq{"b": 1, "c":[]int{2, 3}})
+    // b<>? AND c NOT IN (?,?) [1, 2, 3]
+
+3. Gt, Gte, Lt, Lte
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
+    // a>? AND b>=? [1, 2]
+    sql, args, _ := ToSQL(Lt{"a", 1}.Or(Lte{"b", 2}))
+    // a<? OR b<=? [1, 2]
+
+4. Like
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Like{"a", "c"})
+    // a LIKE ? [%c%]
+
+5. Expr you can customerize your sql with Expr
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Expr("a = ? ", 1))
+    // a = ? [1]
+    sql, args, _ := ToSQL(Eq{"a": Expr("select id from table where c = ?", 1)})
+    // a=(select id from table where c = ?) [1]
+
+6. In and NotIn
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(In("a", 1, 2, 3))
+    // a IN (?,?,?) [1,2,3]
+    sql, args, _ := ToSQL(In("a", []int{1, 2, 3}))
+    // a IN (?,?,?) [1,2,3]
+    sql, args, _ := ToSQL(In("a", Expr("select id from b where c = ?", 1))))
+    // a IN (select id from b where c = ?) [1]
+
+7. IsNull and NotNull
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(IsNull{"a"})
+    // a IS NULL []
+    sql, args, _ := ToSQL(NotNull{"b"})
+     // b IS NOT NULL []
+
+8. And(conds ...Cond), And can connect one or more condtions via AND
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
+    // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
+
+9. Or(conds ...Cond), Or can connect one or more conditions via Or
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
+    // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
+    sql, args, _ := ToSQL(Or(Eq{"a":1}, And(Like{"b", "c"}, Neq{"d", 2})))
+    // a=? OR (b LIKE ? AND d<>?) [1, %c%, 2]
+
+10. Between
+
+    import . "github.com/go-xorm/builder"
+
+    sql, args, _ := ToSQL(Between("a", 1, 2))
+    // a BETWEEN 1 AND 2
+
+11. define yourself conditions
+Since Cond is a interface, you can define yourself conditions and compare with them
+*/
+package builder

+ 40 - 0
error.go

@@ -0,0 +1,40 @@
+// Copyright 2016 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 "errors"
+
+var (
+	// ErrNotSupportType not supported SQL type error
+	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")
+	// ErrNoTableName no table name
+	ErrNoTableName = errors.New("No table indicated")
+	// ErrNoColumnToInsert no column to update
+	ErrNoColumnToUpdate = errors.New("No column(s) to update")
+	// ErrNoColumnToInsert no column to update
+	ErrNoColumnToInsert = errors.New("No column(s) to insert")
+	// ErrNotSupportDialectType not supported dialect type error
+	ErrNotSupportDialectType = errors.New("Not supported dialect type")
+	// ErrNotUnexpectedUnionConditions using union in a wrong way
+	ErrNotUnexpectedUnionConditions = errors.New("Unexpected conditional fields in UNION query")
+	// ErrUnsupportedUnionMembers unexpected members in UNION query
+	ErrUnsupportedUnionMembers = errors.New("Unexpected members in UNION query")
+	// ErrUnexpectedSubQuery Unexpected sub-query in SELECT query
+	ErrUnexpectedSubQuery = errors.New("Unexpected sub-query in SELECT query")
+	// ErrDialectNotSetUp dialect is not setup yet
+	ErrDialectNotSetUp = errors.New("Dialect is not setup yet, try to use `Dialect(dbType)` at first")
+	// ErrInvalidLimitation offset or limit is not correct
+	ErrInvalidLimitation = errors.New("Offset or limit is not correct")
+	// ErrUnnamedDerivedTable Every derived table must have its own alias
+	ErrUnnamedDerivedTable = errors.New("Every derived table must have its own alias")
+	// ErrInconsistentDialect Inconsistent dialect in same builder
+	ErrInconsistentDialect = errors.New("Inconsistent dialect in same builder")
+)

+ 1 - 0
go.mod

@@ -0,0 +1 @@
+module "github.com/go-xorm/builder"

+ 203 - 0
sql.go

@@ -0,0 +1,203 @@
+// 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"
+	"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 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.writer.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
+	case expr, *expr:
+		return true
+	}
+
+	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 := StringBuilder{}
+	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 {
+				_, err = fmt.Fprintf(&buf, "'%v'", arg)
+			}
+			if err != nil {
+				return "", err
+			}
+			j = j + 1
+		}
+	}
+	_, err := buf.WriteString(sql[start:])
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+// ConvertToBoundSQL will convert SQL and args to a bound SQL
+func ConvertExprToBoundSQL(sql string, args []interface{}) (string,[]interface{}, error) {
+	buf := StringBuilder{}
+	var i, j, start int
+	var sqlArgs []interface{}
+	for ; i < len(sql); i++ {
+		if sql[i] == '?' {
+			_, err := buf.WriteString(sql[start:i])
+			if err != nil {
+				return "",sqlArgs, err
+			}
+			start = i + 1
+
+			if len(args) == j {
+				return "",sqlArgs, ErrNeedMoreArguments
+			}
+
+			arg := args[j]
+			if exprArg, ok := arg.(expr);ok{
+				arg = exprArg.sql
+				_, err = fmt.Fprint(&buf, arg)
+				if err != nil {
+					return "",sqlArgs, err
+				}
+				for i,_:=range exprArg.args{
+					sqlArgs = append(sqlArgs, exprArg.args[i])
+				}
+			}else{
+				_, err = fmt.Fprint(&buf, "?")
+				if err != nil {
+					return "",sqlArgs, err
+				}
+				sqlArgs = append(sqlArgs, arg)
+			}
+
+			j = j + 1
+		}
+	}
+	_, err := buf.WriteString(sql[start:])
+	if err != nil {
+		return "",sqlArgs, err
+	}
+	return buf.String(), sqlArgs,nil
+}
+
+// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix
+func ConvertPlaceholder(sql, prefix string) (string, error) {
+	buf := StringBuilder{}
+	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
+}

+ 209 - 0
sql_test.go

@@ -0,0 +1,209 @@
+// 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"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/go-xorm/sqlfiddle"
+	"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 placeholderBoundSQL = "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 TestBoundSQLConverter(t *testing.T) {
+	newSQL, err := ConvertToBoundSQL(placeholderConverterSQL, []interface{}{1, 2.1, "3", uint(4), "5", true})
+	assert.NoError(t, err)
+	assert.EqualValues(t, placeholderBoundSQL, newSQL)
+
+	newSQL, err = ConvertToBoundSQL(placeholderConverterSQL, []interface{}{1, 2.1, sql2.Named("any", "3"), uint(4), "5", true})
+	assert.NoError(t, err)
+	assert.EqualValues(t, placeholderBoundSQL, newSQL)
+
+	newSQL, err = ConvertToBoundSQL(placeholderConverterSQL, []interface{}{1, 2.1, "3", 4, "5"})
+	assert.Error(t, err)
+	assert.EqualValues(t, ErrNeedMoreArguments, err)
+
+	newSQL, err = ToBoundSQL(Select("id").From("table").Where(In("a", 1, 2)))
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT id FROM table WHERE a IN (1,2)", newSQL)
+
+	newSQL, err = ToBoundSQL(Eq{"a": 1})
+	assert.NoError(t, err)
+	assert.EqualValues(t, "a=1", newSQL)
+
+	newSQL, err = ToBoundSQL(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)
+}
+
+type fiddler struct {
+	sessionCode string
+	dbType      int
+	f           *sqlfiddle.Fiddle
+}
+
+func readPreparationSQLFromFile(path string) (string, error) {
+	file, err := os.Open(path)
+	defer file.Close()
+	if err != nil {
+		return "", err
+	}
+
+	data, err := ioutil.ReadAll(file)
+	if err != nil {
+		return "", err
+	}
+
+	return string(data), nil
+}
+
+func newFiddler(fiddleServerAddr, dbDialect, preparationSQL string) (*fiddler, error) {
+	var dbType int
+	switch dbDialect {
+	case MYSQL:
+		dbType = sqlfiddle.Mysql5_6
+	case MSSQL:
+		dbType = sqlfiddle.MSSQL2017
+	case POSTGRES:
+		dbType = sqlfiddle.PostgreSQL96
+	case ORACLE:
+		dbType = sqlfiddle.Oracle11gR2
+	case SQLITE:
+		dbType = sqlfiddle.SQLite_WebSQL
+	default:
+		return nil, ErrNotSupportDialectType
+	}
+
+	f := sqlfiddle.NewFiddle(fiddleServerAddr)
+	response, err := f.CreateSchema(dbType, preparationSQL)
+	if err != nil {
+		return nil, err
+	}
+
+	return &fiddler{sessionCode: response.Code, f: f, dbType: dbType}, nil
+}
+
+func (f *fiddler) executableCheck(obj interface{}) error {
+	var sql string
+	var err error
+	switch obj.(type) {
+	case *Builder:
+		sql, err = obj.(*Builder).ToBoundSQL()
+		if err != nil {
+			return err
+		}
+	case string:
+		sql = obj.(string)
+	}
+
+	_, err = f.f.RunSQL(f.dbType, f.sessionCode, sql)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func TestReadPreparationSQLFromFile(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/mysql_fiddle_data.sql")
+	assert.NoError(t, err)
+	fmt.Println(sqlFromFile)
+}
+
+/*
+func TestNewFiddler(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/mysql_fiddle_data.sql")
+	assert.NoError(t, err)
+	f, err := newFiddler("", MYSQL, sqlFromFile)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, f.sessionCode)
+}
+
+func TestExecutableCheck(t *testing.T) {
+	sqlFromFile, err := readPreparationSQLFromFile("testdata/mysql_fiddle_data.sql")
+	assert.NoError(t, err)
+	f, err := newFiddler("", MYSQL, sqlFromFile)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, f.sessionCode)
+
+	assert.NoError(t, f.executableCheck("SELECT * FROM table1"))
+
+	err = f.executableCheck("SELECT * FROM table3")
+	assert.Error(t, err)
+}*/
+
+func TestToSQLInDifferentDialects(t *testing.T) {
+	sql, args, err := Postgres().Select().From("table1").Where(Eq{"a": "1"}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=$1 AND b<>$2", sql)
+	assert.EqualValues(t, []interface{}{"1", "100"}, args)
+
+	sql, args, err = MySQL().Select().From("table1").Where(Eq{"a": "1"}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=? AND b<>?", sql)
+	assert.EqualValues(t, []interface{}{"1", "100"}, args)
+
+	sql, args, err = MsSQL().Select().From("table1").Where(Eq{"a": "1"}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=@p1 AND b<>@p2", sql)
+	assert.EqualValues(t, []interface{}{sql2.Named("p1", "1"), sql2.Named("p2", "100")}, args)
+
+	// test sql.NamedArg in cond
+	sql, args, err = MsSQL().Select().From("table1").Where(Eq{"a": sql2.NamedArg{Name: "param", Value: "1"}}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=@p1 AND b<>@p2", sql)
+	assert.EqualValues(t, []interface{}{sql2.Named("p1", "1"), sql2.Named("p2", "100")}, args)
+
+	sql, args, err = Oracle().Select().From("table1").Where(Eq{"a": "1"}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=:p1 AND b<>:p2", sql)
+	assert.EqualValues(t, []interface{}{sql2.Named("p1", "1"), sql2.Named("p2", "100")}, args)
+
+	// test sql.NamedArg in cond
+	sql, args, err = Oracle().Select().From("table1").Where(Eq{"a": sql2.Named("a", "1")}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=:p1 AND b<>:p2", sql)
+	assert.EqualValues(t, []interface{}{sql2.Named("p1", "1"), sql2.Named("p2", "100")}, args)
+
+	sql, args, err = SQLite().Select().From("table1").Where(Eq{"a": "1"}.And(Neq{"b": "100"})).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=? AND b<>?", sql)
+	assert.EqualValues(t, []interface{}{"1", "100"}, args)
+}

+ 119 - 0
string_builder.go

@@ -0,0 +1,119 @@
+// Copyright 2017 The Go 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 (
+	"unicode/utf8"
+	"unsafe"
+)
+
+// A StringBuilder is used to efficiently build a string using Write methods.
+// It minimizes memory copying. The zero value is ready to use.
+// Do not copy a non-zero Builder.
+type StringBuilder struct {
+	addr *StringBuilder // of receiver, to detect copies by value
+	buf  []byte
+}
+
+// noescape hides a pointer from escape analysis.  noescape is
+// the identity function but escape analysis doesn't think the
+// output depends on the input. noescape is inlined and currently
+// compiles down to zero instructions.
+// USE CAREFULLY!
+// This was copied from the runtime; see issues 23382 and 7921.
+//go:nosplit
+func noescape(p unsafe.Pointer) unsafe.Pointer {
+	x := uintptr(p)
+	return unsafe.Pointer(x ^ 0)
+}
+
+func (b *StringBuilder) copyCheck() {
+	if b.addr == nil {
+		// This hack works around a failing of Go's escape analysis
+		// that was causing b to escape and be heap allocated.
+		// See issue 23382.
+		// TODO: once issue 7921 is fixed, this should be reverted to
+		// just "b.addr = b".
+		b.addr = (*StringBuilder)(noescape(unsafe.Pointer(b)))
+	} else if b.addr != b {
+		panic("strings: illegal use of non-zero Builder copied by value")
+	}
+}
+
+// String returns the accumulated string.
+func (b *StringBuilder) String() string {
+	return *(*string)(unsafe.Pointer(&b.buf))
+}
+
+// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
+func (b *StringBuilder) Len() int { return len(b.buf) }
+
+// Reset resets the Builder to be empty.
+func (b *StringBuilder) Reset() {
+	b.addr = nil
+	b.buf = nil
+}
+
+// grow copies the buffer to a new, larger buffer so that there are at least n
+// bytes of capacity beyond len(b.buf).
+func (b *StringBuilder) grow(n int) {
+	buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
+	copy(buf, b.buf)
+	b.buf = buf
+}
+
+// Grow grows b's capacity, if necessary, to guarantee space for
+// another n bytes. After Grow(n), at least n bytes can be written to b
+// without another allocation. If n is negative, Grow panics.
+func (b *StringBuilder) Grow(n int) {
+	b.copyCheck()
+	if n < 0 {
+		panic("strings.Builder.Grow: negative count")
+	}
+	if cap(b.buf)-len(b.buf) < n {
+		b.grow(n)
+	}
+}
+
+// Write appends the contents of p to b's buffer.
+// Write always returns len(p), nil.
+func (b *StringBuilder) Write(p []byte) (int, error) {
+	b.copyCheck()
+	b.buf = append(b.buf, p...)
+	return len(p), nil
+}
+
+// WriteByte appends the byte c to b's buffer.
+// The returned error is always nil.
+func (b *StringBuilder) WriteByte(c byte) error {
+	b.copyCheck()
+	b.buf = append(b.buf, c)
+	return nil
+}
+
+// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
+// It returns the length of r and a nil error.
+func (b *StringBuilder) WriteRune(r rune) (int, error) {
+	b.copyCheck()
+	if r < utf8.RuneSelf {
+		b.buf = append(b.buf, byte(r))
+		return 1, nil
+	}
+	l := len(b.buf)
+	if cap(b.buf)-l < utf8.UTFMax {
+		b.grow(utf8.UTFMax)
+	}
+	n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
+	b.buf = b.buf[:l+n]
+	return n, nil
+}
+
+// WriteString appends the contents of s to b's buffer.
+// It returns the length of s and a nil error.
+func (b *StringBuilder) WriteString(s string) (int, error) {
+	b.copyCheck()
+	b.buf = append(b.buf, s...)
+	return len(s), nil
+}

+ 44 - 0
string_builder_test.go

@@ -0,0 +1,44 @@
+// 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"
+)
+
+func TestStringBuilderWriteByte(t *testing.T) {
+	var b StringBuilder
+	err := b.WriteByte('c')
+	assert.NoError(t, err)
+	assert.EqualValues(t, "c", b.String())
+
+	b.Reset()
+
+	var content = "123456"
+	for _, c := range content {
+		_, err = b.WriteRune(c)
+		assert.NoError(t, err)
+	}
+	assert.EqualValues(t, 6, b.Len())
+	assert.EqualValues(t, content, b.String())
+
+	for i := 0; i < 100; i++ {
+		for _, c := range content {
+			_, err = b.WriteRune(c)
+			assert.NoError(t, err)
+		}
+	}
+
+	b.Grow(600)
+
+	for i := 0; i < 100; i++ {
+		for _, c := range content {
+			_, err = b.WriteRune(c)
+			assert.NoError(t, err)
+		}
+	}
+}

+ 213 - 0
testdata/mssql_fiddle_data.sql

@@ -0,0 +1,213 @@
+create table table1 (
+  id int primary key,
+  a  varchar(40),
+  b  varchar(40),
+  c  varchar(40)
+);
+
+create table table2 (
+  id     int primary key,
+  ref_id int,
+  d      varchar(40)
+);
+INSERT INTO table1 (id, a, b, c)
+VALUES (0, '0', '0', '0');
+INSERT INTO table1 (id, a, b, c)
+VALUES (1, '1', '1', '1');
+INSERT INTO table1 (id, a, b, c)
+VALUES (2, '2', '2', '2');
+INSERT INTO table1 (id, a, b, c)
+VALUES (3, '3', '3', '3');
+INSERT INTO table1 (id, a, b, c)
+VALUES (4, '4', '4', '4');
+INSERT INTO table1 (id, a, b, c)
+VALUES (5, '5', '5', '5');
+INSERT INTO table1 (id, a, b, c)
+VALUES (6, '6', '6', '6');
+INSERT INTO table1 (id, a, b, c)
+VALUES (7, '7', '7', '7');
+INSERT INTO table1 (id, a, b, c)
+VALUES (8, '8', '8', '8');
+INSERT INTO table1 (id, a, b, c)
+VALUES (9, '9', '9', '9');
+INSERT INTO table1 (id, a, b, c)
+VALUES (10, '10', '10', '10');
+INSERT INTO table1 (id, a, b, c)
+VALUES (11, '11', '11', '11');
+INSERT INTO table1 (id, a, b, c)
+VALUES (12, '12', '12', '12');
+INSERT INTO table1 (id, a, b, c)
+VALUES (13, '13', '13', '13');
+INSERT INTO table1 (id, a, b, c)
+VALUES (14, '14', '14', '14');
+INSERT INTO table1 (id, a, b, c)
+VALUES (15, '15', '15', '15');
+INSERT INTO table1 (id, a, b, c)
+VALUES (16, '16', '16', '16');
+INSERT INTO table1 (id, a, b, c)
+VALUES (17, '17', '17', '17');
+INSERT INTO table1 (id, a, b, c)
+VALUES (18, '18', '18', '18');
+INSERT INTO table1 (id, a, b, c)
+VALUES (19, '19', '19', '19');
+INSERT INTO table1 (id, a, b, c)
+VALUES (20, '20', '20', '20');
+INSERT INTO table1 (id, a, b, c)
+VALUES (21, '21', '21', '21');
+INSERT INTO table1 (id, a, b, c)
+VALUES (22, '22', '22', '22');
+INSERT INTO table1 (id, a, b, c)
+VALUES (23, '23', '23', '23');
+INSERT INTO table1 (id, a, b, c)
+VALUES (24, '24', '24', '24');
+INSERT INTO table1 (id, a, b, c)
+VALUES (25, '25', '25', '25');
+INSERT INTO table1 (id, a, b, c)
+VALUES (26, '26', '26', '26');
+INSERT INTO table1 (id, a, b, c)
+VALUES (27, '27', '27', '27');
+INSERT INTO table1 (id, a, b, c)
+VALUES (28, '28', '28', '28');
+INSERT INTO table1 (id, a, b, c)
+VALUES (29, '29', '29', '29');
+INSERT INTO table1 (id, a, b, c)
+VALUES (30, '30', '30', '30');
+INSERT INTO table1 (id, a, b, c)
+VALUES (31, '31', '31', '31');
+INSERT INTO table1 (id, a, b, c)
+VALUES (32, '32', '32', '32');
+INSERT INTO table1 (id, a, b, c)
+VALUES (33, '33', '33', '33');
+INSERT INTO table1 (id, a, b, c)
+VALUES (34, '34', '34', '34');
+INSERT INTO table1 (id, a, b, c)
+VALUES (35, '35', '35', '35');
+INSERT INTO table1 (id, a, b, c)
+VALUES (36, '36', '36', '36');
+INSERT INTO table1 (id, a, b, c)
+VALUES (37, '37', '37', '37');
+INSERT INTO table1 (id, a, b, c)
+VALUES (38, '38', '38', '38');
+INSERT INTO table1 (id, a, b, c)
+VALUES (39, '39', '39', '39');
+INSERT INTO table1 (id, a, b, c)
+VALUES (40, '40', '40', '40');
+INSERT INTO table1 (id, a, b, c)
+VALUES (41, '41', '41', '41');
+INSERT INTO table1 (id, a, b, c)
+VALUES (42, '42', '42', '42');
+INSERT INTO table1 (id, a, b, c)
+VALUES (43, '43', '43', '43');
+INSERT INTO table1 (id, a, b, c)
+VALUES (44, '44', '44', '44');
+INSERT INTO table1 (id, a, b, c)
+VALUES (45, '45', '45', '45');
+INSERT INTO table1 (id, a, b, c)
+VALUES (46, '46', '46', '46');
+INSERT INTO table1 (id, a, b, c)
+VALUES (47, '47', '47', '47');
+INSERT INTO table1 (id, a, b, c)
+VALUES (48, '48', '48', '48');
+INSERT INTO table1 (id, a, b, c)
+VALUES (49, '49', '49', '49');
+
+INSERT INTO table2 (id, ref_id, d)
+VALUES (0, '0', '0');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (1, '1', '1');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (2, '2', '2');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (3, '3', '3');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (4, '4', '4');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (5, '5', '5');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (6, '6', '6');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (7, '7', '7');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (8, '8', '8');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (9, '9', '9');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (10, '10', '10');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (11, '11', '11');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (12, '12', '12');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (13, '13', '13');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (14, '14', '14');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (15, '15', '15');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (16, '16', '16');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (17, '17', '17');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (18, '18', '18');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (19, '19', '19');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (20, '20', '20');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (21, '21', '21');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (22, '22', '22');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (23, '23', '23');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (24, '24', '24');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (25, '25', '25');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (26, '26', '26');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (27, '27', '27');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (28, '28', '28');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (29, '29', '29');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (30, '30', '30');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (31, '31', '31');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (32, '32', '32');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (33, '33', '33');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (34, '34', '34');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (35, '35', '35');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (36, '36', '36');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (37, '37', '37');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (38, '38', '38');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (39, '39', '39');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (40, '40', '40');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (41, '41', '41');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (42, '42', '42');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (43, '43', '43');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (44, '44', '44');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (45, '45', '45');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (46, '46', '46');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (47, '47', '47');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (48, '48', '48');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (49, '49', '49');

+ 213 - 0
testdata/mysql_fiddle_data.sql

@@ -0,0 +1,213 @@
+create table table1 (
+  id int primary key,
+  a  varchar(40),
+  b  varchar(40),
+  c  varchar(40)
+);
+
+create table table2 (
+  id     int primary key,
+  ref_id int,
+  d      varchar(40)
+);
+INSERT INTO table1 (id, a, b, c)
+VALUES (0, '0', '0', '0');
+INSERT INTO table1 (id, a, b, c)
+VALUES (1, '1', '1', '1');
+INSERT INTO table1 (id, a, b, c)
+VALUES (2, '2', '2', '2');
+INSERT INTO table1 (id, a, b, c)
+VALUES (3, '3', '3', '3');
+INSERT INTO table1 (id, a, b, c)
+VALUES (4, '4', '4', '4');
+INSERT INTO table1 (id, a, b, c)
+VALUES (5, '5', '5', '5');
+INSERT INTO table1 (id, a, b, c)
+VALUES (6, '6', '6', '6');
+INSERT INTO table1 (id, a, b, c)
+VALUES (7, '7', '7', '7');
+INSERT INTO table1 (id, a, b, c)
+VALUES (8, '8', '8', '8');
+INSERT INTO table1 (id, a, b, c)
+VALUES (9, '9', '9', '9');
+INSERT INTO table1 (id, a, b, c)
+VALUES (10, '10', '10', '10');
+INSERT INTO table1 (id, a, b, c)
+VALUES (11, '11', '11', '11');
+INSERT INTO table1 (id, a, b, c)
+VALUES (12, '12', '12', '12');
+INSERT INTO table1 (id, a, b, c)
+VALUES (13, '13', '13', '13');
+INSERT INTO table1 (id, a, b, c)
+VALUES (14, '14', '14', '14');
+INSERT INTO table1 (id, a, b, c)
+VALUES (15, '15', '15', '15');
+INSERT INTO table1 (id, a, b, c)
+VALUES (16, '16', '16', '16');
+INSERT INTO table1 (id, a, b, c)
+VALUES (17, '17', '17', '17');
+INSERT INTO table1 (id, a, b, c)
+VALUES (18, '18', '18', '18');
+INSERT INTO table1 (id, a, b, c)
+VALUES (19, '19', '19', '19');
+INSERT INTO table1 (id, a, b, c)
+VALUES (20, '20', '20', '20');
+INSERT INTO table1 (id, a, b, c)
+VALUES (21, '21', '21', '21');
+INSERT INTO table1 (id, a, b, c)
+VALUES (22, '22', '22', '22');
+INSERT INTO table1 (id, a, b, c)
+VALUES (23, '23', '23', '23');
+INSERT INTO table1 (id, a, b, c)
+VALUES (24, '24', '24', '24');
+INSERT INTO table1 (id, a, b, c)
+VALUES (25, '25', '25', '25');
+INSERT INTO table1 (id, a, b, c)
+VALUES (26, '26', '26', '26');
+INSERT INTO table1 (id, a, b, c)
+VALUES (27, '27', '27', '27');
+INSERT INTO table1 (id, a, b, c)
+VALUES (28, '28', '28', '28');
+INSERT INTO table1 (id, a, b, c)
+VALUES (29, '29', '29', '29');
+INSERT INTO table1 (id, a, b, c)
+VALUES (30, '30', '30', '30');
+INSERT INTO table1 (id, a, b, c)
+VALUES (31, '31', '31', '31');
+INSERT INTO table1 (id, a, b, c)
+VALUES (32, '32', '32', '32');
+INSERT INTO table1 (id, a, b, c)
+VALUES (33, '33', '33', '33');
+INSERT INTO table1 (id, a, b, c)
+VALUES (34, '34', '34', '34');
+INSERT INTO table1 (id, a, b, c)
+VALUES (35, '35', '35', '35');
+INSERT INTO table1 (id, a, b, c)
+VALUES (36, '36', '36', '36');
+INSERT INTO table1 (id, a, b, c)
+VALUES (37, '37', '37', '37');
+INSERT INTO table1 (id, a, b, c)
+VALUES (38, '38', '38', '38');
+INSERT INTO table1 (id, a, b, c)
+VALUES (39, '39', '39', '39');
+INSERT INTO table1 (id, a, b, c)
+VALUES (40, '40', '40', '40');
+INSERT INTO table1 (id, a, b, c)
+VALUES (41, '41', '41', '41');
+INSERT INTO table1 (id, a, b, c)
+VALUES (42, '42', '42', '42');
+INSERT INTO table1 (id, a, b, c)
+VALUES (43, '43', '43', '43');
+INSERT INTO table1 (id, a, b, c)
+VALUES (44, '44', '44', '44');
+INSERT INTO table1 (id, a, b, c)
+VALUES (45, '45', '45', '45');
+INSERT INTO table1 (id, a, b, c)
+VALUES (46, '46', '46', '46');
+INSERT INTO table1 (id, a, b, c)
+VALUES (47, '47', '47', '47');
+INSERT INTO table1 (id, a, b, c)
+VALUES (48, '48', '48', '48');
+INSERT INTO table1 (id, a, b, c)
+VALUES (49, '49', '49', '49');
+
+INSERT INTO table2 (id, ref_id, d)
+VALUES (0, '0', '0');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (1, '1', '1');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (2, '2', '2');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (3, '3', '3');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (4, '4', '4');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (5, '5', '5');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (6, '6', '6');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (7, '7', '7');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (8, '8', '8');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (9, '9', '9');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (10, '10', '10');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (11, '11', '11');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (12, '12', '12');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (13, '13', '13');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (14, '14', '14');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (15, '15', '15');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (16, '16', '16');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (17, '17', '17');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (18, '18', '18');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (19, '19', '19');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (20, '20', '20');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (21, '21', '21');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (22, '22', '22');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (23, '23', '23');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (24, '24', '24');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (25, '25', '25');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (26, '26', '26');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (27, '27', '27');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (28, '28', '28');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (29, '29', '29');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (30, '30', '30');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (31, '31', '31');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (32, '32', '32');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (33, '33', '33');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (34, '34', '34');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (35, '35', '35');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (36, '36', '36');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (37, '37', '37');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (38, '38', '38');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (39, '39', '39');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (40, '40', '40');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (41, '41', '41');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (42, '42', '42');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (43, '43', '43');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (44, '44', '44');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (45, '45', '45');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (46, '46', '46');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (47, '47', '47');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (48, '48', '48');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (49, '49', '49');

+ 214 - 0
testdata/oracle_fiddle_data.sql

@@ -0,0 +1,214 @@
+create table table1 (
+  id number(9) not null primary key,
+  a  varchar2(40),
+  b  varchar2(40),
+  c  varchar2(40)
+);
+
+create table table2 (
+  id     number(9) not null primary key,
+  ref_id number(9),
+  d      varchar2(40)
+);
+
+INSERT INTO table1 (id, a, b, c)
+VALUES (0, '0', '0', '0');
+INSERT INTO table1 (id, a, b, c)
+VALUES (1, '1', '1', '1');
+INSERT INTO table1 (id, a, b, c)
+VALUES (2, '2', '2', '2');
+INSERT INTO table1 (id, a, b, c)
+VALUES (3, '3', '3', '3');
+INSERT INTO table1 (id, a, b, c)
+VALUES (4, '4', '4', '4');
+INSERT INTO table1 (id, a, b, c)
+VALUES (5, '5', '5', '5');
+INSERT INTO table1 (id, a, b, c)
+VALUES (6, '6', '6', '6');
+INSERT INTO table1 (id, a, b, c)
+VALUES (7, '7', '7', '7');
+INSERT INTO table1 (id, a, b, c)
+VALUES (8, '8', '8', '8');
+INSERT INTO table1 (id, a, b, c)
+VALUES (9, '9', '9', '9');
+INSERT INTO table1 (id, a, b, c)
+VALUES (10, '10', '10', '10');
+INSERT INTO table1 (id, a, b, c)
+VALUES (11, '11', '11', '11');
+INSERT INTO table1 (id, a, b, c)
+VALUES (12, '12', '12', '12');
+INSERT INTO table1 (id, a, b, c)
+VALUES (13, '13', '13', '13');
+INSERT INTO table1 (id, a, b, c)
+VALUES (14, '14', '14', '14');
+INSERT INTO table1 (id, a, b, c)
+VALUES (15, '15', '15', '15');
+INSERT INTO table1 (id, a, b, c)
+VALUES (16, '16', '16', '16');
+INSERT INTO table1 (id, a, b, c)
+VALUES (17, '17', '17', '17');
+INSERT INTO table1 (id, a, b, c)
+VALUES (18, '18', '18', '18');
+INSERT INTO table1 (id, a, b, c)
+VALUES (19, '19', '19', '19');
+INSERT INTO table1 (id, a, b, c)
+VALUES (20, '20', '20', '20');
+INSERT INTO table1 (id, a, b, c)
+VALUES (21, '21', '21', '21');
+INSERT INTO table1 (id, a, b, c)
+VALUES (22, '22', '22', '22');
+INSERT INTO table1 (id, a, b, c)
+VALUES (23, '23', '23', '23');
+INSERT INTO table1 (id, a, b, c)
+VALUES (24, '24', '24', '24');
+INSERT INTO table1 (id, a, b, c)
+VALUES (25, '25', '25', '25');
+INSERT INTO table1 (id, a, b, c)
+VALUES (26, '26', '26', '26');
+INSERT INTO table1 (id, a, b, c)
+VALUES (27, '27', '27', '27');
+INSERT INTO table1 (id, a, b, c)
+VALUES (28, '28', '28', '28');
+INSERT INTO table1 (id, a, b, c)
+VALUES (29, '29', '29', '29');
+INSERT INTO table1 (id, a, b, c)
+VALUES (30, '30', '30', '30');
+INSERT INTO table1 (id, a, b, c)
+VALUES (31, '31', '31', '31');
+INSERT INTO table1 (id, a, b, c)
+VALUES (32, '32', '32', '32');
+INSERT INTO table1 (id, a, b, c)
+VALUES (33, '33', '33', '33');
+INSERT INTO table1 (id, a, b, c)
+VALUES (34, '34', '34', '34');
+INSERT INTO table1 (id, a, b, c)
+VALUES (35, '35', '35', '35');
+INSERT INTO table1 (id, a, b, c)
+VALUES (36, '36', '36', '36');
+INSERT INTO table1 (id, a, b, c)
+VALUES (37, '37', '37', '37');
+INSERT INTO table1 (id, a, b, c)
+VALUES (38, '38', '38', '38');
+INSERT INTO table1 (id, a, b, c)
+VALUES (39, '39', '39', '39');
+INSERT INTO table1 (id, a, b, c)
+VALUES (40, '40', '40', '40');
+INSERT INTO table1 (id, a, b, c)
+VALUES (41, '41', '41', '41');
+INSERT INTO table1 (id, a, b, c)
+VALUES (42, '42', '42', '42');
+INSERT INTO table1 (id, a, b, c)
+VALUES (43, '43', '43', '43');
+INSERT INTO table1 (id, a, b, c)
+VALUES (44, '44', '44', '44');
+INSERT INTO table1 (id, a, b, c)
+VALUES (45, '45', '45', '45');
+INSERT INTO table1 (id, a, b, c)
+VALUES (46, '46', '46', '46');
+INSERT INTO table1 (id, a, b, c)
+VALUES (47, '47', '47', '47');
+INSERT INTO table1 (id, a, b, c)
+VALUES (48, '48', '48', '48');
+INSERT INTO table1 (id, a, b, c)
+VALUES (49, '49', '49', '49');
+
+INSERT INTO table2 (id, ref_id, d)
+VALUES (0, '0', '0');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (1, '1', '1');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (2, '2', '2');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (3, '3', '3');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (4, '4', '4');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (5, '5', '5');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (6, '6', '6');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (7, '7', '7');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (8, '8', '8');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (9, '9', '9');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (10, '10', '10');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (11, '11', '11');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (12, '12', '12');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (13, '13', '13');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (14, '14', '14');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (15, '15', '15');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (16, '16', '16');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (17, '17', '17');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (18, '18', '18');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (19, '19', '19');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (20, '20', '20');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (21, '21', '21');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (22, '22', '22');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (23, '23', '23');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (24, '24', '24');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (25, '25', '25');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (26, '26', '26');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (27, '27', '27');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (28, '28', '28');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (29, '29', '29');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (30, '30', '30');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (31, '31', '31');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (32, '32', '32');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (33, '33', '33');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (34, '34', '34');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (35, '35', '35');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (36, '36', '36');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (37, '37', '37');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (38, '38', '38');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (39, '39', '39');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (40, '40', '40');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (41, '41', '41');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (42, '42', '42');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (43, '43', '43');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (44, '44', '44');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (45, '45', '45');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (46, '46', '46');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (47, '47', '47');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (48, '48', '48');
+INSERT INTO table2 (id, ref_id, d)
+VALUES (49, '49', '49');