refs #108: support tuples

This commit is contained in:
Huan Du
2023-07-22 22:51:03 +08:00
parent cb38c02175
commit 218c717c01
3 changed files with 84 additions and 3 deletions
+9
View File
@@ -222,6 +222,10 @@ func (args *Args) compileArg(buf *strings.Builder, flavor Flavor, values []inter
case rawArgs:
buf.WriteString(a.expr)
case listArgs:
if a.isTuple {
buf.WriteRune('(')
}
if len(a.args) > 0 {
values = args.compileArg(buf, flavor, values, a.args[0])
}
@@ -230,6 +234,11 @@ func (args *Args) compileArg(buf *strings.Builder, flavor Flavor, values []inter
buf.WriteString(", ")
values = args.compileArg(buf, flavor, values, a.args[i])
}
if a.isTuple {
buf.WriteRune(')')
}
default:
switch flavor {
case MySQL, SQLite, CQL, ClickHouse, Presto:
+30 -3
View File
@@ -47,7 +47,12 @@ func flatten(v reflect.Value) (elem interface{}, flattened []interface{}) {
}
if k != reflect.Slice && k != reflect.Array {
return v.Interface(), nil
if !v.IsValid() || !v.CanInterface() {
return
}
elem = v.Interface()
return elem, nil
}
for i, l := 0, v.Len(); i < l; i++ {
@@ -73,13 +78,35 @@ func Raw(expr string) interface{} {
}
type listArgs struct {
args []interface{}
args []interface{}
isTuple bool
}
// List marks arg as a list of data.
// If arg is `[]int{1, 2, 3}`, it will be compiled to `?, ?, ?` with args `[1 2 3]`.
func List(arg interface{}) interface{} {
return listArgs{Flatten(arg)}
return listArgs{
args: Flatten(arg),
}
}
// Tuple wraps values into a tuple and can be used as a single value.
func Tuple(values ...interface{}) interface{} {
return listArgs{
args: values,
isTuple: true,
}
}
// TupleNames joins names with tuple format.
// The names is not escaped. Use `EscapeAll` to escape them if necessary.
func TupleNames(names ...string) string {
buf := &strings.Builder{}
buf.WriteRune('(')
buf.WriteString(strings.Join(names, ", "))
buf.WriteRune(')')
return buf.String()
}
type namedArgs struct {
+45
View File
@@ -4,6 +4,7 @@
package sqlbuilder
import (
"fmt"
"testing"
"github.com/huandu/go-assert"
@@ -54,3 +55,47 @@ func TestFlatten(t *testing.T) {
a.Equal(actual, expected)
}
}
func TestTuple(t *testing.T) {
a := assert.New(t)
cases := []struct {
values []interface{}
expected string
}{
{
nil,
"()",
},
{
[]interface{}{1, "bar", nil, Tuple("foo", Tuple(2, "baz"))},
"(1, 'bar', NULL, ('foo', (2, 'baz')))",
},
}
for _, c := range cases {
sql, args := Build("$?", Tuple(c.values...)).Build()
actual, err := DefaultFlavor.Interpolate(sql, args)
a.NilError(err)
a.Equal(actual, c.expected)
}
}
func ExampleTuple() {
sb := Select("id", "name").From("user")
sb.Where(
sb.In(
TupleNames("type", "status"),
Tuple("web", 1),
Tuple("app", 1),
Tuple("app", 2),
),
)
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// SELECT id, name FROM user WHERE (type, status) IN ((?, ?), (?, ?), (?, ?))
// [web 1 app 1 app 2]
}