diff --git a/builder/LICENSE b/builder/LICENSE new file mode 100644 index 00000000..614d5e28 --- /dev/null +++ b/builder/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. diff --git a/builder/README.md b/builder/README.md new file mode 100644 index 00000000..53e54032 --- /dev/null +++ b/builder/README.md @@ -0,0 +1,206 @@ +# SQL builder + +[![Build Status](https://drone.gitea.com/api/badges/xorm/builder/status.svg)](https://drone.gitea.com/xorm/builder) [![](http://gocover.io/_badge/xorm.io/builder)](http://gocover.io/xorm.io/builder) +[![](https://goreportcard.com/badge/xorm.io/builder)](https://goreportcard.com/report/xorm.io/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 xorm.io/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 . "xorm.io/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 . "xorm.io/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 . "xorm.io/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? [1, %c%, 2] +``` + +* `Or(conds ...Cond)`, Or can connect one or more conditions via Or + +```Go +import . "xorm.io/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 . "xorm.io/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`. \ No newline at end of file diff --git a/builder/builder.go b/builder/builder.go new file mode 100644 index 00000000..cccc8a7f --- /dev/null +++ b/builder/builder.go @@ -0,0 +1,321 @@ +// 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" +) + +type optype byte + +const ( + condType optype = iota // only conditions + selectType // select + insertType // insert + updateType // update + deleteType // delete + setOpType // set operation +) + +// all databasees +const ( + POSTGRES = "postgres" + SQLITE = "sqlite3" + MYSQL = "mysql" + MSSQL = "mssql" + ORACLE = "oracle" + + UNION = "union" + INTERSECT = "intersect" + EXCEPT = "except" +) + +type join struct { + joinType string + joinTable interface{} + joinCond Cond +} + +type setOp struct { + opType string + distinctType 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 + setOps []setOp + limitation *limit + insertCols []string + insertVals []interface{} + updates []UpdateCond + 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 +} + +// Union sets union conditions +func (b *Builder) Union(distinctType string, cond *Builder) *Builder { + return b.setOperation(UNION, distinctType, cond) +} + +// Intersect sets intersect conditions +func (b *Builder) Intersect(distinctType string, cond *Builder) *Builder { + return b.setOperation(INTERSECT, distinctType, cond) +} + +// Except sets except conditions +func (b *Builder) Except(distinctType string, cond *Builder) *Builder { + return b.setOperation(EXCEPT, distinctType, cond) +} + +func (b *Builder) setOperation(opType, distinctType string, cond *Builder) *Builder { + + var builder *Builder + if b.optype != setOpType { + builder = &Builder{cond: NewCond()} + builder.optype = setOpType + builder.dialect = b.dialect + builder.selects = b.selects + + currentSetOps := b.setOps + // erase sub setOps (actually append to new Builder.unions) + b.setOps = nil + + for e := range currentSetOps { + currentSetOps[e].builder.dialect = b.dialect + } + + builder.setOps = append(append(builder.setOps, setOp{opType, "", b}), currentSetOps...) + } else { + builder = b + } + + if cond != nil { + if cond.dialect == "" && builder.dialect != "" { + cond.dialect = builder.dialect + } + + builder.setOps = append(builder.setOps, setOp{opType, distinctType, cond}) + } + + 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 +} + +// 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 +} + +// Update sets update SQL +func (b *Builder) Update(updates ...Cond) *Builder { + b.updates = make([]UpdateCond, 0, len(updates)) + for _, update := range updates { + if u, ok := update.(UpdateCond); ok && u.IsValid() { + b.updates = append(b.updates, u) + } + } + 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 setOpType: + return b.setOpWriteTo(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.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 generated a bound SQL string +func (b *Builder) ToBoundSQL() (string, error) { + w := NewWriter() + if err := b.WriteTo(w); err != nil { + return "", err + } + + return ConvertToBoundSQL(w.String(), w.args) +} diff --git a/builder/builder_b_test.go b/builder/builder_b_test.go new file mode 100644 index 00000000..6bab9a6f --- /dev/null +++ b/builder/builder_b_test.go @@ -0,0 +1,298 @@ +// 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) + } + 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))] +} diff --git a/builder/builder_delete.go b/builder/builder_delete.go new file mode 100644 index 00000000..317cc3ff --- /dev/null +++ b/builder/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) +} diff --git a/builder/builder_delete_test.go b/builder/builder_delete_test.go new file mode 100644 index 00000000..7b404989 --- /dev/null +++ b/builder/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) +} diff --git a/builder/builder_insert.go b/builder/builder_insert.go new file mode 100644 index 00000000..8cef5c56 --- /dev/null +++ b/builder/builder_insert.go @@ -0,0 +1,149 @@ +// 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" + "sort" +) + +// 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 if value == nil { + fmt.Fprintf(valBuffer, `null`) + } 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 +} + +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 +} diff --git a/builder/builder_insert_test.go b/builder/builder_insert_test.go new file mode 100644 index 00000000..bb7305b2 --- /dev/null +++ b/builder/builder_insert_test.go @@ -0,0 +1,54 @@ +// 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) + + sql, err = Insert(Eq{`a`: nil}).Into(`table1`).ToBoundSQL() + assert.NoError(t, err) + assert.EqualValues(t, `INSERT INTO table1 (a) Values (null)`, sql) + + sql, args, err := Insert(Eq{`a`: nil, `b`: `str`}).Into(`table1`).ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, `INSERT INTO table1 (a,b) Values (null,?)`, sql) + assert.EqualValues(t, []interface{}{`str`}, args) +} + +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) +} diff --git a/builder/builder_join.go b/builder/builder_join.go new file mode 100644 index 00000000..a6604c5f --- /dev/null +++ b/builder/builder_join.go @@ -0,0 +1,42 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +// InnerJoin sets inner join +func (b *Builder) InnerJoin(joinTable, joinCond interface{}) *Builder { + return b.Join("INNER", joinTable, joinCond) +} + +// LeftJoin sets left join SQL +func (b *Builder) LeftJoin(joinTable, joinCond interface{}) *Builder { + return b.Join("LEFT", joinTable, joinCond) +} + +// RightJoin sets right join SQL +func (b *Builder) RightJoin(joinTable, joinCond interface{}) *Builder { + return b.Join("RIGHT", joinTable, joinCond) +} + +// CrossJoin sets cross join SQL +func (b *Builder) CrossJoin(joinTable, joinCond interface{}) *Builder { + return b.Join("CROSS", joinTable, joinCond) +} + +// FullJoin sets full join SQL +func (b *Builder) FullJoin(joinTable, joinCond interface{}) *Builder { + return b.Join("FULL", joinTable, joinCond) +} + +// Join sets join table and conditions +func (b *Builder) Join(joinType string, joinTable, 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 +} diff --git a/builder/builder_join_test.go b/builder/builder_join_test.go new file mode 100644 index 00000000..b61c484a --- /dev/null +++ b/builder/builder_join_test.go @@ -0,0 +1,50 @@ +// 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 TestJoin(t *testing.T) { + 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?) ON table1.id=? AND table2.id?) ON table2.id = table3.tid WHERE a=?", + sql) + assert.EqualValues(t, []interface{}{1, 1, 3, "2", 1}, args) +} diff --git a/builder/builder_limit.go b/builder/builder_limit.go new file mode 100644 index 00000000..82e11793 --- /dev/null +++ b/builder/builder_limit.go @@ -0,0 +1,103 @@ +// 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 + defer func() { + b.limitation = limit + }() + 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 == setOpType { + 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 == setOpType { + 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 == setOpType { + 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 +} diff --git a/builder/builder_limit_test.go b/builder/builder_limit_test.go new file mode 100644 index 00000000..2702e801 --- /dev/null +++ b/builder/builder_limit_test.go @@ -0,0 +1,129 @@ +// 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 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)) +} diff --git a/builder/builder_select.go b/builder/builder_select.go new file mode 100644 index 00000000..087a71d8 --- /dev/null +++ b/builder/builder_select.go @@ -0,0 +1,158 @@ +// 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, setOpType: + 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 { + b, ok := v.joinTable.(*Builder) + if ok { + if _, err := fmt.Fprintf(w, " %s JOIN (", v.joinType); err != nil { + return err + } + if err := b.WriteTo(w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, ") ON "); err != nil { + return err + } + } else { + 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 +} diff --git a/builder/builder_select_test.go b/builder/builder_select_test.go new file mode 100644 index 00000000..fc100e6e --- /dev/null +++ b/builder/builder_select_test.go @@ -0,0 +1,115 @@ +// 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) + + _, _, 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) +} diff --git a/builder/builder_set_operations.go b/builder/builder_set_operations.go new file mode 100644 index 00000000..b2b4a3da --- /dev/null +++ b/builder/builder_set_operations.go @@ -0,0 +1,51 @@ +// 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) setOpWriteTo(w Writer) error { + if b.limitation != nil || b.cond.IsValid() || + b.orderBy != "" || b.having != "" || b.groupBy != "" { + return ErrNotUnexpectedUnionConditions + } + + for idx, o := range b.setOps { + current := o.builder + if current.optype != selectType { + return ErrUnsupportedUnionMembers + } + + if len(b.setOps) == 1 { + if err := current.selectWriteTo(w); err != nil { + return err + } + } else { + if b.dialect != "" && b.dialect != current.dialect { + return ErrInconsistentDialect + } + + if idx != 0 { + if o.distinctType == "" { + fmt.Fprint(w, fmt.Sprintf(" %s ", strings.ToUpper(o.opType))) + } else { + fmt.Fprint(w, fmt.Sprintf(" %s %s ", strings.ToUpper(o.opType), strings.ToUpper(o.distinctType))) + } + } + fmt.Fprint(w, "(") + + if err := current.selectWriteTo(w); err != nil { + return err + } + + fmt.Fprint(w, ")") + } + } + + return nil +} diff --git a/builder/builder_set_operations_test.go b/builder/builder_set_operations_test.go new file mode 100644 index 00000000..9ebfb6f3 --- /dev/null +++ b/builder/builder_set_operations_test.go @@ -0,0 +1,278 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +import ( + "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 + _, _, 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 + _, _, 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 + _, _, 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) +} + +func TestBuilder_Intersect(t *testing.T) { + sql, args, err := Select("*").From("t1").Where(Eq{"status": "1"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Intersect("distinct", Select("*").From("t2").Where(Eq{"status": "3"})). + Intersect("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) INTERSECT ALL (SELECT * FROM t2 WHERE status=?) INTERSECT DISTINCT (SELECT * FROM t2 WHERE status=?) INTERSECT (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"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + Intersect("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) INTERSECT ALL (SELECT * FROM t2 WHERE status=? LIMIT 10) INTERSECT (SELECT * FROM t2 WHERE status=?)", sql) + assert.EqualValues(t, []interface{}{"1", "2", "3"}, args) + + // will raise error + _, _, err = MySQL().Select("*").From("t1").Where(Eq{"status": "1"}). + Intersect("all", Oracle().Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + ToSQL() + assert.Error(t, err) + assert.EqualValues(t, ErrInconsistentDialect, err) + + // will raise error + _, _, err = Select("*").From("table1").Where(Eq{"a": "1"}). + Intersect("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 + _, _, err = Delete(Eq{"a": 1}).From("t1"). + Intersect("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"}). + Intersect("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"}). + Intersect("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"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Insert(Eq{"status": "1"}).Into("t2").ToSQL() + assert.NoError(t, err) + fmt.Println(sql, args) +} + +func TestBuilder_Except(t *testing.T) { + sql, args, err := Select("*").From("t1").Where(Eq{"status": "1"}). + Except("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Except("distinct", Select("*").From("t2").Where(Eq{"status": "3"})). + Except("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) EXCEPT ALL (SELECT * FROM t2 WHERE status=?) EXCEPT DISTINCT (SELECT * FROM t2 WHERE status=?) EXCEPT (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"}). + Except("all", Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + Except("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) EXCEPT ALL (SELECT * FROM t2 WHERE status=? LIMIT 10) EXCEPT (SELECT * FROM t2 WHERE status=?)", sql) + assert.EqualValues(t, []interface{}{"1", "2", "3"}, args) + + // will raise error + _, _, err = MySQL().Select("*").From("t1").Where(Eq{"status": "1"}). + Except("all", Oracle().Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + ToSQL() + assert.Error(t, err) + assert.EqualValues(t, ErrInconsistentDialect, err) + + // will raise error + _, _, err = Select("*").From("table1").Where(Eq{"a": "1"}). + Except("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 + _, _, err = Delete(Eq{"a": 1}).From("t1"). + Except("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"}). + Except("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"}). + Except("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"}). + Except("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Insert(Eq{"status": "1"}).Into("t2").ToSQL() + assert.NoError(t, err) + fmt.Println(sql, args) +} + +func TestBuilder_SetOperations(t *testing.T) { + sql, args, err := Select("*").From("t1").Where(Eq{"status": "1"}). + Union("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Intersect("distinct", Select("*").From("t2").Where(Eq{"status": "3"})). + Except("", 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=?) INTERSECT DISTINCT (SELECT * FROM t2 WHERE status=?) EXCEPT (SELECT * FROM t2 WHERE status=?)", sql) + assert.EqualValues(t, []interface{}{"1", "2", "3", "3"}, args) + + sql, args, err = Select("*").From("t1").Where(Eq{"status": "1"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Union("distinct", Select("*").From("t2").Where(Eq{"status": "3"})). + Except("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) INTERSECT ALL (SELECT * FROM t2 WHERE status=?) UNION DISTINCT (SELECT * FROM t2 WHERE status=?) EXCEPT (SELECT * FROM t2 WHERE status=?)", sql) + assert.EqualValues(t, []interface{}{"1", "2", "3", "3"}, args) + + sql, args, err = Select("*").From("t1").Where(Eq{"status": "1"}). + Except("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Intersect("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=?) EXCEPT ALL (SELECT * FROM t2 WHERE status=?) INTERSECT 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"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + Intersect("", Select("*").From("t2").Where(Eq{"status": "3"})). + ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "(SELECT * FROM t1 WHERE status=?) INTERSECT ALL (SELECT * FROM t2 WHERE status=? LIMIT 10) INTERSECT (SELECT * FROM t2 WHERE status=?)", sql) + assert.EqualValues(t, []interface{}{"1", "2", "3"}, args) + + // will raise error + _, _, err = MySQL().Select("*").From("t1").Where(Eq{"status": "1"}). + Intersect("all", Oracle().Select("*").From("t2").Where(Eq{"status": "2"}).Limit(10)). + ToSQL() + assert.Error(t, err) + assert.EqualValues(t, ErrInconsistentDialect, err) + + // will raise error + _, _, err = Select("*").From("table1").Where(Eq{"a": "1"}). + Intersect("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 + _, _, err = Delete(Eq{"a": 1}).From("t1"). + Intersect("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"}). + Intersect("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"}). + Intersect("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"}). + Intersect("all", Select("*").From("t2").Where(Eq{"status": "2"})). + Insert(Eq{"status": "1"}).Into("t2").ToSQL() + assert.NoError(t, err) + fmt.Println(sql, args) +} diff --git a/builder/builder_test.go b/builder/builder_test.go new file mode 100644 index 00000000..79c06364 --- /dev/null +++ b/builder/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}, + }, + { + 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) +} diff --git a/builder/builder_update.go b/builder/builder_update.go new file mode 100644 index 00000000..5fffbe34 --- /dev/null +++ b/builder/builder_update.go @@ -0,0 +1,57 @@ +// 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" +) + +// UpdateCond defines an interface that cond could be used with update +type UpdateCond interface { + IsValid() bool + OpWriteTo(op string, w Writer) error +} + +// Update creates an update Builder +func Update(updates ...Cond) *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 !b.cond.IsValid() { + return nil + } + + if _, err := fmt.Fprint(w, " WHERE "); err != nil { + return err + } + + return b.cond.WriteTo(w) +} diff --git a/builder/builder_update_test.go b/builder/builder_update_test.go new file mode 100644 index 00000000..9f3fc2e0 --- /dev/null +++ b/builder/builder_update_test.go @@ -0,0 +1,62 @@ +// 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) + + sql, args, err = Update(Eq{"a": 1}, Expr("c = c+1")).From("table1").Where(Eq{"b": 2}).ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "UPDATE table1 SET a=?,c = c+1 WHERE b=?", sql) + assert.EqualValues(t, []interface{}{1, 2}, args) + + sql, args, err = Update(Eq{"a": 2}).From("table1").ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "UPDATE table1 SET a=?", sql) + assert.EqualValues(t, []interface{}{2}, args) +} diff --git a/builder/cond.go b/builder/cond.go new file mode 100644 index 00000000..149f5d8c --- /dev/null +++ b/builder/cond.go @@ -0,0 +1,38 @@ +// 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 + +// 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 +} diff --git a/builder/cond_and.go b/builder/cond_and.go new file mode 100644 index 00000000..e30bd186 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_between.go b/builder/cond_between.go new file mode 100644 index 00000000..10e0b831 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_compare.go b/builder/cond_compare.go new file mode 100644 index 00000000..1c293719 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_eq.go b/builder/cond_eq.go new file mode 100644 index 00000000..9976d180 --- /dev/null +++ b/builder/cond_eq.go @@ -0,0 +1,117 @@ +// 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{} + +// OpWriteTo writes conditions with special operator +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))) + case nil: + if _, err := fmt.Fprintf(w, "%s=null", k); err != nil { + return err + } + 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://gitea.com/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 +} diff --git a/builder/cond_expr.go b/builder/cond_expr.go new file mode 100644 index 00000000..8288aa04 --- /dev/null +++ b/builder/cond_expr.go @@ -0,0 +1,43 @@ +// 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) OpWriteTo(op string, w Writer) error { + return expr.WriteTo(w) +} + +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 +} diff --git a/builder/cond_if.go b/builder/cond_if.go new file mode 100644 index 00000000..af9eb321 --- /dev/null +++ b/builder/cond_if.go @@ -0,0 +1,49 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +type condIf struct { + condition bool + condTrue Cond + condFalse Cond +} + +var _ Cond = condIf{} + +// If returns Cond via condition +func If(condition bool, condTrue Cond, condFalse ...Cond) Cond { + var c = condIf{ + condition: condition, + condTrue: condTrue, + } + if len(condFalse) > 0 { + c.condFalse = condFalse[0] + } + return c +} + +func (condIf condIf) WriteTo(w Writer) error { + if condIf.condition { + return condIf.condTrue.WriteTo(w) + } else if condIf.condFalse != nil { + return condIf.condFalse.WriteTo(w) + } + return nil +} + +func (condIf condIf) And(conds ...Cond) Cond { + return And(condIf, And(conds...)) +} + +func (condIf condIf) Or(conds ...Cond) Cond { + return Or(condIf, Or(conds...)) +} + +func (condIf condIf) IsValid() bool { + if condIf.condition { + return condIf.condTrue != nil + } + return condIf.condFalse != nil +} diff --git a/builder/cond_if_test.go b/builder/cond_if_test.go new file mode 100644 index 00000000..0a268c41 --- /dev/null +++ b/builder/cond_if_test.go @@ -0,0 +1,38 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCond_If(t *testing.T) { + cond1 := If(1 > 0, Eq{"a": 1}, Eq{"b": 1}) + sql, err := ToBoundSQL(cond1) + assert.NoError(t, err) + assert.EqualValues(t, "a=1", sql) + + cond2 := If(1 < 0, Eq{"a": 1}, Eq{"b": 1}) + sql, err = ToBoundSQL(cond2) + assert.NoError(t, err) + assert.EqualValues(t, "b=1", sql) + + cond3 := If(1 > 0, cond2, Eq{"c": 1}) + sql, err = ToBoundSQL(cond3) + assert.NoError(t, err) + assert.EqualValues(t, "b=1", sql) + + cond4 := If(2 < 0, Eq{"d": "a"}) + sql, err = ToBoundSQL(cond4) + assert.NoError(t, err) + assert.EqualValues(t, "", sql) + + cond5 := And(cond1, cond2, cond3, cond4) + sql, err = ToBoundSQL(cond5) + assert.NoError(t, err) + assert.EqualValues(t, "a=1 AND b=1 AND b=1", sql) +} diff --git a/builder/cond_in.go b/builder/cond_in.go new file mode 100644 index 00000000..f6366d35 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_like.go b/builder/cond_like.go new file mode 100644 index 00000000..e34202f8 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_neq.go b/builder/cond_neq.go new file mode 100644 index 00000000..687c59fc --- /dev/null +++ b/builder/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://gitea.com/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 +} diff --git a/builder/cond_not.go b/builder/cond_not.go new file mode 100644 index 00000000..667dfe72 --- /dev/null +++ b/builder/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() +} diff --git a/builder/cond_notin.go b/builder/cond_notin.go new file mode 100644 index 00000000..dc3ac49a --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_null.go b/builder/cond_null.go new file mode 100644 index 00000000..bf2aaf85 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_or.go b/builder/cond_or.go new file mode 100644 index 00000000..52442653 --- /dev/null +++ b/builder/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 +} diff --git a/builder/cond_test.go b/builder/cond_test.go new file mode 100644 index 00000000..51f37e71 --- /dev/null +++ b/builder/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) { + +} diff --git a/builder/doc.go b/builder/doc.go new file mode 100644 index 00000000..6e7dd452 --- /dev/null +++ b/builder/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 xorm.io/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 . "xorm.io/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 . "xorm.io/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 . "xorm.io/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? [1, %c%, 2] + +9. Or(conds ...Cond), Or can connect one or more conditions via Or + + import . "xorm.io/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 . "xorm.io/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 diff --git a/builder/error.go b/builder/error.go new file mode 100644 index 00000000..b0ded29f --- /dev/null +++ b/builder/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") + // ErrNoColumnToUpdate no column to update + ErrNoColumnToUpdate = errors.New("No column(s) to update") + // ErrNoColumnToInsert no column to insert + 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") +) diff --git a/builder/sql.go b/builder/sql.go new file mode 100644 index 00000000..60f22621 --- /dev/null +++ b/builder/sql.go @@ -0,0 +1,168 @@ +// Copyright 2018 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +import ( + sql2 "database/sql" + "fmt" + "reflect" + "strings" + "time" +) + +func condToSQL(cond Cond) (string, []interface{}, error) { + if cond == nil || !cond.IsValid() { + return "", nil, nil + } + + w := NewWriter() + if err := cond.WriteTo(w); err != nil { + return "", nil, err + } + return w.String(), w.args, nil +} + +func condToBoundSQL(cond Cond) (string, error) { + if cond == nil || !cond.IsValid() { + return "", nil + } + + w := NewWriter() + if err := cond.WriteTo(w); err != nil { + return "", err + } + return ConvertToBoundSQL(w.String(), w.args) +} + +// ToSQL convert a builder or conditions to SQL and args +func ToSQL(cond interface{}) (string, []interface{}, error) { + switch cond.(type) { + case Cond: + return condToSQL(cond.(Cond)) + case *Builder: + return cond.(*Builder).ToSQL() + } + return "", nil, ErrNotSupportType +} + +// ToBoundSQL convert a builder or conditions to parameters bound SQL +func ToBoundSQL(cond interface{}) (string, error) { + switch cond.(type) { + case Cond: + return condToBoundSQL(cond.(Cond)) + case *Builder: + return cond.(*Builder).ToBoundSQL() + } + return "", ErrNotSupportType +} + +func noSQLQuoteNeeded(a interface{}) bool { + if a == nil { + return false + } + switch a.(type) { + case int, int8, int16, int32, int64: + return true + case uint, uint8, uint16, uint32, uint64: + return true + case float32, float64: + return true + case bool: + return true + case string: + return false + case time.Time, *time.Time: + return false + } + + t := reflect.TypeOf(a) + + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.Bool: + return true + case reflect.String: + return false + } + + return false +} + +// ConvertToBoundSQL will convert SQL and args to a bound SQL +func ConvertToBoundSQL(sql string, args []interface{}) (string, error) { + buf := strings.Builder{} + var i, j, start int + for ; i < len(sql); i++ { + if sql[i] == '?' { + _, err := buf.WriteString(sql[start:i]) + if err != nil { + return "", err + } + start = i + 1 + + if len(args) == j { + return "", ErrNeedMoreArguments + } + + arg := args[j] + if namedArg, ok := arg.(sql2.NamedArg); ok { + arg = namedArg.Value + } + + if noSQLQuoteNeeded(arg) { + _, err = fmt.Fprint(&buf, arg) + } else { + // replace ' -> '' (standard replacement) to avoid critical SQL injection, + // NOTICE: may allow some injection like % (or _) in LIKE query + _, err = fmt.Fprintf(&buf, "'%v'", strings.Replace(fmt.Sprintf("%v", arg), "'", + "''", -1)) + } + if err != nil { + return "", err + } + j = j + 1 + } + } + _, err := buf.WriteString(sql[start:]) + if err != nil { + return "", err + } + return buf.String(), nil +} + +// ConvertPlaceholder replaces the place holder ? to $1, $2 ... or :1, :2 ... according prefix +func ConvertPlaceholder(sql, prefix string) (string, error) { + buf := strings.Builder{} + var i, j, start int + var ready = true + for ; i < len(sql); i++ { + if sql[i] == '\'' && i > 0 && sql[i-1] != '\\' { + ready = !ready + } + if ready && 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 +} diff --git a/builder/sql_test.go b/builder/sql_test.go new file mode 100644 index 00000000..38f6378d --- /dev/null +++ b/builder/sql_test.go @@ -0,0 +1,257 @@ +// 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" + + "gitea.com/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 TestNoSQLQuoteNeeded(t *testing.T) { + assert.False(t, noSQLQuoteNeeded(nil)) +} + +func TestPlaceholderConverter(t *testing.T) { + var convertCases = []struct { + before, after string + mark string + }{ + { + before: placeholderConverterSQL, + after: placeholderConvertedSQL, + mark: "$", + }, + { + before: "SELECT a, b, 'a?b' FROM table_a WHERE id=?", + after: "SELECT a, b, 'a?b' FROM table_a WHERE id=:1", + mark: ":", + }, + { + before: "SELECT a, b, 'a\\'?b' FROM table_a WHERE id=?", + after: "SELECT a, b, 'a\\'?b' FROM table_a WHERE id=$1", + mark: "$", + }, + { + before: "SELECT a, b, 'a\\'b' FROM table_a WHERE id=?", + after: "SELECT a, b, 'a\\'b' FROM table_a WHERE id=$1", + mark: "$", + }, + } + + for _, kase := range convertCases { + t.Run(kase.before, func(t *testing.T) { + newSQL, err := ConvertPlaceholder(kase.before, kase.mark) + assert.NoError(t, err) + assert.EqualValues(t, kase.after, 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) +} + +func TestToSQLInjectionHarmlessDisposal(t *testing.T) { + sql, err := MySQL().Select("*").From("table1").Where(Cond(Eq{"name": "cat';truncate table table1;"})).ToBoundSQL() + assert.NoError(t, err) + assert.EqualValues(t, "SELECT * FROM table1 WHERE name='cat'';truncate table table1;'", sql) + + sql, err = MySQL().Update(Eq{`a`: 1, `b`: nil}).From(`table1`).ToBoundSQL() + assert.NoError(t, err) + assert.EqualValues(t, "UPDATE table1 SET a=1,b=null", sql) + + sql, args, err := MySQL().Update(Eq{`a`: 1, `b`: nil}).From(`table1`).ToSQL() + assert.NoError(t, err) + assert.EqualValues(t, "UPDATE table1 SET a=?,b=null", sql) + assert.EqualValues(t, []interface{}{1}, args) +} diff --git a/builder/testdata/mssql_fiddle_data.sql b/builder/testdata/mssql_fiddle_data.sql new file mode 100644 index 00000000..d16f9137 --- /dev/null +++ b/builder/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'); \ No newline at end of file diff --git a/builder/testdata/mysql_fiddle_data.sql b/builder/testdata/mysql_fiddle_data.sql new file mode 100644 index 00000000..d16f9137 --- /dev/null +++ b/builder/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'); \ No newline at end of file diff --git a/builder/testdata/oracle_fiddle_data.sql b/builder/testdata/oracle_fiddle_data.sql new file mode 100644 index 00000000..0cf3ec48 --- /dev/null +++ b/builder/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'); \ No newline at end of file diff --git a/builder/writer.go b/builder/writer.go new file mode 100644 index 00000000..fb4fae5c --- /dev/null +++ b/builder/writer.go @@ -0,0 +1,42 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package builder + +import ( + "io" + "strings" +) + +// 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 { + *strings.Builder + args []interface{} +} + +// NewWriter creates a new string writer +func NewWriter() *BytesWriter { + w := &BytesWriter{ + Builder: &strings.Builder{}, + } + return w +} + +// Append appends args to Writer +func (w *BytesWriter) Append(args ...interface{}) { + w.args = append(w.args, args...) +} + +// Args returns args +func (w *BytesWriter) Args() []interface{} { + return w.args +} diff --git a/caches/cache.go b/caches/cache.go index 7b80eb88..84b36633 100644 --- a/caches/cache.go +++ b/caches/cache.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/schemas" ) const ( diff --git a/caches/lru_test.go b/caches/lru_test.go index 771b924c..f6583d8d 100644 --- a/caches/lru_test.go +++ b/caches/lru_test.go @@ -7,8 +7,8 @@ package caches import ( "testing" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" - "xorm.io/xorm/schemas" ) func TestLRUCache(t *testing.T) { diff --git a/core/db.go b/core/db.go index 50c64c6f..1fe7c9e8 100644 --- a/core/db.go +++ b/core/db.go @@ -13,9 +13,9 @@ import ( "regexp" "sync" - "xorm.io/xorm/contexts" - "xorm.io/xorm/log" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/names" ) var ( diff --git a/core/db_test.go b/core/db_test.go index 777ab0ad..4ee5945d 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/names" _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" diff --git a/core/stmt.go b/core/stmt.go index d46ac9c6..45fe72c6 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -10,7 +10,7 @@ import ( "errors" "reflect" - "xorm.io/xorm/contexts" + "gitea.com/laixyz/xorm/contexts" ) // Stmt reprents a stmt objects diff --git a/core/tx.go b/core/tx.go index a85a6874..fde6b24a 100644 --- a/core/tx.go +++ b/core/tx.go @@ -8,7 +8,7 @@ import ( "context" "database/sql" - "xorm.io/xorm/contexts" + "gitea.com/laixyz/xorm/contexts" ) var ( diff --git a/dialects/dialect.go b/dialects/dialect.go index dc96f73a..566c0111 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) // URI represents an uri to visit database diff --git a/dialects/mssql.go b/dialects/mssql.go index 8e76e538..3de1f002 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/dialects/mysql.go b/dialects/mysql.go index fad8c325..ee252711 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -14,8 +14,8 @@ import ( "strings" "time" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/dialects/oracle.go b/dialects/oracle.go index 91eed251..33d8bc35 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/dialects/postgres.go b/dialects/postgres.go index a2c0de74..b5fb1caa 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) // from http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 73f98beb..7a65a928 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -12,8 +12,8 @@ import ( "regexp" "strings" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/dialects/table_name.go b/dialects/table_name.go index e190cd4b..afd7051d 100644 --- a/dialects/table_name.go +++ b/dialects/table_name.go @@ -9,8 +9,8 @@ import ( "reflect" "strings" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/names" ) // TableNameWithSchema will add schema prefix on table name if possible diff --git a/dialects/table_name_test.go b/dialects/table_name_test.go index 66edc2b4..834fa64c 100644 --- a/dialects/table_name_test.go +++ b/dialects/table_name_test.go @@ -7,7 +7,7 @@ package dialects import ( "testing" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/names" "github.com/stretchr/testify/assert" ) diff --git a/dialects/time.go b/dialects/time.go index b0394745..4f0dea73 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -7,7 +7,7 @@ package dialects import ( "time" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/schemas" ) // FormatTime format time as column type diff --git a/engine.go b/engine.go index 6c894e74..8c849e2d 100644 --- a/engine.go +++ b/engine.go @@ -17,15 +17,15 @@ import ( "strings" "time" - "xorm.io/xorm/caches" - "xorm.io/xorm/contexts" - "xorm.io/xorm/core" - "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/log" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" - "xorm.io/xorm/tags" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" + "gitea.com/laixyz/xorm/tags" ) // Engine is the major struct of xorm, it means a database manager. diff --git a/engine_group.go b/engine_group.go index cdd9dd44..71f17402 100644 --- a/engine_group.go +++ b/engine_group.go @@ -8,11 +8,11 @@ import ( "context" "time" - "xorm.io/xorm/caches" - "xorm.io/xorm/contexts" - "xorm.io/xorm/dialects" - "xorm.io/xorm/log" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/names" ) // EngineGroup defines an engine group diff --git a/go.mod b/go.mod index e0d22a24..439030ab 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ -module xorm.io/xorm +module gitea.com/laixyz/xorm -go 1.11 +go 1.15 require ( - github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc + gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a + github.com/denisenkom/go-mssqldb v0.9.0 github.com/go-sql-driver/mysql v1.5.0 - github.com/lib/pq v1.7.0 - github.com/mattn/go-sqlite3 v1.14.0 - github.com/stretchr/testify v1.4.0 + github.com/lib/pq v1.9.0 + github.com/mattn/go-sqlite3 v1.14.6 + github.com/stretchr/testify v1.6.1 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - xorm.io/builder v0.3.7 ) diff --git a/go.sum b/go.sum index 844dd094..d555a8b3 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,9 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= -github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -18,10 +16,10 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= -github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -30,31 +28,21 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -63,8 +51,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= -xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integrations/cache_test.go b/integrations/cache_test.go index 44e817b1..6e83ed6d 100644 --- a/integrations/cache_test.go +++ b/integrations/cache_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "xorm.io/xorm/caches" + "gitea.com/laixyz/xorm/caches" "github.com/stretchr/testify/assert" ) diff --git a/integrations/engine_group_test.go b/integrations/engine_group_test.go index 635f73a6..65b4aa97 100644 --- a/integrations/engine_group_test.go +++ b/integrations/engine_group_test.go @@ -7,9 +7,9 @@ package integrations import ( "testing" - "xorm.io/xorm" - "xorm.io/xorm/log" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" ) diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 0e5d3424..f193a2d0 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "xorm.io/xorm" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm" + "gitea.com/laixyz/xorm/schemas" _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" diff --git a/integrations/processors_test.go b/integrations/processors_test.go index e349988d..b3fda7b4 100644 --- a/integrations/processors_test.go +++ b/integrations/processors_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "xorm.io/xorm" + "gitea.com/laixyz/xorm" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_cols_test.go b/integrations/session_cols_test.go index b74c6f8a..906ad08c 100644 --- a/integrations/session_cols_test.go +++ b/integrations/session_cols_test.go @@ -7,9 +7,9 @@ package integrations import ( "testing" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" - "xorm.io/builder" - "xorm.io/xorm/schemas" ) func TestSetExpr(t *testing.T) { diff --git a/integrations/session_cond_test.go b/integrations/session_cond_test.go index a0a91cad..9431d2a9 100644 --- a/integrations/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -9,8 +9,8 @@ import ( "fmt" "testing" + "gitea.com/laixyz/xorm/builder" "github.com/stretchr/testify/assert" - "xorm.io/builder" ) func TestBuilder(t *testing.T) { diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index f3565963..449fd9f4 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "xorm.io/xorm/caches" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index c3e99183..12b93250 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/names" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/names" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index e4d9f82e..f0808c22 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "xorm.io/xorm/contexts" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 47789b8a..a4fdc5cc 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "xorm.io/xorm" + "gitea.com/laixyz/xorm" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_pk_test.go b/integrations/session_pk_test.go index d5f23491..57bb29e0 100644 --- a/integrations/session_pk_test.go +++ b/integrations/session_pk_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" - "xorm.io/xorm/schemas" ) type IntId struct { diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index 30f2e6ab..1a8ca82f 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" ) diff --git a/integrations/session_stats_test.go b/integrations/session_stats_test.go index 47a64076..4e5b4aae 100644 --- a/integrations/session_stats_test.go +++ b/integrations/session_stats_test.go @@ -9,8 +9,8 @@ import ( "strconv" "testing" + "gitea.com/laixyz/xorm/builder" "github.com/stretchr/testify/assert" - "xorm.io/builder" ) func isFloatEq(i, j float64, precision int) bool { diff --git a/integrations/session_tx_test.go b/integrations/session_tx_test.go index 4cff5610..f7363166 100644 --- a/integrations/session_tx_test.go +++ b/integrations/session_tx_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/names" "github.com/stretchr/testify/assert" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/names" ) func TestTransaction(t *testing.T) { diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 07c722bd..384b4f2e 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -10,11 +10,11 @@ import ( "testing" "time" + "gitea.com/laixyz/xorm" + "gitea.com/laixyz/xorm/internal/statements" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/names" "github.com/stretchr/testify/assert" - "xorm.io/xorm" - "xorm.io/xorm/internal/statements" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/names" ) func TestUpdateMap(t *testing.T) { diff --git a/integrations/tags_test.go b/integrations/tags_test.go index f787fffe..b675969c 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" ) type tempUser struct { diff --git a/integrations/tests.go b/integrations/tests.go index 31fa99bf..2116e227 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -12,12 +12,12 @@ import ( "strings" "testing" - "xorm.io/xorm" - "xorm.io/xorm/caches" - "xorm.io/xorm/dialects" - "xorm.io/xorm/log" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/integrations/time_test.go b/integrations/time_test.go index 6d8d812c..ce43c04c 100644 --- a/integrations/time_test.go +++ b/integrations/time_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "xorm.io/xorm/internal/utils" + "gitea.com/laixyz/xorm/internal/utils" "github.com/stretchr/testify/assert" ) diff --git a/integrations/types_test.go b/integrations/types_test.go index 112308f3..a99aac8b 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -9,10 +9,10 @@ import ( "fmt" "testing" - "xorm.io/xorm" - "xorm.io/xorm/convert" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/schemas" "github.com/stretchr/testify/assert" ) diff --git a/interface.go b/interface.go index 0fe9cbe1..6402a208 100644 --- a/interface.go +++ b/interface.go @@ -10,12 +10,12 @@ import ( "reflect" "time" - "xorm.io/xorm/caches" - "xorm.io/xorm/contexts" - "xorm.io/xorm/dialects" - "xorm.io/xorm/log" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" ) // Interface defines the interface which Engine, EngineGroup and Session will implementate. diff --git a/internal/statements/cache.go b/internal/statements/cache.go index cb33df08..cda303e3 100644 --- a/internal/statements/cache.go +++ b/internal/statements/cache.go @@ -8,8 +8,8 @@ import ( "fmt" "strings" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) func (statement *Statement) ConvertIDSQL(sqlStr string) string { diff --git a/internal/statements/column_map.go b/internal/statements/column_map.go index bb764b4e..2967ebd1 100644 --- a/internal/statements/column_map.go +++ b/internal/statements/column_map.go @@ -7,7 +7,7 @@ package statements import ( "strings" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/schemas" ) type columnMap []string diff --git a/internal/statements/expr_param.go b/internal/statements/expr_param.go index 6657408e..072ce829 100644 --- a/internal/statements/expr_param.go +++ b/internal/statements/expr_param.go @@ -8,8 +8,8 @@ import ( "fmt" "strings" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" ) type ErrUnsupportedExprType struct { diff --git a/internal/statements/insert.go b/internal/statements/insert.go index 6cbbbeda..f9ed5ac5 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -8,8 +8,8 @@ import ( "fmt" "strings" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" ) func (statement *Statement) writeInsertOutput(buf *strings.Builder, table *schemas.Table) error { diff --git a/internal/statements/pk.go b/internal/statements/pk.go index 59da89c0..4179808b 100644 --- a/internal/statements/pk.go +++ b/internal/statements/pk.go @@ -8,8 +8,8 @@ import ( "fmt" "reflect" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/internal/statements/query.go b/internal/statements/query.go index ab3021bf..8722598c 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -10,8 +10,8 @@ import ( "reflect" "strings" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" ) func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { diff --git a/internal/statements/statement.go b/internal/statements/statement.go index a4294bec..813bdc89 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -12,14 +12,14 @@ import ( "strings" "time" - "xorm.io/builder" - "xorm.io/xorm/contexts" - "xorm.io/xorm/convert" - "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" - "xorm.io/xorm/tags" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" + "gitea.com/laixyz/xorm/tags" ) var ( diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index dc14467d..cc63e0e0 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "xorm.io/builder" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/schemas" ) func quoteNeeded(a interface{}) bool { diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go index 15f446f4..efb1c1b0 100644 --- a/internal/statements/statement_test.go +++ b/internal/statements/statement_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" + "gitea.com/laixyz/xorm/tags" "github.com/stretchr/testify/assert" - "xorm.io/xorm/caches" - "xorm.io/xorm/dialects" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" - "xorm.io/xorm/tags" _ "github.com/mattn/go-sqlite3" ) diff --git a/internal/statements/update.go b/internal/statements/update.go index 251880b2..057c59b2 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -11,11 +11,11 @@ import ( "reflect" "time" - "xorm.io/xorm/convert" - "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) func (statement *Statement) ifAddColUpdate(col *schemas.Column, includeVersion, includeUpdated, includeNil, diff --git a/internal/statements/values.go b/internal/statements/values.go index 71327c55..79c73ee5 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -11,10 +11,10 @@ import ( "reflect" "time" - "xorm.io/xorm/convert" - "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/log/logger_context.go b/log/logger_context.go index 46802576..7510668a 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -7,7 +7,7 @@ package log import ( "fmt" - "xorm.io/xorm/contexts" + "gitea.com/laixyz/xorm/contexts" ) // LogContext represents a log context diff --git a/migrate/migrate.go b/migrate/migrate.go index 82c58f90..5fc9e6f8 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "xorm.io/xorm" + "gitea.com/laixyz/xorm" ) // MigrateFunc is the func signature for migrating. diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 19554f7e..99aa167a 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -6,9 +6,9 @@ import ( "os" "testing" + "gitea.com/laixyz/xorm" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" - "xorm.io/xorm" ) type Person struct { diff --git a/rows.go b/rows.go index a56ea1c9..ce9b19d5 100644 --- a/rows.go +++ b/rows.go @@ -10,9 +10,9 @@ import ( "fmt" "reflect" - "xorm.io/builder" - "xorm.io/xorm/core" - "xorm.io/xorm/internal/utils" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/internal/utils" ) // Rows rows wrapper a rows to diff --git a/schemas/pk.go b/schemas/pk.go index 03916b44..eb44c775 100644 --- a/schemas/pk.go +++ b/schemas/pk.go @@ -8,7 +8,7 @@ import ( "bytes" "encoding/gob" - "xorm.io/xorm/internal/utils" + "gitea.com/laixyz/xorm/internal/utils" ) type PK []interface{} diff --git a/session.go b/session.go index 17abd453..4c7f9061 100644 --- a/session.go +++ b/session.go @@ -18,13 +18,13 @@ import ( "strings" "time" - "xorm.io/xorm/contexts" - "xorm.io/xorm/convert" - "xorm.io/xorm/core" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/internal/statements" - "xorm.io/xorm/log" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/contexts" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/internal/statements" + "gitea.com/laixyz/xorm/log" + "gitea.com/laixyz/xorm/schemas" ) // ErrFieldIsNotExist columns does not exist diff --git a/session_cols.go b/session_cols.go index ca3589ab..eea2b26d 100644 --- a/session_cols.go +++ b/session_cols.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/schemas" ) func setColumnInt(bean interface{}, col *schemas.Column, t int64) { diff --git a/session_cond.go b/session_cond.go index 25d17148..6e631bab 100644 --- a/session_cond.go +++ b/session_cond.go @@ -4,7 +4,7 @@ package xorm -import "xorm.io/builder" +import "gitea.com/laixyz/xorm/builder" // SQL provides raw sql input parameter. When you have a complex SQL statement // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. diff --git a/session_convert.go b/session_convert.go index a6839947..25769933 100644 --- a/session_convert.go +++ b/session_convert.go @@ -13,10 +13,10 @@ import ( "strings" "time" - "xorm.io/xorm/convert" - "xorm.io/xorm/internal/json" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/internal/json" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) func (session *Session) str2Time(col *schemas.Column, data string) (outTime time.Time, outErr error) { diff --git a/session_delete.go b/session_delete.go index 13bf791f..94bab62c 100644 --- a/session_delete.go +++ b/session_delete.go @@ -9,8 +9,8 @@ import ( "fmt" "strconv" - "xorm.io/xorm/caches" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/session_find.go b/session_find.go index 0daea005..bf0e82b1 100644 --- a/session_find.go +++ b/session_find.go @@ -9,11 +9,11 @@ import ( "fmt" "reflect" - "xorm.io/builder" - "xorm.io/xorm/caches" - "xorm.io/xorm/internal/statements" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/internal/statements" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) const ( diff --git a/session_get.go b/session_get.go index afedcd1f..db1c44c9 100644 --- a/session_get.go +++ b/session_get.go @@ -11,9 +11,9 @@ import ( "reflect" "strconv" - "xorm.io/xorm/caches" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) // Get retrieve one record from database, bean's non-empty fields diff --git a/session_insert.go b/session_insert.go index 5f968151..0f7ec4cd 100644 --- a/session_insert.go +++ b/session_insert.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) // ErrNoElementsOnSlice represents an error there is no element when insert diff --git a/session_iterate.go b/session_iterate.go index 8cab8f48..80b4766e 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -7,7 +7,7 @@ package xorm import ( "reflect" - "xorm.io/xorm/internal/utils" + "gitea.com/laixyz/xorm/internal/utils" ) // IterFunc only use by Iterate diff --git a/session_query.go b/session_query.go index 12136466..7f17cd9a 100644 --- a/session_query.go +++ b/session_query.go @@ -10,8 +10,8 @@ import ( "strconv" "time" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/core" + "gitea.com/laixyz/xorm/schemas" ) // Query runs a raw sql and return records as []map[string][]byte diff --git a/session_raw.go b/session_raw.go index 4cfe297a..be00f81e 100644 --- a/session_raw.go +++ b/session_raw.go @@ -8,7 +8,7 @@ import ( "database/sql" "reflect" - "xorm.io/xorm/core" + "gitea.com/laixyz/xorm/core" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { diff --git a/session_schema.go b/session_schema.go index 9ccf8abe..3a0371a2 100644 --- a/session_schema.go +++ b/session_schema.go @@ -12,8 +12,8 @@ import ( "os" "strings" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) // Ping test if database is ok diff --git a/session_update.go b/session_update.go index 7df8c752..d4b50de1 100644 --- a/session_update.go +++ b/session_update.go @@ -11,10 +11,10 @@ import ( "strconv" "strings" - "xorm.io/builder" - "xorm.io/xorm/caches" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/builder" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/internal/utils" + "gitea.com/laixyz/xorm/schemas" ) func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { diff --git a/tags/parser.go b/tags/parser.go index a301d124..7190671d 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -13,11 +13,11 @@ import ( "sync" "time" - "xorm.io/xorm/caches" - "xorm.io/xorm/convert" - "xorm.io/xorm/dialects" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/convert" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/names" + "gitea.com/laixyz/xorm/schemas" ) var ( diff --git a/tags/parser_test.go b/tags/parser_test.go index ff304a5b..157b97dc 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -8,10 +8,10 @@ import ( "reflect" "testing" + "gitea.com/laixyz/xorm/caches" + "gitea.com/laixyz/xorm/dialects" + "gitea.com/laixyz/xorm/names" "github.com/stretchr/testify/assert" - "xorm.io/xorm/caches" - "xorm.io/xorm/dialects" - "xorm.io/xorm/names" ) type ParseTableName1 struct{} diff --git a/tags/tag.go b/tags/tag.go index bb5b5838..dae9d13b 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "xorm.io/xorm/schemas" + "gitea.com/laixyz/xorm/schemas" ) func splitTag(tag string) (tags []string) { diff --git a/tags/tag_test.go b/tags/tag_test.go index 5775b40a..1e7f3198 100644 --- a/tags/tag_test.go +++ b/tags/tag_test.go @@ -7,7 +7,7 @@ package tags import ( "testing" - "xorm.io/xorm/internal/utils" + "gitea.com/laixyz/xorm/internal/utils" ) func TestSplitTag(t *testing.T) {