Skip to content

Commit

Permalink
Merge pull request #117 from huandu/feature-tuples
Browse files Browse the repository at this point in the history
Fix #108: Support tuple
  • Loading branch information
huandu committed Jul 22, 2023
2 parents 15280ec + 2023261 commit 18c5787
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ fmt.Println(args)
There are several modifiers for arguments.

- `List(arg)` represents a list of arguments. If `arg` is a slice or array, e.g. a slice with 3 ints, it will be compiled to `?, ?, ?` and flattened in the final arguments as 3 ints. It's a tool for convenience. We can use it in the `IN` expression or `VALUES` of `INSERT INTO`.
- `TupleNames(names)` and `Tuple(values)` represent the tuple syntax in SQL. See [Tuple](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#example-Tuple) for usage.
- `Named(name, arg)` represents a named argument. It only works with `Build` or `BuildNamed` to define a named placeholder using syntax `${name}`.
- `Raw(expr)` marks an `expr` as a plain string in SQL rather than an argument. When we build a builder, the value of raw expressions are copied in SQL string directly without leaving any `?` in SQL.

Expand Down
9 changes: 9 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ func (args *Args) compileArg(buf *stringBuilder, flavor Flavor, values []interfa
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])
}
Expand All @@ -230,6 +234,11 @@ func (args *Args) compileArg(buf *stringBuilder, flavor Flavor, values []interfa
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:
Expand Down
33 changes: 30 additions & 3 deletions modifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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++ {
Expand All @@ -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 {
Expand Down
45 changes: 45 additions & 0 deletions modifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package sqlbuilder

import (
"fmt"
"testing"

"github.com/huandu/go-assert"
Expand Down Expand Up @@ -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]
}

0 comments on commit 18c5787

Please sign in to comment.