Skip to content

Commit 9489225

Browse files
committed
refs #108: support tuples
1 parent 20d1fd8 commit 9489225

File tree

3 files changed

+84
-3
lines changed

3 files changed

+84
-3
lines changed

args.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ func (args *Args) compileArg(buf *strings.Builder, flavor Flavor, values []inter
222222
case rawArgs:
223223
buf.WriteString(a.expr)
224224
case listArgs:
225+
if a.isTuple {
226+
buf.WriteRune('(')
227+
}
228+
225229
if len(a.args) > 0 {
226230
values = args.compileArg(buf, flavor, values, a.args[0])
227231
}
@@ -230,6 +234,11 @@ func (args *Args) compileArg(buf *strings.Builder, flavor Flavor, values []inter
230234
buf.WriteString(", ")
231235
values = args.compileArg(buf, flavor, values, a.args[i])
232236
}
237+
238+
if a.isTuple {
239+
buf.WriteRune(')')
240+
}
241+
233242
default:
234243
switch flavor {
235244
case MySQL, SQLite, CQL, ClickHouse, Presto:

modifiers.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ func flatten(v reflect.Value) (elem interface{}, flattened []interface{}) {
4747
}
4848

4949
if k != reflect.Slice && k != reflect.Array {
50-
return v.Interface(), nil
50+
if !v.IsValid() || !v.CanInterface() {
51+
return
52+
}
53+
54+
elem = v.Interface()
55+
return elem, nil
5156
}
5257

5358
for i, l := 0, v.Len(); i < l; i++ {
@@ -73,13 +78,35 @@ func Raw(expr string) interface{} {
7378
}
7479

7580
type listArgs struct {
76-
args []interface{}
81+
args []interface{}
82+
isTuple bool
7783
}
7884

7985
// List marks arg as a list of data.
8086
// If arg is `[]int{1, 2, 3}`, it will be compiled to `?, ?, ?` with args `[1 2 3]`.
8187
func List(arg interface{}) interface{} {
82-
return listArgs{Flatten(arg)}
88+
return listArgs{
89+
args: Flatten(arg),
90+
}
91+
}
92+
93+
// Tuple wraps values into a tuple and can be used as a single value.
94+
func Tuple(values ...interface{}) interface{} {
95+
return listArgs{
96+
args: values,
97+
isTuple: true,
98+
}
99+
}
100+
101+
// TupleNames joins names with tuple format.
102+
// The names is not escaped. Use `EscapeAll` to escape them if necessary.
103+
func TupleNames(names ...string) string {
104+
buf := &strings.Builder{}
105+
buf.WriteRune('(')
106+
buf.WriteString(strings.Join(names, ", "))
107+
buf.WriteRune(')')
108+
109+
return buf.String()
83110
}
84111

85112
type namedArgs struct {

modifiers_test.go

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

66
import (
7+
"fmt"
78
"testing"
89

910
"github.com/huandu/go-assert"
@@ -54,3 +55,47 @@ func TestFlatten(t *testing.T) {
5455
a.Equal(actual, expected)
5556
}
5657
}
58+
59+
func TestTuple(t *testing.T) {
60+
a := assert.New(t)
61+
cases := []struct {
62+
values []interface{}
63+
expected string
64+
}{
65+
{
66+
nil,
67+
"()",
68+
},
69+
{
70+
[]interface{}{1, "bar", nil, Tuple("foo", Tuple(2, "baz"))},
71+
"(1, 'bar', NULL, ('foo', (2, 'baz')))",
72+
},
73+
}
74+
75+
for _, c := range cases {
76+
sql, args := Build("$?", Tuple(c.values...)).Build()
77+
actual, err := DefaultFlavor.Interpolate(sql, args)
78+
a.NilError(err)
79+
a.Equal(actual, c.expected)
80+
}
81+
}
82+
83+
func ExampleTuple() {
84+
sb := Select("id", "name").From("user")
85+
sb.Where(
86+
sb.In(
87+
TupleNames("type", "status"),
88+
Tuple("web", 1),
89+
Tuple("app", 1),
90+
Tuple("app", 2),
91+
),
92+
)
93+
sql, args := sb.Build()
94+
95+
fmt.Println(sql)
96+
fmt.Println(args)
97+
98+
// Output:
99+
// SELECT id, name FROM user WHERE (type, status) IN ((?, ?), (?, ?), (?, ?))
100+
// [web 1 app 1 app 2]
101+
}

0 commit comments

Comments
 (0)