Skip to content

Commit 20d1fd8

Browse files
authored
Merge pull request #102 from huandu/feature-more-cond
fix #100 Add EXISTS/NOT EXISTS/ANY/SOME/ALL in `Cond`
2 parents c25b4f2 + 95d77d1 commit 20d1fd8

File tree

12 files changed

+230
-40
lines changed

12 files changed

+230
-40
lines changed

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Usage](#usage)
1010
- [Basic usage](#basic-usage)
1111
- [Pre-defined SQL builders](#pre-defined-sql-builders)
12+
- [Build `WHERE` clause](#build-where-clause)
1213
- [Build SQL for different systems](#build-sql-for-different-systems)
1314
- [Using `Struct` as a light weight ORM](#using-struct-as-a-light-weight-orm)
1415
- [Nested SQL](#nested-sql)
@@ -110,6 +111,59 @@ Following are some utility methods to deal with special cases.
110111

111112
To learn how to use builders, check out [examples on GoDoc](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#pkg-examples).
112113

114+
### Build `WHERE` clause
115+
116+
`WHERE` clause is the most important part of a SQL. We can use `Where` method to add one or more conditions to a builder.
117+
118+
To make building `WHERE` clause easier, there is an utility type called `Cond` to build condition. All builders which support `WHERE` clause have an anonymous `Cond` field so that we can call methods implemented by `Cond` on these builders.
119+
120+
```go
121+
sb := sqlbuilder.Select("id").From("user")
122+
sb.Where(
123+
sb.In("status", 1, 2, 5),
124+
sb.Or(
125+
sb.Equal("name", "foo"),
126+
sb.Like("email", "foo@%"),
127+
),
128+
)
129+
130+
sql, args := sb.Build()
131+
fmt.Println(sql)
132+
fmt.Println(args)
133+
134+
// Output:
135+
// SELECT id FROM user WHERE status IN (?, ?, ?) AND (name = ? OR email LIKE ?)
136+
// [1 2 5 foo foo@%]
137+
```
138+
139+
There are many methods for building conditions.
140+
141+
- [Cond.Equal](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Equal)/[Cond.E](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.E): `field = value`.
142+
- [Cond.NotEqual](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NotEqual)/[Cond.NE](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NE): `field <> value`.
143+
- [Cond.GreaterThan](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.GreaterThan)/[Cond.G](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.G): `field > value`.
144+
- [Cond.GreaterEqualThan](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.GreaterEqualThan)/[Cond.GE](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.GE): `field >= value`.
145+
- [Cond.LessThan](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.LessThan)/[Cond.L](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.L): `field < value`.
146+
- [Cond.LessEqualThan](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.LessEqualThan)/[Cond.LE](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.LE): `field <= value`.
147+
- [Cond.In](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.In): `field IN (value1, value2, ...)`.
148+
- [Cond.NotIn](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NotIn): `field NOT IN (value1, value2, ...)`.
149+
- [Cond.Like](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Like): `field LIKE value`.
150+
- [Cond.NotLike](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NotLike): `field NOT LIKE value`.
151+
- [Cond.Between](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Between): `field BETWEEN lower AND upper`.
152+
- [Cond.NotBetween](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NotBetween): `field NOT BETWEEN lower AND upper`.
153+
- [Cond.IsNull](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.IsNull): `field IS NULL`.
154+
- [Cond.IsNotNull](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.IsNotNull): `field IS NOT NULL`.
155+
- [Cond.Exists](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Exists): `EXISTS (subquery)`.
156+
- [Cond.NotExists](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.NotExists): `NOT EXISTS (subquery)`.
157+
- [Cond.Any](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Any): `field op ANY (value1, value2, ...)`.
158+
- [Cond.All](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.All): `field op ALL (value1, value2, ...)`.
159+
- [Cond.Some](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Some): `field op SOME (value1, value2, ...)`.
160+
- [Cond.Var](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Var): A placeholder for any value.
161+
162+
There are also some methods to combine conditions.
163+
164+
- [Cond.And](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.And): Combine conditions with `AND` operator.
165+
- [Cond.Or](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Cond.Or): Combine conditions with `OR` operator.
166+
113167
### Build SQL for different systems
114168

115169
SQL syntax and parameter marks vary in different systems. In this package, we introduce a concept called "flavor" to smooth out these difference.

args.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package sqlbuilder
55

66
import (
7-
"bytes"
87
"database/sql"
98
"fmt"
109
"sort"
@@ -98,7 +97,7 @@ func (args *Args) Compile(format string, initialValue ...interface{}) (query str
9897
//
9998
// See doc for `Compile` to learn details.
10099
func (args *Args) CompileWithFlavor(format string, flavor Flavor, initialValue ...interface{}) (query string, values []interface{}) {
101-
buf := &bytes.Buffer{}
100+
buf := &strings.Builder{}
102101
idx := strings.IndexRune(format, '$')
103102
offset := 0
104103
values = initialValue
@@ -161,7 +160,7 @@ func (args *Args) CompileWithFlavor(format string, flavor Flavor, initialValue .
161160
return
162161
}
163162

164-
func (args *Args) compileNamed(buf *bytes.Buffer, flavor Flavor, format string, values []interface{}) (string, []interface{}) {
163+
func (args *Args) compileNamed(buf *strings.Builder, flavor Flavor, format string, values []interface{}) (string, []interface{}) {
165164
i := 1
166165

167166
for ; i < len(format) && format[i] != '}'; i++ {
@@ -183,7 +182,7 @@ func (args *Args) compileNamed(buf *bytes.Buffer, flavor Flavor, format string,
183182
return format, values
184183
}
185184

186-
func (args *Args) compileDigits(buf *bytes.Buffer, flavor Flavor, format string, values []interface{}, offset int) (string, []interface{}, int) {
185+
func (args *Args) compileDigits(buf *strings.Builder, flavor Flavor, format string, values []interface{}, offset int) (string, []interface{}, int) {
187186
i := 1
188187

189188
for ; i < len(format) && '0' <= format[i] && format[i] <= '9'; i++ {
@@ -200,7 +199,7 @@ func (args *Args) compileDigits(buf *bytes.Buffer, flavor Flavor, format string,
200199
return format, values, offset
201200
}
202201

203-
func (args *Args) compileSuccessive(buf *bytes.Buffer, flavor Flavor, format string, values []interface{}, offset int) (string, []interface{}, int) {
202+
func (args *Args) compileSuccessive(buf *strings.Builder, flavor Flavor, format string, values []interface{}, offset int) (string, []interface{}, int) {
204203
if offset >= len(args.args) {
205204
return format, values, offset
206205
}
@@ -211,7 +210,7 @@ func (args *Args) compileSuccessive(buf *bytes.Buffer, flavor Flavor, format str
211210
return format, values, offset + 1
212211
}
213212

214-
func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interface{}, arg interface{}) []interface{} {
213+
func (args *Args) compileArg(buf *strings.Builder, flavor Flavor, values []interface{}, arg interface{}) []interface{} {
215214
switch a := arg.(type) {
216215
case Builder:
217216
var s string

cond.go

Lines changed: 157 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package sqlbuilder
55

66
import (
7-
"fmt"
87
"strings"
98
)
109

@@ -15,17 +14,25 @@ type Cond struct {
1514

1615
// Equal represents "field = value".
1716
func (c *Cond) Equal(field string, value interface{}) string {
18-
return fmt.Sprintf("%s = %s", Escape(field), c.Args.Add(value))
17+
buf := &strings.Builder{}
18+
buf.WriteString(Escape(field))
19+
buf.WriteString(" = ")
20+
buf.WriteString(c.Args.Add(value))
21+
return buf.String()
1922
}
2023

2124
// E is an alias of Equal.
2225
func (c *Cond) E(field string, value interface{}) string {
2326
return c.Equal(field, value)
2427
}
2528

26-
// NotEqual represents "field != value".
29+
// NotEqual represents "field <> value".
2730
func (c *Cond) NotEqual(field string, value interface{}) string {
28-
return fmt.Sprintf("%s <> %s", Escape(field), c.Args.Add(value))
31+
buf := &strings.Builder{}
32+
buf.WriteString(Escape(field))
33+
buf.WriteString(" <> ")
34+
buf.WriteString(c.Args.Add(value))
35+
return buf.String()
2936
}
3037

3138
// NE is an alias of NotEqual.
@@ -35,7 +42,11 @@ func (c *Cond) NE(field string, value interface{}) string {
3542

3643
// GreaterThan represents "field > value".
3744
func (c *Cond) GreaterThan(field string, value interface{}) string {
38-
return fmt.Sprintf("%s > %s", Escape(field), c.Args.Add(value))
45+
buf := &strings.Builder{}
46+
buf.WriteString(Escape(field))
47+
buf.WriteString(" > ")
48+
buf.WriteString(c.Args.Add(value))
49+
return buf.String()
3950
}
4051

4152
// G is an alias of GreaterThan.
@@ -45,7 +56,11 @@ func (c *Cond) G(field string, value interface{}) string {
4556

4657
// GreaterEqualThan represents "field >= value".
4758
func (c *Cond) GreaterEqualThan(field string, value interface{}) string {
48-
return fmt.Sprintf("%s >= %s", Escape(field), c.Args.Add(value))
59+
buf := &strings.Builder{}
60+
buf.WriteString(Escape(field))
61+
buf.WriteString(" >= ")
62+
buf.WriteString(c.Args.Add(value))
63+
return buf.String()
4964
}
5065

5166
// GE is an alias of GreaterEqualThan.
@@ -55,7 +70,11 @@ func (c *Cond) GE(field string, value interface{}) string {
5570

5671
// LessThan represents "field < value".
5772
func (c *Cond) LessThan(field string, value interface{}) string {
58-
return fmt.Sprintf("%s < %s", Escape(field), c.Args.Add(value))
73+
buf := &strings.Builder{}
74+
buf.WriteString(Escape(field))
75+
buf.WriteString(" < ")
76+
buf.WriteString(c.Args.Add(value))
77+
return buf.String()
5978
}
6079

6180
// L is an alias of LessThan.
@@ -65,7 +84,11 @@ func (c *Cond) L(field string, value interface{}) string {
6584

6685
// LessEqualThan represents "field <= value".
6786
func (c *Cond) LessEqualThan(field string, value interface{}) string {
68-
return fmt.Sprintf("%s <= %s", Escape(field), c.Args.Add(value))
87+
buf := &strings.Builder{}
88+
buf.WriteString(Escape(field))
89+
buf.WriteString(" <= ")
90+
buf.WriteString(c.Args.Add(value))
91+
return buf.String()
6992
}
7093

7194
// LE is an alias of LessEqualThan.
@@ -81,7 +104,12 @@ func (c *Cond) In(field string, value ...interface{}) string {
81104
vs = append(vs, c.Args.Add(v))
82105
}
83106

84-
return fmt.Sprintf("%s IN (%s)", Escape(field), strings.Join(vs, ", "))
107+
buf := &strings.Builder{}
108+
buf.WriteString(Escape(field))
109+
buf.WriteString(" IN (")
110+
buf.WriteString(strings.Join(vs, ", "))
111+
buf.WriteString(")")
112+
return buf.String()
85113
}
86114

87115
// NotIn represents "field NOT IN (value...)".
@@ -92,47 +120,158 @@ func (c *Cond) NotIn(field string, value ...interface{}) string {
92120
vs = append(vs, c.Args.Add(v))
93121
}
94122

95-
return fmt.Sprintf("%s NOT IN (%s)", Escape(field), strings.Join(vs, ", "))
123+
buf := &strings.Builder{}
124+
buf.WriteString(Escape(field))
125+
buf.WriteString(" NOT IN (")
126+
buf.WriteString(strings.Join(vs, ", "))
127+
buf.WriteString(")")
128+
return buf.String()
96129
}
97130

98131
// Like represents "field LIKE value".
99132
func (c *Cond) Like(field string, value interface{}) string {
100-
return fmt.Sprintf("%s LIKE %s", Escape(field), c.Args.Add(value))
133+
buf := &strings.Builder{}
134+
buf.WriteString(Escape(field))
135+
buf.WriteString(" LIKE ")
136+
buf.WriteString(c.Args.Add(value))
137+
return buf.String()
101138
}
102139

103140
// NotLike represents "field NOT LIKE value".
104141
func (c *Cond) NotLike(field string, value interface{}) string {
105-
return fmt.Sprintf("%s NOT LIKE %s", Escape(field), c.Args.Add(value))
142+
buf := &strings.Builder{}
143+
buf.WriteString(Escape(field))
144+
buf.WriteString(" NOT LIKE ")
145+
buf.WriteString(c.Args.Add(value))
146+
return buf.String()
106147
}
107148

108149
// IsNull represents "field IS NULL".
109150
func (c *Cond) IsNull(field string) string {
110-
return fmt.Sprintf("%s IS NULL", Escape(field))
151+
buf := &strings.Builder{}
152+
buf.WriteString(Escape(field))
153+
buf.WriteString(" IS NULL")
154+
return buf.String()
111155
}
112156

113157
// IsNotNull represents "field IS NOT NULL".
114158
func (c *Cond) IsNotNull(field string) string {
115-
return fmt.Sprintf("%s IS NOT NULL", Escape(field))
159+
buf := &strings.Builder{}
160+
buf.WriteString(Escape(field))
161+
buf.WriteString(" IS NOT NULL")
162+
return buf.String()
116163
}
117164

118165
// Between represents "field BETWEEN lower AND upper".
119166
func (c *Cond) Between(field string, lower, upper interface{}) string {
120-
return fmt.Sprintf("%s BETWEEN %s AND %s", Escape(field), c.Args.Add(lower), c.Args.Add(upper))
167+
buf := &strings.Builder{}
168+
buf.WriteString(Escape(field))
169+
buf.WriteString(" BETWEEN ")
170+
buf.WriteString(c.Args.Add(lower))
171+
buf.WriteString(" AND ")
172+
buf.WriteString(c.Args.Add(upper))
173+
return buf.String()
121174
}
122175

123176
// NotBetween represents "field NOT BETWEEN lower AND upper".
124177
func (c *Cond) NotBetween(field string, lower, upper interface{}) string {
125-
return fmt.Sprintf("%s NOT BETWEEN %s AND %s", Escape(field), c.Args.Add(lower), c.Args.Add(upper))
178+
buf := &strings.Builder{}
179+
buf.WriteString(Escape(field))
180+
buf.WriteString(" NOT BETWEEN ")
181+
buf.WriteString(c.Args.Add(lower))
182+
buf.WriteString(" AND ")
183+
buf.WriteString(c.Args.Add(upper))
184+
return buf.String()
126185
}
127186

128187
// Or represents OR logic like "expr1 OR expr2 OR expr3".
129188
func (c *Cond) Or(orExpr ...string) string {
130-
return fmt.Sprintf("(%s)", strings.Join(orExpr, " OR "))
189+
buf := &strings.Builder{}
190+
buf.WriteString("(")
191+
buf.WriteString(strings.Join(orExpr, " OR "))
192+
buf.WriteString(")")
193+
return buf.String()
131194
}
132195

133196
// And represents AND logic like "expr1 AND expr2 AND expr3".
134197
func (c *Cond) And(andExpr ...string) string {
135-
return fmt.Sprintf("(%s)", strings.Join(andExpr, " AND "))
198+
buf := &strings.Builder{}
199+
buf.WriteString("(")
200+
buf.WriteString(strings.Join(andExpr, " AND "))
201+
buf.WriteString(")")
202+
return buf.String()
203+
}
204+
205+
// Exists represents "EXISTS (subquery)".
206+
func (c *Cond) Exists(subquery interface{}) string {
207+
buf := &strings.Builder{}
208+
buf.WriteString("EXISTS (")
209+
buf.WriteString(c.Args.Add(subquery))
210+
buf.WriteString(")")
211+
return buf.String()
212+
}
213+
214+
// NotExists represents "NOT EXISTS (subquery)".
215+
func (c *Cond) NotExists(subquery interface{}) string {
216+
buf := &strings.Builder{}
217+
buf.WriteString("NOT EXISTS (")
218+
buf.WriteString(c.Args.Add(subquery))
219+
buf.WriteString(")")
220+
return buf.String()
221+
}
222+
223+
// Any represents "field op ANY (value...)".
224+
func (c *Cond) Any(field, op string, value ...interface{}) string {
225+
vs := make([]string, 0, len(value))
226+
227+
for _, v := range value {
228+
vs = append(vs, c.Args.Add(v))
229+
}
230+
231+
buf := &strings.Builder{}
232+
buf.WriteString(Escape(field))
233+
buf.WriteString(" ")
234+
buf.WriteString(op)
235+
buf.WriteString(" ANY (")
236+
buf.WriteString(strings.Join(vs, ", "))
237+
buf.WriteString(")")
238+
return buf.String()
239+
}
240+
241+
// All represents "field op ALL (value...)".
242+
func (c *Cond) All(field, op string, value ...interface{}) string {
243+
vs := make([]string, 0, len(value))
244+
245+
for _, v := range value {
246+
vs = append(vs, c.Args.Add(v))
247+
}
248+
249+
buf := &strings.Builder{}
250+
buf.WriteString(Escape(field))
251+
buf.WriteString(" ")
252+
buf.WriteString(op)
253+
buf.WriteString(" ALL (")
254+
buf.WriteString(strings.Join(vs, ", "))
255+
buf.WriteString(")")
256+
return buf.String()
257+
}
258+
259+
// Some represents "field op SOME (value...)".
260+
func (c *Cond) Some(field, op string, value ...interface{}) string {
261+
vs := make([]string, 0, len(value))
262+
263+
for _, v := range value {
264+
vs = append(vs, c.Args.Add(v))
265+
}
266+
267+
buf := &strings.Builder{}
268+
buf.WriteString(Escape(field))
269+
buf.WriteString(" ")
270+
buf.WriteString(op)
271+
buf.WriteString(" SOME (")
272+
buf.WriteString(strings.Join(vs, ", "))
273+
buf.WriteString(")")
274+
return buf.String()
136275
}
137276

138277
// Var returns a placeholder for value.

0 commit comments

Comments
 (0)