├── delete.go ├── exception.go ├── go.mod ├── null.go ├── update.go ├── select.go ├── in.go ├── LICENSE ├── between.go ├── exists.go ├── aggregate.go ├── union.go ├── groupby.go ├── orderby.go ├── go.sum ├── insert.go ├── join.go ├── future.go ├── where.go ├── tests └── querybuilder_test.go └── readme.md /delete.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (builder *Builder) DeleteSql() (sql string, bindings []interface{}) { 8 | sql = fmt.Sprintf("delete from %s", builder.table) 9 | 10 | if !builder.wheres.IsEmpty() { 11 | sql = fmt.Sprintf("%s where %s", sql, builder.wheres.String()) 12 | } 13 | bindings = builder.GetBindings() 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /exception.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "github.com/goal-web/contracts" 5 | ) 6 | 7 | type ParamException struct { 8 | Err error 9 | Arg interface{} 10 | Condition string 11 | previous contracts.Exception 12 | } 13 | 14 | func (p ParamException) Error() string { 15 | return p.Err.Error() 16 | } 17 | 18 | func (p ParamException) GetPrevious() contracts.Exception { 19 | return p.previous 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goal-web/querybuilder 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/goal-web/contracts v0.1.66 7 | github.com/goal-web/supports v0.1.32 8 | github.com/stretchr/testify v1.7.0 9 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pkg/errors v0.8.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /null.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import "github.com/goal-web/contracts" 4 | 5 | func (builder *Builder) WhereIsNull(field string, whereType ...contracts.WhereJoinType) contracts.QueryBuilder { 6 | if len(whereType) == 0 { 7 | return builder.Where(field, "is", "null") 8 | } 9 | return builder.Where(field, "is", "null", whereType[0]) 10 | } 11 | 12 | func (builder *Builder) WhereNotNull(field string, whereType ...contracts.WhereJoinType) contracts.QueryBuilder { 13 | if len(whereType) == 0 { 14 | return builder.Where(field, "is not", "null") 15 | } 16 | return builder.Where(field, "is not", "null", whereType[0]) 17 | } 18 | 19 | func (builder *Builder) OrWhereIsNull(field string) contracts.QueryBuilder { 20 | return builder.OrWhere(field, "is", "null") 21 | } 22 | 23 | func (builder *Builder) OrWhereNotNull(field string) contracts.QueryBuilder { 24 | return builder.OrWhere(field, "is not", "null") 25 | } 26 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | "strings" 7 | ) 8 | 9 | type Expression string 10 | 11 | func (builder *Builder) UpdateSql(value contracts.Fields) (sql string, bindings []interface{}) { 12 | if len(value) == 0 { 13 | return 14 | } 15 | valuesString := make([]string, 0) 16 | for name, field := range value { 17 | if expression, isExpression := field.(Expression); isExpression { 18 | valuesString = append(valuesString, fmt.Sprintf("%s = %s", name, expression)) 19 | } else { 20 | valuesString = append(valuesString, fmt.Sprintf("%s = ?", name)) 21 | bindings = append(bindings, field) 22 | } 23 | } 24 | 25 | sql = fmt.Sprintf("update %s set %s", builder.table, strings.Join(valuesString, ",")) 26 | 27 | if !builder.wheres.IsEmpty() { 28 | sql = fmt.Sprintf("%s where %s", sql, builder.wheres.String()) 29 | } 30 | 31 | bindings = append(bindings, builder.bindings[whereBinding]...) 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /select.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | func (builder *Builder) Select(fields ...string) contracts.QueryBuilder { 9 | builder.fields = fields 10 | return builder 11 | } 12 | 13 | func (builder *Builder) AddSelect(fields ...string) contracts.QueryBuilder { 14 | builder.fields = append(builder.fields, fields...) 15 | return builder 16 | } 17 | 18 | func (builder *Builder) SelectSub(provider contracts.QueryProvider, as string) contracts.QueryBuilder { 19 | subBuilder := provider() 20 | builder.fields = []string{fmt.Sprintf("(%s) as %s", subBuilder.ToSql(), as)} 21 | return builder.addBinding(selectBinding, subBuilder.GetBindings()...) 22 | } 23 | func (builder *Builder) AddSelectSub(provider contracts.QueryProvider, as string) contracts.QueryBuilder { 24 | subBuilder := provider() 25 | builder.fields = append(builder.fields, fmt.Sprintf("(%s) as %s", subBuilder.ToSql(), as)) 26 | return builder.addBinding(selectBinding, subBuilder.GetBindings()...) 27 | } 28 | -------------------------------------------------------------------------------- /in.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import "github.com/goal-web/contracts" 4 | 5 | // WhereIn args 参数可以是整数、浮点数、字符串、interface{} 等类型的数组,或者用` and `隔开的字符串,或者在源码中了解更多 https://github.com/goal-web/querybuilder/blob/78bcc832604bfcdb68579e3dd1441796a16994cf/builder.go#L74 6 | func (builder *Builder) WhereIn(field string, args interface{}, joinType ...contracts.WhereJoinType) contracts.QueryBuilder { 7 | if len(joinType) == 0 { 8 | return builder.Where(field, "in", args) 9 | } 10 | return builder.Where(field, "in", args, joinType[0]) 11 | } 12 | 13 | func (builder *Builder) OrWhereIn(field string, args interface{}) contracts.QueryBuilder { 14 | return builder.OrWhere(field, "in", args) 15 | } 16 | 17 | func (builder *Builder) WhereNotIn(field string, args interface{}, joinType ...contracts.WhereJoinType) contracts.QueryBuilder { 18 | if len(joinType) == 0 { 19 | return builder.Where(field, "not in", args) 20 | } 21 | return builder.Where(field, "not in", args) 22 | } 23 | 24 | func (builder *Builder) OrWhereNotIn(field string, args interface{}) contracts.QueryBuilder { 25 | return builder.OrWhere(field, "not in", args) 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 goal-web 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /between.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import "github.com/goal-web/contracts" 4 | 5 | // WhereBetween args 参数可以是整数、浮点数、字符串、interface{} 等类型的数组,或者用` and `隔开的字符串,或者在源码中了解更多 https://github.com/goal-web/querybuilder/blob/78bcc832604bfcdb68579e3dd1441796a16994cf/builder.go#L74 6 | func (builder *Builder) WhereBetween(field string, args interface{}, whereType ...contracts.WhereJoinType) contracts.QueryBuilder { 7 | if len(whereType) > 0 { 8 | return builder.Where(field, "between", args, whereType[0]) 9 | } 10 | 11 | return builder.Where(field, "between", args) 12 | } 13 | 14 | func (builder *Builder) OrWhereBetween(field string, args interface{}) contracts.QueryBuilder { 15 | return builder.OrWhere(field, "between", args) 16 | } 17 | 18 | func (builder *Builder) WhereNotBetween(field string, args interface{}, whereType ...contracts.WhereJoinType) contracts.QueryBuilder { 19 | if len(whereType) > 0 { 20 | return builder.Where(field, "not between", args, whereType[0]) 21 | } 22 | 23 | return builder.Where(field, "not between", args) 24 | } 25 | 26 | func (builder *Builder) OrWhereNotBetween(field string, args interface{}) contracts.QueryBuilder { 27 | return builder.OrWhere(field, "not between", args) 28 | } 29 | -------------------------------------------------------------------------------- /exists.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | func (builder *Builder) WhereExists(provider contracts.QueryProvider, where ...contracts.WhereJoinType) contracts.QueryBuilder { 9 | subBuilder := provider() 10 | subSql := fmt.Sprintf("(%s)", subBuilder.ToSql()) 11 | if len(where) == 0 { 12 | return builder.addBinding(whereBinding, subBuilder.GetBindings()...). 13 | Where("", "exists", subSql) 14 | } 15 | 16 | return builder.addBinding(whereBinding, subBuilder.GetBindings()...). 17 | Where("", "exists", subSql, where[0]) 18 | 19 | } 20 | 21 | func (builder *Builder) OrWhereExists(provider contracts.QueryProvider) contracts.QueryBuilder { 22 | return builder.WhereExists(provider, contracts.Or) 23 | } 24 | 25 | func (builder *Builder) WhereNotExists(provider contracts.QueryProvider, where ...contracts.WhereJoinType) contracts.QueryBuilder { 26 | subBuilder := provider() 27 | subSql := fmt.Sprintf("(%s)", subBuilder.ToSql()) 28 | if len(where) == 0 { 29 | return builder.addBinding(whereBinding, subBuilder.GetBindings()...). 30 | Where("", "not exists", subSql) 31 | } 32 | 33 | return builder.addBinding(whereBinding, subBuilder.GetBindings()...). 34 | Where("", "not exists", subSql, where[0]) 35 | } 36 | 37 | func (builder *Builder) OrWhereNotExists(provider contracts.QueryProvider) contracts.QueryBuilder { 38 | return builder.WhereNotExists(provider, contracts.Or) 39 | } 40 | -------------------------------------------------------------------------------- /aggregate.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | func (builder *Builder) WithCount(fields ...string) contracts.QueryBuilder { 9 | if len(fields) == 0 { 10 | return builder.Select("count(*)") 11 | } 12 | return builder.Select(fmt.Sprintf("count(%s) as %s_count", fields[0], fields[0])) 13 | } 14 | 15 | func (builder *Builder) WithAvg(field string, as ...string) contracts.QueryBuilder { 16 | if len(as) == 0 { 17 | return builder.Select(fmt.Sprintf("avg(%s) as %s_avg", field, field)) 18 | } 19 | return builder.Select(fmt.Sprintf("avg(%s) as %s", field, as[0])) 20 | } 21 | 22 | func (builder *Builder) WithSum(field string, as ...string) contracts.QueryBuilder { 23 | if len(as) == 0 { 24 | return builder.Select(fmt.Sprintf("sum(%s) as %s_sum", field, field)) 25 | } 26 | return builder.Select(fmt.Sprintf("sum(%s) as %s", field, as[0])) 27 | } 28 | 29 | func (builder *Builder) WithMax(field string, as ...string) contracts.QueryBuilder { 30 | if len(as) == 0 { 31 | return builder.Select(fmt.Sprintf("max(%s) as %s_max", field, field)) 32 | } 33 | return builder.Select(fmt.Sprintf("max(%s) as %s", field, as[0])) 34 | } 35 | 36 | func (builder *Builder) WithMin(field string, as ...string) contracts.QueryBuilder { 37 | if len(as) == 0 { 38 | return builder.Select(fmt.Sprintf("min(%s) as %s_min", field, field)) 39 | } 40 | return builder.Select(fmt.Sprintf("min(%s) as %s", field, as[0])) 41 | } 42 | -------------------------------------------------------------------------------- /union.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | type Unions map[contracts.UnionJoinType][]contracts.QueryBuilder 9 | 10 | func (this Unions) IsEmpty() bool { 11 | return len(this) == 0 12 | } 13 | 14 | func (this Unions) String() (result string) { 15 | if this.IsEmpty() { 16 | return 17 | } 18 | for unionType, builders := range this { 19 | for _, builder := range builders { 20 | result = fmt.Sprintf("%s %s (%s)", result, unionType, builder.ToSql()) 21 | } 22 | } 23 | 24 | return 25 | } 26 | 27 | func (builder *Builder) Union(b contracts.QueryBuilder, unionType ...contracts.UnionJoinType) contracts.QueryBuilder { 28 | if builder != nil { 29 | if len(unionType) > 0 { 30 | builder.unions[unionType[0]] = append(builder.unions[unionType[0]], b) 31 | } else { 32 | builder.unions[contracts.Union] = append(builder.unions[contracts.Union], b) 33 | } 34 | } 35 | 36 | return builder.addBinding(unionBinding, builder.GetBindings()...) 37 | } 38 | 39 | func (builder *Builder) UnionAll(b contracts.QueryBuilder) contracts.QueryBuilder { 40 | return builder.Union(b, contracts.UnionAll) 41 | } 42 | 43 | func (builder *Builder) UnionByProvider(provider contracts.QueryProvider, unionType ...contracts.UnionJoinType) contracts.QueryBuilder { 44 | return builder.Union(provider(), unionType...) 45 | } 46 | 47 | func (builder *Builder) UnionAllByProvider(provider contracts.QueryProvider) contracts.QueryBuilder { 48 | return builder.Union(provider(), contracts.UnionAll) 49 | } 50 | -------------------------------------------------------------------------------- /groupby.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "github.com/goal-web/contracts" 5 | "strings" 6 | ) 7 | 8 | type GroupBy []string 9 | 10 | func (this GroupBy) IsEmpty() bool { 11 | return len(this) == 0 12 | } 13 | 14 | func (this GroupBy) String() string { 15 | if this.IsEmpty() { 16 | return "" 17 | } 18 | 19 | return strings.Join(this, ",") 20 | } 21 | 22 | func (builder *Builder) GroupBy(columns ...string) contracts.QueryBuilder { 23 | builder.groupBy = append(builder.groupBy, columns...) 24 | 25 | return builder 26 | } 27 | 28 | func (builder *Builder) Having(field string, args ...interface{}) contracts.QueryBuilder { 29 | var ( 30 | arg interface{} 31 | condition = "=" 32 | whereType = contracts.And 33 | ) 34 | switch len(args) { 35 | case 1: 36 | arg = args[0] 37 | case 2: 38 | condition = args[0].(string) 39 | arg = args[1] 40 | case 3: 41 | condition = args[0].(string) 42 | arg = args[1] 43 | whereType = args[2].(contracts.WhereJoinType) 44 | } 45 | 46 | raw, bindings := builder.prepareArgs(condition, arg) 47 | 48 | builder.having.wheres[whereType] = append(builder.having.wheres[whereType], &Where{ 49 | field: field, 50 | condition: condition, 51 | arg: raw, 52 | }) 53 | 54 | return builder.addBinding(havingBinding, bindings...) 55 | } 56 | 57 | func (builder *Builder) OrHaving(field string, args ...interface{}) contracts.QueryBuilder { 58 | var ( 59 | arg interface{} 60 | condition = "=" 61 | ) 62 | switch len(args) { 63 | case 1: 64 | arg = args[0] 65 | case 2: 66 | condition = args[0].(string) 67 | arg = args[1] 68 | default: 69 | condition = args[0].(string) 70 | arg = args[1] 71 | } 72 | raw, bindings := builder.prepareArgs(condition, arg) 73 | 74 | builder.having.wheres[contracts.Or] = append(builder.having.wheres[contracts.Or], &Where{ 75 | field: field, 76 | condition: condition, 77 | arg: raw, 78 | }) 79 | return builder.addBinding(havingBinding, bindings...) 80 | } 81 | -------------------------------------------------------------------------------- /orderby.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | "strings" 7 | ) 8 | 9 | const RandomOrder contracts.OrderType = "RANDOM()" 10 | const RandOrder contracts.OrderType = "RAND()" 11 | 12 | type OrderBy struct { 13 | field string 14 | fieldOrderType contracts.OrderType 15 | } 16 | 17 | type OrderByFields []OrderBy 18 | 19 | func (this OrderByFields) IsEmpty() bool { 20 | return len(this) == 0 21 | } 22 | 23 | func (this OrderByFields) String() string { 24 | if this.IsEmpty() { 25 | return "" 26 | } 27 | 28 | columns := make([]string, 0) 29 | 30 | for _, orderBy := range this { 31 | if orderBy.field == "" { 32 | columns = append(columns, fmt.Sprintf("%s", orderBy.fieldOrderType)) 33 | } else { 34 | columns = append(columns, fmt.Sprintf("%s %s", orderBy.field, orderBy.fieldOrderType)) 35 | } 36 | } 37 | 38 | return strings.Join(columns, ",") 39 | } 40 | 41 | func (builder *Builder) OrderBy(field string, columnOrderType ...contracts.OrderType) contracts.QueryBuilder { 42 | if len(columnOrderType) > 0 { 43 | builder.orderBy = append(builder.orderBy, OrderBy{ 44 | field: field, 45 | fieldOrderType: columnOrderType[0], 46 | }) 47 | } else { 48 | builder.orderBy = append(builder.orderBy, OrderBy{ 49 | field: field, 50 | fieldOrderType: contracts.Asc, 51 | }) 52 | } 53 | 54 | return builder 55 | } 56 | 57 | func (builder *Builder) OrderByDesc(field string) contracts.QueryBuilder { 58 | builder.orderBy = append(builder.orderBy, OrderBy{ 59 | field: field, 60 | fieldOrderType: contracts.Desc, 61 | }) 62 | return builder 63 | } 64 | 65 | func (builder *Builder) InRandomOrder(orderFunc ...contracts.OrderType) contracts.QueryBuilder { 66 | fn := RandomOrder 67 | if len(orderFunc) > 0 { 68 | fn = orderFunc[0] 69 | } 70 | 71 | builder.orderBy = append(builder.orderBy, OrderBy{ 72 | field: "", 73 | fieldOrderType: fn, 74 | }) 75 | return builder 76 | } 77 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/goal-web/contracts v0.1.66 h1:X6nCPHLJXNkmfiYejd4zO78JCTGoaluBNQK/GmwUT0A= 5 | github.com/goal-web/contracts v0.1.66/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= 6 | github.com/goal-web/supports v0.1.32 h1:gB+nbrcnTJwwSHakkJTDI15y8Vaj6sx3ToGUwo7vOnE= 7 | github.com/goal-web/supports v0.1.32/go.mod h1:tdfY3NY4HenWU3WE7wZT0E1AljwRKWL7TgD4jL+yUoA= 8 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 9 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 14 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= 16 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= 21 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /insert.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | "github.com/goal-web/supports/utils" 7 | "strings" 8 | ) 9 | 10 | func getInsertType(insertType2 ...contracts.InsertType) contracts.InsertType { 11 | if len(insertType2) > 0 { 12 | return insertType2[0] 13 | } 14 | return contracts.Insert 15 | } 16 | 17 | func (builder *Builder) CreateSql(value contracts.Fields, insertType2 ...contracts.InsertType) (sql string, bindings []interface{}) { 18 | if len(value) == 0 { 19 | return 20 | } 21 | keys := make([]string, 0) 22 | 23 | valuesString := fmt.Sprintf("(%s)", strings.Join(utils.MakeSymbolArray("?", len(value)), ",")) 24 | for name, field := range value { 25 | bindings = append(bindings, field) 26 | keys = append(keys, name) 27 | } 28 | 29 | sql = fmt.Sprintf("%s into %s %s values %s", getInsertType(insertType2...), builder.table, fmt.Sprintf("(%s)", strings.Join(keys, ",")), valuesString) 30 | return 31 | } 32 | 33 | func (builder *Builder) InsertSql(values []contracts.Fields, insertType2 ...contracts.InsertType) (sql string, bindings []interface{}) { 34 | if len(values) == 0 { 35 | return 36 | } 37 | fields := utils.GetMapKeys(values[0]) 38 | valuesString := make([]string, 0) 39 | 40 | for _, value := range values { 41 | valuesString = append(valuesString, fmt.Sprintf("(%s)", strings.Join(utils.MakeSymbolArray("?", len(value)), ","))) 42 | for _, field := range fields { 43 | bindings = append(bindings, value[field]) 44 | } 45 | } 46 | 47 | fieldsString := fmt.Sprintf(" (%s)", strings.Join(fields, ",")) 48 | 49 | sql = fmt.Sprintf("%s into %s%s values %s", getInsertType(insertType2...), builder.table, fieldsString, strings.Join(valuesString, ",")) 50 | return 51 | } 52 | 53 | func (builder *Builder) InsertIgnoreSql(values []contracts.Fields) (sql string, bindings []interface{}) { 54 | return builder.InsertSql(values, contracts.InsertIgnore) 55 | } 56 | 57 | func (builder *Builder) InsertReplaceSql(values []contracts.Fields) (sql string, bindings []interface{}) { 58 | return builder.InsertSql(values, contracts.InsertReplace) 59 | } 60 | -------------------------------------------------------------------------------- /join.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | type Join struct { 9 | table string 10 | join contracts.JoinType 11 | conditions *Wheres 12 | } 13 | 14 | func (this Join) String() (result string) { 15 | result = fmt.Sprintf("%s join %s", this.join, this.table) 16 | if this.conditions.IsEmpty() { 17 | return 18 | } 19 | result = fmt.Sprintf("%s on (%s)", result, this.conditions.String()) 20 | return 21 | } 22 | 23 | type Joins []Join 24 | 25 | func (this Joins) IsEmpty() bool { 26 | return len(this) == 0 27 | } 28 | 29 | func (this Joins) String() (result string) { 30 | if this.IsEmpty() { 31 | return 32 | } 33 | 34 | for index, join := range this { 35 | if index == 0 { 36 | result = join.String() 37 | } else { 38 | result = fmt.Sprintf("%s %s", result, join.String()) 39 | } 40 | } 41 | 42 | return 43 | } 44 | 45 | func (builder *Builder) Join(table string, first, condition, second string, joins ...contracts.JoinType) contracts.QueryBuilder { 46 | join := contracts.InnerJoin 47 | if len(joins) > 0 { 48 | join = joins[0] 49 | } 50 | builder.joins = append(builder.joins, Join{table, join, &Wheres{wheres: map[contracts.WhereJoinType][]*Where{ 51 | contracts.And: {&Where{ 52 | field: first, 53 | condition: condition, 54 | arg: second, 55 | }}, 56 | }}}) 57 | 58 | return builder 59 | } 60 | 61 | func (builder *Builder) JoinSub(provider contracts.QueryProvider, as, first, condition, second string, joins ...contracts.JoinType) contracts.QueryBuilder { 62 | join := contracts.InnerJoin 63 | if len(joins) > 0 { 64 | join = joins[0] 65 | } 66 | subBuilder := provider() 67 | builder.joins = append(builder.joins, Join{fmt.Sprintf("(%s) as %s", subBuilder.ToSql(), as), join, &Wheres{wheres: map[contracts.WhereJoinType][]*Where{ 68 | contracts.And: {&Where{ 69 | field: first, 70 | condition: condition, 71 | arg: second, 72 | }}, 73 | }}}) 74 | 75 | return builder.addBinding(joinBinding, subBuilder.GetBindings()...) 76 | } 77 | 78 | func (builder *Builder) FullJoin(table string, first, condition, second string) contracts.QueryBuilder { 79 | return builder.Join(table, first, condition, second, contracts.FullJoin) 80 | } 81 | func (builder *Builder) FullOutJoin(table string, first, condition, second string) contracts.QueryBuilder { 82 | return builder.Join(table, first, condition, second, contracts.FullOutJoin) 83 | } 84 | 85 | func (builder *Builder) LeftJoin(table string, first, condition, second string) contracts.QueryBuilder { 86 | return builder.Join(table, first, condition, second, contracts.LeftJoin) 87 | } 88 | 89 | func (builder *Builder) RightJoin(table string, first, condition, second string) contracts.QueryBuilder { 90 | return builder.Join(table, first, condition, second, contracts.RightJoin) 91 | } 92 | -------------------------------------------------------------------------------- /future.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import "github.com/goal-web/contracts" 4 | 5 | func (builder *Builder) SetExecutor(executor contracts.SqlExecutor) contracts.QueryBuilder { 6 | return builder.QueryBuilder.SetExecutor(executor) 7 | } 8 | 9 | func (builder *Builder) Create(fields contracts.Fields) interface{} { 10 | return builder.QueryBuilder.Create(fields) 11 | } 12 | 13 | func (builder *Builder) Insert(values ...contracts.Fields) bool { 14 | return builder.QueryBuilder.Insert(values...) 15 | } 16 | 17 | func (builder *Builder) Delete() int64 { 18 | return builder.QueryBuilder.Delete() 19 | } 20 | 21 | func (builder *Builder) Update(fields contracts.Fields) int64 { 22 | return builder.QueryBuilder.Update(fields) 23 | } 24 | 25 | func (builder *Builder) Get() contracts.Collection { 26 | return builder.QueryBuilder.Get() 27 | } 28 | 29 | func (builder *Builder) SelectForUpdate() contracts.Collection { 30 | return builder.QueryBuilder.SelectForUpdate() 31 | } 32 | 33 | func (builder *Builder) Find(key interface{}) interface{} { 34 | return builder.QueryBuilder.Find(key) 35 | } 36 | 37 | func (builder *Builder) First() interface{} { 38 | return builder.QueryBuilder.First() 39 | } 40 | 41 | func (builder *Builder) InsertGetId(values ...contracts.Fields) int64 { 42 | return builder.QueryBuilder.InsertGetId(values...) 43 | } 44 | 45 | func (builder *Builder) InsertOrIgnore(values ...contracts.Fields) int64 { 46 | return builder.QueryBuilder.InsertOrIgnore(values...) 47 | } 48 | 49 | func (builder *Builder) InsertOrReplace(values ...contracts.Fields) int64 { 50 | return builder.QueryBuilder.InsertOrReplace(values...) 51 | } 52 | 53 | func (builder *Builder) FirstOrCreate(values ...contracts.Fields) interface{} { 54 | return builder.QueryBuilder.FirstOrCreate(values...) 55 | } 56 | 57 | func (builder *Builder) UpdateOrInsert(attributes contracts.Fields, values ...contracts.Fields) bool { 58 | return builder.QueryBuilder.UpdateOrInsert(attributes, values...) 59 | } 60 | 61 | func (builder *Builder) UpdateOrCreate(attributes contracts.Fields, values ...contracts.Fields) interface{} { 62 | return builder.QueryBuilder.UpdateOrCreate(attributes, values...) 63 | } 64 | 65 | func (builder *Builder) FirstOrFail() interface{} { 66 | return builder.QueryBuilder.FirstOrFail() 67 | } 68 | 69 | func (builder *Builder) Count(columns ...string) int64 { 70 | return builder.QueryBuilder.Count(columns...) 71 | } 72 | 73 | func (builder *Builder) Avg(column string, as ...string) int64 { 74 | return builder.QueryBuilder.Avg(column, as...) 75 | } 76 | 77 | func (builder *Builder) Sum(column string, as ...string) int64 { 78 | return builder.QueryBuilder.Sum(column, as...) 79 | } 80 | 81 | func (builder *Builder) Max(column string, as ...string) int64 { 82 | return builder.QueryBuilder.Max(column, as...) 83 | } 84 | 85 | func (builder *Builder) Min(column string, as ...string) int64 { 86 | return builder.QueryBuilder.Min(column, as...) 87 | } 88 | 89 | func (builder *Builder) SimplePaginate(perPage int64, current ...int64) contracts.Collection { 90 | return builder.WithPagination(perPage, current...).Get() 91 | } 92 | 93 | func (builder *Builder) FirstOr(provider contracts.InstanceProvider) interface{} { 94 | if result := builder.First(); result != nil { 95 | return result 96 | } 97 | return provider() 98 | } 99 | 100 | func (builder *Builder) FirstWhere(column string, args ...interface{}) interface{} { 101 | return builder.Where(column, args...).First() 102 | } 103 | 104 | func (builder *Builder) Paginate(perPage int64, current ...int64) (contracts.Collection, int64) { 105 | return builder.SimplePaginate(perPage, current...), builder.Count() 106 | } 107 | -------------------------------------------------------------------------------- /where.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | ) 7 | 8 | type Where struct { 9 | field string 10 | condition string 11 | arg string 12 | } 13 | 14 | func (this *Where) String() string { 15 | if this == nil { 16 | return "" 17 | } 18 | return fmt.Sprintf("%s %s %s", this.field, this.condition, this.arg) 19 | } 20 | 21 | type Wheres struct { 22 | subWheres map[contracts.WhereJoinType][]*Wheres 23 | wheres map[contracts.WhereJoinType][]*Where 24 | } 25 | 26 | func (this *Wheres) IsEmpty() bool { 27 | return len(this.subWheres) == 0 && len(this.wheres) == 0 28 | } 29 | 30 | func (this Wheres) getSubStringers(whereType contracts.WhereJoinType) []fmt.Stringer { 31 | stringers := make([]fmt.Stringer, 0) 32 | for _, where := range this.subWheres[whereType] { 33 | stringers = append(stringers, where) 34 | } 35 | return stringers 36 | } 37 | 38 | func (this Wheres) getStringers(whereType contracts.WhereJoinType) []fmt.Stringer { 39 | stringers := make([]fmt.Stringer, 0) 40 | for _, where := range this.wheres[whereType] { 41 | stringers = append(stringers, where) 42 | } 43 | return stringers 44 | } 45 | 46 | func (this *Wheres) getSubWheres(whereType contracts.WhereJoinType) string { 47 | return JoinSubStringerArray(this.getSubStringers(whereType), string(whereType)) 48 | } 49 | 50 | func (this *Wheres) getWheres(whereType contracts.WhereJoinType) string { 51 | return JoinStringerArray(this.getStringers(whereType), string(whereType)) 52 | } 53 | 54 | func (this *Wheres) String() (result string) { 55 | if this == nil || this.IsEmpty() { 56 | return "" 57 | } 58 | 59 | result = this.getSubWheres(contracts.And) 60 | andWheres := this.getWheres(contracts.And) 61 | 62 | if result != "" { 63 | if andWheres != "" { 64 | result = fmt.Sprintf("%s and %s", result, andWheres) 65 | } 66 | } else { 67 | result = andWheres 68 | } 69 | 70 | orSubWheres := this.getSubWheres(contracts.Or) 71 | if result == "" { 72 | result = orSubWheres 73 | } else if orSubWheres != "" { 74 | result = fmt.Sprintf("%s or %s", result, orSubWheres) 75 | } 76 | 77 | orWheres := this.getWheres(contracts.Or) 78 | if result == "" { 79 | result = orWheres 80 | } else if orWheres != "" { 81 | result = fmt.Sprintf("%s or %s", result, orWheres) 82 | } 83 | 84 | return 85 | } 86 | 87 | func (builder *Builder) WhereFunc(callback contracts.QueryFunc, whereType ...contracts.WhereJoinType) contracts.QueryBuilder { 88 | subBuilder := &Builder{ 89 | wheres: &Wheres{ 90 | wheres: map[contracts.WhereJoinType][]*Where{}, 91 | subWheres: map[contracts.WhereJoinType][]*Wheres{}, 92 | }, 93 | bindings: map[bindingType][]interface{}{}, 94 | } 95 | callback(subBuilder) 96 | if len(whereType) == 0 { 97 | builder.wheres.subWheres[contracts.And] = append(builder.wheres.subWheres[contracts.And], subBuilder.getWheres()) 98 | } else { 99 | builder.wheres.subWheres[whereType[0]] = append(builder.wheres.subWheres[whereType[0]], subBuilder.getWheres()) 100 | } 101 | return builder.addBinding(whereBinding, subBuilder.GetBindings()...) 102 | } 103 | 104 | func (builder *Builder) WhereFields(fields contracts.Fields) contracts.QueryBuilder { 105 | for column, value := range fields { 106 | builder.Where(column, value) 107 | } 108 | return builder 109 | } 110 | 111 | func (builder *Builder) OrWhereFunc(callback contracts.QueryFunc) contracts.QueryBuilder { 112 | return builder.WhereFunc(callback, contracts.Or) 113 | } 114 | 115 | func (builder *Builder) Where(field string, args ...interface{}) contracts.QueryBuilder { 116 | var ( 117 | arg interface{} 118 | condition = "=" 119 | whereType = contracts.And 120 | ) 121 | switch len(args) { 122 | case 1: 123 | arg = args[0] 124 | case 2: 125 | condition = args[0].(string) 126 | arg = args[1] 127 | case 3: 128 | condition = args[0].(string) 129 | arg = args[1] 130 | whereType = args[2].(contracts.WhereJoinType) 131 | } 132 | 133 | raw, bindings := builder.prepareArgs(condition, arg) 134 | 135 | builder.wheres.wheres[whereType] = append(builder.wheres.wheres[whereType], &Where{ 136 | field: field, 137 | condition: condition, 138 | arg: raw, 139 | }) 140 | 141 | return builder.addBinding(whereBinding, bindings...) 142 | } 143 | 144 | func (builder *Builder) OrWhere(field string, args ...interface{}) contracts.QueryBuilder { 145 | var ( 146 | arg interface{} 147 | condition = "=" 148 | ) 149 | switch len(args) { 150 | case 1: 151 | arg = args[0] 152 | case 2: 153 | condition = args[0].(string) 154 | arg = args[1] 155 | default: 156 | condition = args[0].(string) 157 | arg = args[1] 158 | } 159 | raw, bindings := builder.prepareArgs(condition, arg) 160 | 161 | builder.wheres.wheres[contracts.Or] = append(builder.wheres.wheres[contracts.Or], &Where{ 162 | field: field, 163 | condition: condition, 164 | arg: raw, 165 | }) 166 | return builder.addBinding(whereBinding, bindings...) 167 | } 168 | 169 | func JoinStringerArray(arr []fmt.Stringer, sep string) (result string) { 170 | for index, stringer := range arr { 171 | if index == 0 { 172 | result = stringer.String() 173 | } else { 174 | result = fmt.Sprintf("%s %s %s", result, sep, stringer.String()) 175 | } 176 | } 177 | 178 | return 179 | } 180 | 181 | func JoinSubStringerArray(arr []fmt.Stringer, sep string) (result string) { 182 | for index, stringer := range arr { 183 | if index == 0 { 184 | result = fmt.Sprintf("(%s)", stringer.String()) 185 | } else { 186 | result = fmt.Sprintf("%s %s (%s)", result, sep, stringer.String()) 187 | } 188 | } 189 | 190 | return 191 | } 192 | -------------------------------------------------------------------------------- /tests/querybuilder_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goal-web/contracts" 6 | builder "github.com/goal-web/querybuilder" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/xwb1989/sqlparser" 9 | "testing" 10 | ) 11 | 12 | func TestSimpleQueryBuilder(t *testing.T) { 13 | query := builder.NewQuery("users") 14 | query.Where("name", "qbhy"). 15 | Where("age", ">", 18). 16 | Where("gender", "!=", 0, contracts.Or). 17 | OrWhere("amount", ">=", 100). 18 | WhereIsNull("avatar") 19 | fmt.Println(query.ToSql()) 20 | fmt.Println(query.GetBindings()) 21 | 22 | _, err := sqlparser.Parse(query.ToSql()) 23 | assert.Nil(t, err, err) 24 | } 25 | 26 | func TestJoinQueryBuilder(t *testing.T) { 27 | query := builder.NewQuery("users"). 28 | Join("accounts", "accounts.user_id", "=", "users.id"). 29 | JoinSub(func() contracts.QueryBuilder { 30 | return builder.NewQuery("users"). 31 | Where("level", ">", 5) 32 | }, "vip_users", "vip_users.id", "=", "users.id"). 33 | //WhereIn("gender", "1,2"). 34 | WhereIn("gender", []int{1, 2}) 35 | fmt.Println(query.ToSql()) 36 | fmt.Println(query.GetBindings()) 37 | _, err := sqlparser.Parse(query.ToSql()) 38 | assert.Nil(t, err, err) 39 | } 40 | 41 | func TestFromSubQueryBuilder(t *testing.T) { 42 | query := builder.FromSub(func() contracts.QueryBuilder { 43 | return builder.NewQuery("users"). 44 | Where("level", ">", 5) 45 | }, "vip_users"). 46 | //WhereIn("gender", "1,2"). 47 | WhereIn("gender", []int{1, 2}) 48 | fmt.Println(query.ToSql()) 49 | fmt.Println(query.GetBindings()) 50 | _, err := sqlparser.Parse(query.ToSql()) 51 | assert.Nil(t, err, err) 52 | } 53 | 54 | func TestDistinctQueryBuilder(t *testing.T) { 55 | query := builder.NewQuery("users"). 56 | Distinct(). 57 | Join("accounts", "accounts.user_id", "=", "users.id"). 58 | Where("gender", "!=", 0, contracts.Or) 59 | fmt.Println(query.ToSql()) 60 | fmt.Println(query.GetBindings()) 61 | _, err := sqlparser.Parse(query.ToSql()) 62 | assert.Nil(t, err, err) 63 | } 64 | 65 | func TestUpdateSql(t *testing.T) { 66 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).UpdateSql(contracts.Fields{ 67 | "name": "qbhy", "age": 18, "money": 100000000000, 68 | }) 69 | fmt.Println(sql) 70 | fmt.Println(bindings) 71 | _, err := sqlparser.Parse(sql) 72 | assert.Nil(t, err, err) 73 | 74 | rawUpdateSql, rawUpdateBindings := builder.NewQuery("users"). 75 | Where("id", ">", 1). 76 | UpdateSql(contracts.Fields{ 77 | "name": "qbhy", 78 | "age": builder.Expression("`age` + 10"), 79 | "money": 100000000000, 80 | }) 81 | fmt.Println(rawUpdateSql) 82 | fmt.Println(rawUpdateBindings) 83 | _, rawUpdateErr := sqlparser.Parse(rawUpdateSql) 84 | assert.Nil(t, rawUpdateErr, rawUpdateErr) 85 | } 86 | 87 | func TestSelectSub(t *testing.T) { 88 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1). 89 | SelectSub(func() contracts.QueryBuilder { 90 | return builder.NewQuery("accounts").Where("accounts.id", "users.id").WithCount() 91 | }, "accounts_count"). 92 | Join("accounts", "accounts.user_id", "=", "users.id"). 93 | SelectSql() 94 | fmt.Println(sql) 95 | fmt.Println(bindings) 96 | _, err := sqlparser.Parse(sql) 97 | assert.Nil(t, err, err) 98 | } 99 | 100 | func TestWhereByExpression(t *testing.T) { 101 | sql, bindings := builder.NewQuery("users"). 102 | Where("id", ">", 1). 103 | WhereIn("user_id", builder.Expression("(select user_id from follows where follower_id=1)")). 104 | SelectSql() 105 | fmt.Println(sql) 106 | fmt.Println(bindings) 107 | _, err := sqlparser.Parse(sql) 108 | assert.Nil(t, err, err) 109 | } 110 | 111 | func TestWhereByQuery(t *testing.T) { 112 | sql, bindings := builder.NewQuery("users"). 113 | Where("id", ">", 1). 114 | WhereIn("user_id", builder.NewQuery("follows").Where("follower_id", 2)). 115 | SelectSql() 116 | fmt.Println(sql) 117 | fmt.Println(bindings) 118 | _, err := sqlparser.Parse(sql) 119 | assert.Nil(t, err, err) 120 | } 121 | 122 | func TestSelectForUpdate(t *testing.T) { 123 | sql, bindings := builder.NewQuery("users"). 124 | Where("id", ">", 1). 125 | SelectForUpdateSql() 126 | fmt.Println(sql) 127 | fmt.Println(bindings) 128 | _, err := sqlparser.Parse(sql) 129 | assert.Nil(t, err, err) 130 | } 131 | 132 | func TestWhereNotExists(t *testing.T) { 133 | sql, bindings := builder.NewQuery("users"). 134 | Where("id", ">", 1). 135 | WhereNotExists(func() contracts.QueryBuilder { 136 | return builder.NewQuery("users").Select("id").Where("age", ">", 18) 137 | }). 138 | SelectSql() 139 | fmt.Println(sql) 140 | fmt.Println(bindings) 141 | _, err := sqlparser.Parse(sql) 142 | assert.Nil(t, err, err) 143 | } 144 | func TestCount(t *testing.T) { 145 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).WithCount("id").SelectSql() 146 | fmt.Println(sql) 147 | fmt.Println(bindings) 148 | _, err := sqlparser.Parse(sql) 149 | assert.Nil(t, err, err) 150 | } 151 | func TestDeleteSql(t *testing.T) { 152 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).DeleteSql() 153 | fmt.Println(sql) 154 | fmt.Println(bindings) 155 | _, err := sqlparser.Parse(sql) 156 | assert.Nil(t, err, err) 157 | } 158 | func TestInsertSql(t *testing.T) { 159 | sql, bindings := builder.NewQuery("users").InsertSql([]contracts.Fields{ 160 | {"name": "qbhy", "age": 18, "money": 100000000000}, 161 | {"name": "goal", "age": 18, "money": 10}, 162 | }) 163 | fmt.Println(sql) 164 | fmt.Println(bindings) 165 | _, err := sqlparser.Parse(sql) 166 | assert.Nil(t, err, err) 167 | } 168 | func TestInsertIgnoreSql(t *testing.T) { 169 | sql, bindings := builder.NewQuery("users").InsertIgnoreSql([]contracts.Fields{ 170 | {"name": "qbhy", "age": 18, "money": 100000000000}, 171 | {"name": "goal", "age": 18, "money": 10}, 172 | }) 173 | fmt.Println(sql) 174 | fmt.Println(bindings) 175 | _, err := sqlparser.Parse(sql) 176 | assert.Nil(t, err, err) 177 | } 178 | func TestInsertReplaceSql(t *testing.T) { 179 | sql, bindings := builder.NewQuery("users").InsertReplaceSql([]contracts.Fields{ 180 | {"name": "qbhy", "age": 18, "money": 100000000000}, 181 | {"name": "goal", "age": 18, "money": 10}, 182 | }) 183 | fmt.Println(sql) 184 | fmt.Println(bindings) 185 | _, err := sqlparser.Parse(sql) 186 | assert.Nil(t, err, err) 187 | } 188 | 189 | func TestCreateSql(t *testing.T) { 190 | sql, bindings := builder.NewQuery("users").CreateSql(contracts.Fields{ 191 | "name": "qbhy", "age": 18, "money": 100000000000, 192 | }) 193 | fmt.Println(sql) 194 | fmt.Println(bindings) 195 | _, err := sqlparser.Parse(sql) 196 | assert.Nil(t, err, err) 197 | } 198 | 199 | func TestBetweenQueryBuilder(t *testing.T) { 200 | query := builder.NewQuery("users"). 201 | Join("accounts", "accounts.user_id", "=", "users.id"). 202 | WhereFunc(func(b contracts.QueryBuilder) { 203 | // 高瘦 204 | b.WhereBetween("height", []int{180, 200}). 205 | WhereBetween("weight", []int{50, 60}). 206 | WhereIn("id", []int{1, 2, 3, 4, 5}) 207 | }).OrWhereFunc(func(b contracts.QueryBuilder) { 208 | // 矮胖 209 | b.WhereBetween("height", []int{140, 160}). 210 | WhereBetween("weight", []int{70, 140}). 211 | WhereNotBetween("id", []int{1, 5}) 212 | }) 213 | fmt.Println(query.ToSql()) 214 | fmt.Println(query.GetBindings()) 215 | _, err := sqlparser.Parse(query.ToSql()) 216 | assert.Nil(t, err, err) 217 | } 218 | 219 | func TestUnionQueryBuilder(t *testing.T) { 220 | query := builder.NewQuery("users"). 221 | Join("accounts", "accounts.user_id", "=", "users.id"). 222 | Where("gender", "!=", 0, contracts.Or). 223 | UnionByProvider( 224 | func() contracts.QueryBuilder { 225 | return builder.NewQuery("peoples").Where("id", 5) 226 | }, 227 | ). 228 | Union( 229 | builder.NewQuery("accounts"), 230 | ). 231 | UnionAll( 232 | builder.NewQuery("members"), 233 | ). 234 | UnionAll( 235 | builder.NewQuery("students"), 236 | ) 237 | fmt.Println(query.ToSql()) 238 | fmt.Println(query.GetBindings()) 239 | _, err := sqlparser.Parse(query.ToSql()) 240 | assert.Nil(t, err, err) 241 | } 242 | 243 | func TestComplexQueryBuilder(t *testing.T) { 244 | 245 | query := builder.NewQuery("users") 246 | query. 247 | FromSub(func() contracts.QueryBuilder { 248 | return builder.NewQuery("users").Where("amount", ">", 1000) 249 | }, "rich_users"). 250 | Join("accounts", "users.id", "=", "accounts.user_id"). 251 | WhereFunc(func(b contracts.QueryBuilder) { 252 | b.Where("name", "goal"). 253 | Where("age", "<", "18"). 254 | WhereIn("id", []int{1, 2}) 255 | }). 256 | OrWhereFunc(func(b contracts.QueryBuilder) { 257 | b.Where("name", "qbhy"). 258 | Where("age", ">", 18). 259 | WhereNotIn("id", []int{1, 2}) 260 | }). 261 | OrWhereNotIn("id", []int{6, 7}). 262 | OrWhereNotNull("id"). 263 | OrderByDesc("age"). 264 | OrderBy("id"). 265 | GroupBy("country") 266 | 267 | fmt.Println(query.ToSql()) 268 | fmt.Println(query.GetBindings()) 269 | _, err := sqlparser.Parse(query.ToSql()) 270 | assert.Nil(t, err, err) 271 | } 272 | 273 | func TestGroupByQueryBuilder(t *testing.T) { 274 | query := builder. 275 | FromSub(func() contracts.QueryBuilder { 276 | return builder.NewQuery("users").Where("amount", ">", 1000) 277 | }, "rich_users"). 278 | GroupBy("country"). 279 | Having("count(rich_users.id)", "<", 1000). // 人口少 280 | OrHaving("sum(rich_users.amount)", "<", 100) // 或者穷 281 | 282 | fmt.Println(query.ToSql()) 283 | fmt.Println(query.GetBindings()) 284 | _, err := sqlparser.Parse(query.ToSql()) 285 | assert.Nil(t, err, err) 286 | } 287 | 288 | func TestInRandomOrder(t *testing.T) { 289 | query := builder. 290 | NewQuery("users"). 291 | GroupBy("country"). 292 | Having("count(rich_users.id)", "<", 1000). // 人口少 293 | OrHaving("sum(rich_users.amount)", "<", 100). // 或者穷 294 | InRandomOrder() 295 | 296 | fmt.Println(query.ToSql()) 297 | fmt.Println(query.GetBindings()) 298 | _, err := sqlparser.Parse(query.ToSql()) 299 | assert.Nil(t, err, err) 300 | } 301 | 302 | func TestWhereIn(t *testing.T) { 303 | query := builder. 304 | NewQuery("users"). 305 | WhereNotIn("id", []interface{}{1, 2, 3, 4}). 306 | InRandomOrder() 307 | 308 | sql := query.ToSql() 309 | fmt.Println(sql) 310 | fmt.Println(query.GetBindings()) 311 | _, err := sqlparser.Parse(sql) 312 | assert.Nil(t, err, err) 313 | } 314 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Goal/QueryBuilder 2 | Goal 的数据库查询构造器为创建和运行数据库查询提供了一个方便的接口。它可以用于支持大部分数据库操作,并与 Goal 支持的所有数据库系统完美运行。并且大量参考了 `Laravel` 的查询构造器设计,你几乎可以在这个库找到所有与 `Laravel` 对应的方法。 3 | 4 | Goal 的查询构造器实现了类似 PDO 参数绑定的形式,来保护您的应用程序免受 SQL 注入攻击。因此不必清理因参数绑定而传入的字符串。查询构造器会返回你想要的 SQL 语句以及绑定参数。 5 | 6 | ## 运行数据库查询 7 | ### 根据条件从表中检索出数据 8 | 你可以使用 `NewQuery` 方法来开始查询。该方法为给定的表返回一个查询构造器实例,允许你在查询上链式调用更多的约束,最后使用 get 方法获取结果: 9 | ```golang 10 | package querybuilder 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | func TestSimpleQueryBuilder() { 16 | query := NewQuery("users"). 17 | Where("name", "qbhy"). 18 | Where("age", ">", 18). 19 | Where("gender", "!=", 0). 20 | OrWhere("amount", ">=", 100). 21 | WhereIsNull("avatar") 22 | 23 | fmt.Println(query.ToSql()) 24 | fmt.Println(query.GetBindings()) 25 | // select * from users where name = ? and age > ? and gender != ? and avatar is null or amount >= ? 26 | // [qbhy 18 0 100] 27 | } 28 | ``` 29 | > 你也可以通过 `SelectSql` 方法一次性获取你想要的参数。 30 | > 例如:sql, bindings := NewQuery("users").Where("gender", 1).SelectSql() 31 | 32 | ### 插入语句 33 | 你可以通过 `InsertSql` 或者 `CreateSql` 很方便的生成插入语句。 34 | ```golang 35 | package querybuilder 36 | 37 | import ( 38 | "fmt" 39 | "github.com/goal-web/contracts" 40 | ) 41 | 42 | // TestInsertSql 批量插入数据 43 | func TestInsertSql() { 44 | sql, bindings := NewQuery("users").InsertSql([]contracts.Fields{ 45 | {"name": "qbhy", "age": 18, "money": 100000000000}, 46 | {"name": "goal", "age": 18, "money": 10}, 47 | }) 48 | fmt.Println(sql) 49 | fmt.Println(bindings) 50 | // insert into users (name,age,money) values (?,?,?),(?,?,?) 51 | // [qbhy 18 100000000000 goal 18 10] 52 | } 53 | // TestCreateSql 插入单个数据 54 | func TestCreateSql() { 55 | sql, bindings := NewQuery("users").CreateSql(contracts.Fields{ 56 | "name": "qbhy", "age": 18, "money": 100000000000, 57 | }) 58 | fmt.Println(sql) 59 | fmt.Println(bindings) 60 | // insert into users (name,age,money) values (?,?,?) 61 | //[qbhy 18 100000000000] 62 | } 63 | ``` 64 | 65 | ### 更新语句 66 | 你可以通过 `UpdateSql` 很方便的生成更新语句。 67 | ```golang 68 | package querybuilder 69 | 70 | import ( 71 | "fmt" 72 | "github.com/goal-web/contracts" 73 | ) 74 | 75 | func TestUpdateSql() { 76 | sql, bindings := NewQuery("users").Where("id", ">", 1).UpdateSql(contracts.Fields{ 77 | "name": "qbhy", "age": 18, "money": 100000000000, 78 | }) 79 | fmt.Println(sql) 80 | fmt.Println(bindings) 81 | // update users set money = ?,name = ?,age = ? where id > ? 82 | // [qbhy 18 100000000000 1] 83 | } 84 | ``` 85 | 86 | ### 删除语句 87 | 你可以通过 `DeleteSql` 很方便的生成删除语句。 88 | ```golang 89 | package querybuilder 90 | 91 | import ( 92 | "fmt" 93 | ) 94 | 95 | func TestDeleteSql() { 96 | sql, bindings := NewQuery("users").Where("id", ">", 1).DeleteSql() 97 | fmt.Println(sql) 98 | fmt.Println(bindings) 99 | // delete from users where id > ? 100 | // [1] 101 | } 102 | ``` 103 | 104 | ## 更多高级用法 105 | 支持 where嵌套、子查询、连表、连子查询等更多高级用法 106 | ```go 107 | package tests 108 | 109 | import ( 110 | "fmt" 111 | "github.com/goal-web/contracts" 112 | builder "github.com/goal-web/querybuilder" 113 | "github.com/stretchr/testify/assert" 114 | "github.com/xwb1989/sqlparser" 115 | "testing" 116 | ) 117 | 118 | func TestSimpleQueryBuilder(t *testing.T) { 119 | query := builder.NewQuery("users") 120 | query.Where("name", "qbhy"). 121 | Where("age", ">", 18). 122 | Where("gender", "!=", 0, contracts.Or). 123 | OrWhere("amount", ">=", 100). 124 | WhereIsNull("avatar") 125 | fmt.Println(query.ToSql()) 126 | fmt.Println(query.GetBindings()) 127 | 128 | _, err := sqlparser.Parse(query.ToSql()) 129 | assert.Nil(t, err, err) 130 | } 131 | 132 | func TestJoinQueryBuilder(t *testing.T) { 133 | query := builder.NewQuery("users"). 134 | Join("accounts", "accounts.user_id", "=", "users.id"). 135 | JoinSub(func() contracts.QueryBuilder { 136 | return builder.NewQuery("users"). 137 | Where("level", ">", 5) 138 | }, "vip_users", "vip_users.id", "=", "users.id"). 139 | //WhereIn("gender", "1,2"). 140 | WhereIn("gender", []int{1, 2}) 141 | fmt.Println(query.ToSql()) 142 | fmt.Println(query.GetBindings()) 143 | _, err := sqlparser.Parse(query.ToSql()) 144 | assert.Nil(t, err, err) 145 | } 146 | 147 | func TestFromSubQueryBuilder(t *testing.T) { 148 | query := builder.FromSub(func() contracts.QueryBuilder { 149 | return builder.NewQuery("users"). 150 | Where("level", ">", 5) 151 | }, "vip_users"). 152 | //WhereIn("gender", "1,2"). 153 | WhereIn("gender", []int{1, 2}) 154 | fmt.Println(query.ToSql()) 155 | fmt.Println(query.GetBindings()) 156 | _, err := sqlparser.Parse(query.ToSql()) 157 | assert.Nil(t, err, err) 158 | } 159 | 160 | func TestDistinctQueryBuilder(t *testing.T) { 161 | query := builder.NewQuery("users"). 162 | Distinct(). 163 | Join("accounts", "accounts.user_id", "=", "users.id"). 164 | Where("gender", "!=", 0, contracts.Or) 165 | fmt.Println(query.ToSql()) 166 | fmt.Println(query.GetBindings()) 167 | _, err := sqlparser.Parse(query.ToSql()) 168 | assert.Nil(t, err, err) 169 | } 170 | 171 | func TestUpdateSql(t *testing.T) { 172 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).UpdateSql(contracts.Fields{ 173 | "name": "qbhy", "age": 18, "money": 100000000000, 174 | }) 175 | fmt.Println(sql) 176 | fmt.Println(bindings) 177 | _, err := sqlparser.Parse(sql) 178 | assert.Nil(t, err, err) 179 | } 180 | func TestSelectSub(t *testing.T) { 181 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1). 182 | SelectSub(func() contracts.QueryBuilder { 183 | return builder.NewQuery("accounts").Where("accounts.id", "users.id").WithCount() 184 | }, "accounts_count"). 185 | Join("accounts", "accounts.user_id", "=", "users.id"). 186 | SelectSql() 187 | fmt.Println(sql) 188 | fmt.Println(bindings) 189 | _, err := sqlparser.Parse(sql) 190 | assert.Nil(t, err, err) 191 | } 192 | func TestWhereNotExists(t *testing.T) { 193 | sql, bindings := builder.NewQuery("users"). 194 | Where("id", ">", 1). 195 | WhereNotExists(func() contracts.QueryBuilder { 196 | return builder.NewQuery("users").Select("id").Where("age", ">", 18) 197 | }). 198 | SelectSql() 199 | fmt.Println(sql) 200 | fmt.Println(bindings) 201 | _, err := sqlparser.Parse(sql) 202 | assert.Nil(t, err, err) 203 | } 204 | func TestCount(t *testing.T) { 205 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).WithCount("id").SelectSql() 206 | fmt.Println(sql) 207 | fmt.Println(bindings) 208 | _, err := sqlparser.Parse(sql) 209 | assert.Nil(t, err, err) 210 | } 211 | func TestDeleteSql(t *testing.T) { 212 | sql, bindings := builder.NewQuery("users").Where("id", ">", 1).DeleteSql() 213 | fmt.Println(sql) 214 | fmt.Println(bindings) 215 | _, err := sqlparser.Parse(sql) 216 | assert.Nil(t, err, err) 217 | } 218 | func TestInsertSql(t *testing.T) { 219 | sql, bindings := builder.NewQuery("users").InsertSql([]contracts.Fields{ 220 | {"name": "qbhy", "age": 18, "money": 100000000000}, 221 | {"name": "goal", "age": 18, "money": 10}, 222 | }) 223 | fmt.Println(sql) 224 | fmt.Println(bindings) 225 | _, err := sqlparser.Parse(sql) 226 | assert.Nil(t, err, err) 227 | } 228 | func TestInsertIgnoreSql(t *testing.T) { 229 | sql, bindings := builder.NewQuery("users").InsertIgnoreSql([]contracts.Fields{ 230 | {"name": "qbhy", "age": 18, "money": 100000000000}, 231 | {"name": "goal", "age": 18, "money": 10}, 232 | }) 233 | fmt.Println(sql) 234 | fmt.Println(bindings) 235 | _, err := sqlparser.Parse(sql) 236 | assert.Nil(t, err, err) 237 | } 238 | func TestInsertReplaceSql(t *testing.T) { 239 | sql, bindings := builder.NewQuery("users").InsertReplaceSql([]contracts.Fields{ 240 | {"name": "qbhy", "age": 18, "money": 100000000000}, 241 | {"name": "goal", "age": 18, "money": 10}, 242 | }) 243 | fmt.Println(sql) 244 | fmt.Println(bindings) 245 | _, err := sqlparser.Parse(sql) 246 | assert.Nil(t, err, err) 247 | } 248 | 249 | func TestCreateSql(t *testing.T) { 250 | sql, bindings := builder.NewQuery("users").CreateSql(contracts.Fields{ 251 | "name": "qbhy", "age": 18, "money": 100000000000, 252 | }) 253 | fmt.Println(sql) 254 | fmt.Println(bindings) 255 | _, err := sqlparser.Parse(sql) 256 | assert.Nil(t, err, err) 257 | } 258 | 259 | func TestBetweenQueryBuilder(t *testing.T) { 260 | query := builder.NewQuery("users"). 261 | Join("accounts", "accounts.user_id", "=", "users.id"). 262 | WhereFunc(func(b contracts.QueryBuilder) { 263 | // 高瘦 264 | b.WhereBetween("height", []int{180, 200}). 265 | WhereBetween("weight", []int{50, 60}). 266 | WhereIn("id", []int{1, 2, 3, 4, 5}) 267 | }).OrWhereFunc(func(b contracts.QueryBuilder) { 268 | // 矮胖 269 | b.WhereBetween("height", []int{140, 160}). 270 | WhereBetween("weight", []int{70, 140}). 271 | WhereNotBetween("id", []int{1, 5}) 272 | }) 273 | fmt.Println(query.ToSql()) 274 | fmt.Println(query.GetBindings()) 275 | _, err := sqlparser.Parse(query.ToSql()) 276 | assert.Nil(t, err, err) 277 | } 278 | 279 | func TestUnionQueryBuilder(t *testing.T) { 280 | query := builder.NewQuery("users"). 281 | Join("accounts", "accounts.user_id", "=", "users.id"). 282 | Where("gender", "!=", 0, contracts.Or). 283 | UnionByProvider( 284 | func() contracts.QueryBuilder { 285 | return builder.NewQuery("peoples").Where("id", 5) 286 | }, 287 | ). 288 | Union( 289 | builder.NewQuery("accounts"), 290 | ). 291 | UnionAll( 292 | builder.NewQuery("members"), 293 | ). 294 | UnionAll( 295 | builder.NewQuery("students"), 296 | ) 297 | fmt.Println(query.ToSql()) 298 | fmt.Println(query.GetBindings()) 299 | _, err := sqlparser.Parse(query.ToSql()) 300 | assert.Nil(t, err, err) 301 | } 302 | 303 | func TestComplexQueryBuilder(t *testing.T) { 304 | 305 | query := builder.NewQuery("users") 306 | query. 307 | FromSub(func() contracts.QueryBuilder { 308 | return builder.NewQuery("users").Where("amount", ">", 1000) 309 | }, "rich_users"). 310 | Join("accounts", "users.id", "=", "accounts.user_id"). 311 | WhereFunc(func(b contracts.QueryBuilder) { 312 | b.Where("name", "goal"). 313 | Where("age", "<", "18"). 314 | WhereIn("id", []int{1, 2}) 315 | }). 316 | OrWhereFunc(func(b contracts.QueryBuilder) { 317 | b.Where("name", "qbhy"). 318 | Where("age", ">", 18). 319 | WhereNotIn("id", []int{1, 2}) 320 | }). 321 | OrWhereNotIn("id", []int{6, 7}). 322 | OrWhereNotNull("id"). 323 | OrderByDesc("age"). 324 | OrderBy("id"). 325 | GroupBy("country") 326 | 327 | fmt.Println(query.ToSql()) 328 | fmt.Println(query.GetBindings()) 329 | _, err := sqlparser.Parse(query.ToSql()) 330 | assert.Nil(t, err, err) 331 | } 332 | 333 | func TestGroupByQueryBuilder(t *testing.T) { 334 | 335 | query := builder. 336 | FromSub(func() contracts.QueryBuilder { 337 | return builder.NewQuery("users").Where("amount", ">", 1000) 338 | }, "rich_users"). 339 | GroupBy("country"). 340 | Having("count(rich_users.id)", "<", 1000). // 人口少 341 | OrHaving("sum(rich_users.amount)", "<", 100) // 或者穷 342 | 343 | fmt.Println(query.ToSql()) 344 | fmt.Println(query.GetBindings()) 345 | _, err := sqlparser.Parse(query.ToSql()) 346 | assert.Nil(t, err, err) 347 | } 348 | ``` 349 | 正如开头所说,你可以在这里找到几乎所有与 `Laravel` 对应的查询构造器方法,也可以在 [测试文件](https://github.com/goal-web/querybuilder/blob/master/database_test.go) 中找到更多用法 350 | 351 | [goal/query-builder](https://github.com/goal-web/querybuilder) 352 | qbhy0715@qq.com 353 | --------------------------------------------------------------------------------