mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
Merge remote-tracking branch 'upstream/master'
# Conflicts: # README.md # args.go # flavor.go # insert_test.go # interpolate.go # interpolate_test.go # select_test.go # struct.go # struct_test.go
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
- [Usage](#usage)
|
||||
- [Basic usage](#basic-usage)
|
||||
- [Pre-defined SQL builders](#pre-defined-sql-builders)
|
||||
- [Build SQL for MySQL, PostgreSQL, SQLServer or SQLite](#build-sql-for-mysql-postgresql-sqlserver-or-sqlite)
|
||||
- [Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, CQL, or ClickHouse](#build-sql-for-mysql-postgresql-sqlserve-sqlite-or-clickhouse)
|
||||
- [Using `Struct` as a light weight ORM](#using-struct-as-a-light-weight-orm)
|
||||
- [Nested SQL](#nested-sql)
|
||||
- [Use `sql.Named` in a builder](#use-sqlnamed-in-a-builder)
|
||||
@@ -110,7 +110,7 @@ Following are some utility methods to deal with special cases.
|
||||
|
||||
To learn how to use builders, check out [examples on GoDoc](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#pkg-examples).
|
||||
|
||||
### Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, or CQL
|
||||
### Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, CQL, or ClickHouse
|
||||
|
||||
Parameter markers are different in MySQL, PostgreSQL, SQLServer and SQLite. This package provides some methods to set the type of markers (we call it "flavor") in all builders.
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
|
||||
}
|
||||
default:
|
||||
switch flavor {
|
||||
case MySQL, SQLite, CQL:
|
||||
case MySQL, SQLite, CQL, ClickHouse:
|
||||
buf.WriteRune('?')
|
||||
case PostgreSQL:
|
||||
fmt.Fprintf(buf, "$%d", len(values)+1)
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
SQLite
|
||||
SQLServer
|
||||
CQL
|
||||
ClickHouse
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -52,6 +53,8 @@ func (f Flavor) String() string {
|
||||
return "SQLServer"
|
||||
case CQL:
|
||||
return "CQL"
|
||||
case ClickHouse:
|
||||
return "ClickHouse"
|
||||
}
|
||||
|
||||
return "<invalid>"
|
||||
@@ -74,6 +77,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
|
||||
return sqlserverInterpolate(sql, args...)
|
||||
case CQL:
|
||||
return cqlInterpolate(sql, args...)
|
||||
case ClickHouse:
|
||||
return clickhouseInterpolate(sql, args...)
|
||||
}
|
||||
|
||||
return "", ErrInterpolateNotImplemented
|
||||
@@ -124,11 +129,11 @@ func (f Flavor) NewUnionBuilder() *UnionBuilder {
|
||||
// Quote adds quote for name to make sure the name can be used safely
|
||||
// as table name or field name.
|
||||
//
|
||||
// * For MySQL, use back quote (`) to quote name;
|
||||
// * For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
|
||||
// - For MySQL, use back quote (`) to quote name;
|
||||
// - For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
|
||||
func (f Flavor) Quote(name string) string {
|
||||
switch f {
|
||||
case MySQL:
|
||||
case MySQL, ClickHouse:
|
||||
return fmt.Sprintf("`%s`", name)
|
||||
case PostgreSQL, SQLServer, SQLite:
|
||||
return fmt.Sprintf(`"%s"`, name)
|
||||
@@ -153,6 +158,9 @@ func (f Flavor) PrepareInsertIgnore(table string, ib *InsertBuilder) {
|
||||
case SQLite:
|
||||
// see https://www.sqlite.org/lang_insert.html
|
||||
ib.verb = "INSERT OR IGNORE"
|
||||
case ClickHouse:
|
||||
// see https://clickhouse.tech/docs/en/sql-reference/statements/insert-into/
|
||||
ib.verb = "INSERT"
|
||||
default:
|
||||
// panic if the db flavor is not supported
|
||||
panic(fmt.Errorf("unsupported db flavor: %s", ib.args.Flavor.String()))
|
||||
|
||||
@@ -113,6 +113,22 @@ func ExampleInsertBuilder_insertIgnore_sqlite() {
|
||||
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
|
||||
}
|
||||
|
||||
func ExampleInsertBuilder_insertIgnore_clickhouse() {
|
||||
ib := ClickHouse.NewInsertBuilder()
|
||||
ib.InsertIgnoreInto("demo.user")
|
||||
ib.Cols("id", "name", "status", "created_at")
|
||||
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
|
||||
ib.Values(2, "Charmy Liu", 1, 1234567890)
|
||||
|
||||
sql, args := ib.Build()
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// INSERT INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
|
||||
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
|
||||
}
|
||||
|
||||
func ExampleInsertBuilder_replaceInto() {
|
||||
ib := NewInsertBuilder()
|
||||
ib.ReplaceInto("demo.user")
|
||||
|
||||
+115
-70
@@ -4,7 +4,9 @@
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -389,83 +391,22 @@ func cqlInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return mysqlLikeInterpolate(CQL, query, args...)
|
||||
}
|
||||
|
||||
func clickhouseInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return mysqlLikeInterpolate(ClickHouse, query, args...)
|
||||
}
|
||||
|
||||
func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
switch v := arg.(type) {
|
||||
case nil:
|
||||
buf = append(buf, "NULL"...)
|
||||
|
||||
case bool:
|
||||
if v {
|
||||
buf = append(buf, "TRUE"...)
|
||||
case driver.Valuer:
|
||||
if val, err := v.Value(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = append(buf, "FALSE"...)
|
||||
return encodeValue(buf, val, flavor)
|
||||
}
|
||||
|
||||
case int:
|
||||
buf = strconv.AppendInt(buf, int64(v), 10)
|
||||
|
||||
case int8:
|
||||
buf = strconv.AppendInt(buf, int64(v), 10)
|
||||
|
||||
case int16:
|
||||
buf = strconv.AppendInt(buf, int64(v), 10)
|
||||
|
||||
case int32:
|
||||
buf = strconv.AppendInt(buf, int64(v), 10)
|
||||
|
||||
case int64:
|
||||
buf = strconv.AppendInt(buf, v, 10)
|
||||
|
||||
case uint:
|
||||
buf = strconv.AppendUint(buf, uint64(v), 10)
|
||||
|
||||
case uint8:
|
||||
buf = strconv.AppendUint(buf, uint64(v), 10)
|
||||
|
||||
case uint16:
|
||||
buf = strconv.AppendUint(buf, uint64(v), 10)
|
||||
|
||||
case uint32:
|
||||
buf = strconv.AppendUint(buf, uint64(v), 10)
|
||||
|
||||
case uint64:
|
||||
buf = strconv.AppendUint(buf, v, 10)
|
||||
|
||||
case float32:
|
||||
buf = strconv.AppendFloat(buf, float64(v), 'g', -1, 32)
|
||||
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||
|
||||
case []byte:
|
||||
if v == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
break
|
||||
}
|
||||
|
||||
switch flavor {
|
||||
case MySQL:
|
||||
buf = append(buf, "_binary"...)
|
||||
buf = quoteStringValue(buf, *(*string)(unsafe.Pointer(&v)), flavor)
|
||||
|
||||
case PostgreSQL:
|
||||
buf = append(buf, "E'\\\\x"...)
|
||||
buf = appendHex(buf, v)
|
||||
buf = append(buf, "'::bytea"...)
|
||||
|
||||
case SQLite:
|
||||
buf = append(buf, "X'"...)
|
||||
buf = appendHex(buf, v)
|
||||
buf = append(buf, '\'')
|
||||
|
||||
case SQLServer, CQL:
|
||||
buf = append(buf, "0x"...)
|
||||
buf = appendHex(buf, v)
|
||||
}
|
||||
|
||||
case string:
|
||||
buf = quoteStringValue(buf, v, flavor)
|
||||
|
||||
case time.Time:
|
||||
if v.IsZero() {
|
||||
buf = append(buf, "'0000-00-00'"...)
|
||||
@@ -492,6 +433,9 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
|
||||
case CQL:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.999999Z0700")...)
|
||||
|
||||
case ClickHouse:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
|
||||
}
|
||||
|
||||
buf = append(buf, '\'')
|
||||
@@ -500,7 +444,108 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
buf = quoteStringValue(buf, v.String(), flavor)
|
||||
|
||||
default:
|
||||
return nil, ErrInterpolateUnsupportedArgs
|
||||
primative := reflect.ValueOf(arg)
|
||||
|
||||
switch k := primative.Kind(); k {
|
||||
case reflect.Bool:
|
||||
if primative.Bool() {
|
||||
buf = append(buf, "TRUE"...)
|
||||
} else {
|
||||
buf = append(buf, "FALSE"...)
|
||||
}
|
||||
|
||||
case reflect.Int:
|
||||
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
||||
|
||||
case reflect.Int8:
|
||||
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
||||
|
||||
case reflect.Int16:
|
||||
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
||||
|
||||
case reflect.Int32:
|
||||
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
||||
|
||||
case reflect.Int64:
|
||||
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
||||
|
||||
case reflect.Uint:
|
||||
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
||||
|
||||
case reflect.Uint8:
|
||||
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
||||
|
||||
case reflect.Uint16:
|
||||
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
||||
|
||||
case reflect.Uint32:
|
||||
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
||||
|
||||
case reflect.Uint64:
|
||||
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 32)
|
||||
|
||||
case reflect.Float64:
|
||||
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 64)
|
||||
|
||||
case reflect.String:
|
||||
buf = quoteStringValue(buf, primative.String(), flavor)
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
if k == reflect.Slice && primative.IsNil() {
|
||||
buf = append(buf, "NULL"...)
|
||||
break
|
||||
}
|
||||
|
||||
if elem := primative.Type().Elem(); elem.Kind() != reflect.Uint8 {
|
||||
return nil, ErrInterpolateUnsupportedArgs
|
||||
}
|
||||
|
||||
var data []byte
|
||||
|
||||
// Bytes() will panic if primative is an array and cannot be addressed.
|
||||
// Copy all bytes to data as a fallback.
|
||||
if k == reflect.Array && !primative.CanAddr() {
|
||||
l := primative.Len()
|
||||
data = make([]byte, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
data[i] = byte(primative.Index(i).Uint())
|
||||
}
|
||||
} else {
|
||||
data = primative.Bytes()
|
||||
}
|
||||
|
||||
switch flavor {
|
||||
case MySQL:
|
||||
buf = append(buf, "_binary"...)
|
||||
buf = quoteStringValue(buf, *(*string)(unsafe.Pointer(&data)), flavor)
|
||||
|
||||
case PostgreSQL:
|
||||
buf = append(buf, "E'\\\\x"...)
|
||||
buf = appendHex(buf, data)
|
||||
buf = append(buf, "'::bytea"...)
|
||||
|
||||
case SQLite:
|
||||
buf = append(buf, "X'"...)
|
||||
buf = appendHex(buf, data)
|
||||
buf = append(buf, '\'')
|
||||
|
||||
case SQLServer, CQL:
|
||||
buf = append(buf, "0x"...)
|
||||
buf = appendHex(buf, data)
|
||||
|
||||
case ClickHouse:
|
||||
buf = append(buf, "unhex('"...)
|
||||
buf = appendHex(buf, data)
|
||||
buf = append(buf, "')"...)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrInterpolateUnsupportedArgs
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
|
||||
+80
-10
@@ -1,6 +1,8 @@
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
@@ -9,15 +11,24 @@ import (
|
||||
"github.com/huandu/go-assert"
|
||||
)
|
||||
|
||||
type errorValuer int
|
||||
|
||||
var ErrErrorValuer = errors.New("error valuer")
|
||||
|
||||
func (v errorValuer) Value() (driver.Value, error) {
|
||||
return 0, ErrErrorValuer
|
||||
}
|
||||
|
||||
func TestFlavorInterpolate(t *testing.T) {
|
||||
dt := time.Date(2019, 4, 24, 12, 23, 34, 123456789, time.FixedZone("CST", 8*60*60)) // 2019-04-24 12:23:34.987654321 CST
|
||||
_, errOutOfRange := strconv.ParseInt("12345678901234567890", 10, 32)
|
||||
byteArr := [...]byte{'f', 'o', 'o'}
|
||||
cases := []struct {
|
||||
flavor Flavor
|
||||
sql string
|
||||
args []interface{}
|
||||
query string
|
||||
err error
|
||||
Flavor Flavor
|
||||
SQL string
|
||||
Args []interface{}
|
||||
Query string
|
||||
Err error
|
||||
}{
|
||||
{
|
||||
MySQL,
|
||||
@@ -39,6 +50,11 @@ func TestFlavorInterpolate(t *testing.T) {
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{MySQL},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'MySQL'", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", []interface{}{byteArr},
|
||||
"SELECT _binary'foo'", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", nil,
|
||||
@@ -49,6 +65,16 @@ func TestFlavorInterpolate(t *testing.T) {
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", []interface{}{[]complex128{complex(1, 2)}},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", []interface{}{errorValuer(1)},
|
||||
"", ErrErrorValuer,
|
||||
},
|
||||
|
||||
{
|
||||
PostgreSQL,
|
||||
@@ -137,7 +163,6 @@ func TestFlavorInterpolate(t *testing.T) {
|
||||
"SELECT @p1", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
|
||||
{
|
||||
CQL,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
@@ -168,16 +193,61 @@ func TestFlavorInterpolate(t *testing.T) {
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN (?, '?', ?, ?, ?, ?, ?)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), 9.87654321, []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, unhex('49276D206279746573'), '2019-04-24 12:23:34.123457', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{MySQL},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'MySQL'", nil,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?", []interface{}{byteArr},
|
||||
"SELECT unhex('666F6F')", nil,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?", []interface{}{[]complex128{complex(1, 2)}},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
ClickHouse,
|
||||
"SELECT ?", []interface{}{errorValuer(1)},
|
||||
"", ErrErrorValuer,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
t.Run(fmt.Sprintf("%s: %s", c.flavor.String(), c.query), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%s: %s", c.Flavor.String(), c.Query), func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
a.Use(&idx, &c)
|
||||
query, err := c.flavor.Interpolate(c.sql, c.args)
|
||||
query, err := c.Flavor.Interpolate(c.SQL, c.Args)
|
||||
|
||||
a.Equal(query, c.query)
|
||||
a.Assert(err == c.err || err.Error() == c.err.Error())
|
||||
a.Equal(query, c.Query)
|
||||
a.Assert(err == c.Err || err.Error() == c.Err.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
|
||||
}
|
||||
|
||||
switch flavor {
|
||||
case MySQL, SQLite:
|
||||
case MySQL, SQLite, ClickHouse:
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
|
||||
+7
-1
@@ -112,7 +112,7 @@ func ExampleSelectBuilder_join() {
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_limit_offset() {
|
||||
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL}
|
||||
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL, ClickHouse}
|
||||
results := make([][]string, len(flavors))
|
||||
sb := NewSelectBuilder()
|
||||
saveResults := func() {
|
||||
@@ -197,6 +197,12 @@ func ExampleSelectBuilder_limit_offset() {
|
||||
// #2: SELECT * FROM user
|
||||
// #3: SELECT * FROM user LIMIT 1
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
//
|
||||
// ClickHouse
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user
|
||||
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_ForUpdate() {
|
||||
|
||||
@@ -5,6 +5,7 @@ package sqlbuilder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -36,6 +37,8 @@ const (
|
||||
|
||||
var optRegex = regexp.MustCompile(`(?P<` + optName + `>\w+)(\((?P<` + optParams + `>.*)\))?`)
|
||||
|
||||
var typeOfSQLDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
|
||||
// Struct represents a struct type.
|
||||
//
|
||||
// All methods in Struct are thread-safe.
|
||||
@@ -135,7 +138,6 @@ func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder)
|
||||
}
|
||||
|
||||
sb.Select(cols...)
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
@@ -182,7 +184,7 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
val = dereferencedValue(val)
|
||||
val = dereferencedFieldValue(val)
|
||||
}
|
||||
|
||||
data := val.Interface()
|
||||
@@ -240,7 +242,7 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
|
||||
|
||||
for _, item := range value {
|
||||
v := reflect.ValueOf(item)
|
||||
v = dereferencedValue(v)
|
||||
v = dereferencedFieldValue(v)
|
||||
|
||||
if v.Type() == s.structType {
|
||||
vs = append(vs, v)
|
||||
@@ -268,7 +270,7 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
|
||||
nilCnt++
|
||||
}
|
||||
|
||||
val = dereferencedValue(val)
|
||||
val = dereferencedFieldValue(val)
|
||||
|
||||
if val.IsValid() {
|
||||
values[i] = append(values[i], val.Interface())
|
||||
@@ -488,6 +490,18 @@ func dereferencedValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
||||
|
||||
func dereferencedFieldValue(v reflect.Value) reflect.Value {
|
||||
for k := v.Kind(); k == reflect.Ptr || k == reflect.Interface; k = v.Kind() {
|
||||
if v.Type().Implements(typeOfSQLDriverValuer) {
|
||||
break
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// isEmptyValue checks if v is zero.
|
||||
// Following code is borrowed from `IsZero` method in `reflect.Value` since Go 1.13.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -838,6 +839,37 @@ func TestStructFieldAs(t *testing.T) {
|
||||
a.Equal(sql, `UPDATE t SET t1 = ?, t2 = ?, t4 = ?`)
|
||||
}
|
||||
|
||||
type structImplValuer int
|
||||
|
||||
func (v *structImplValuer) Value() (driver.Value, error) {
|
||||
return *v * 2, nil
|
||||
}
|
||||
|
||||
type structContainsValuer struct {
|
||||
F1 string
|
||||
F2 *structImplValuer
|
||||
}
|
||||
|
||||
func TestStructFieldsImplValuer(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
st := NewStruct(new(structContainsValuer))
|
||||
f1 := "foo"
|
||||
f2 := structImplValuer(100)
|
||||
|
||||
sql, args := st.Update("t", structContainsValuer{
|
||||
F1: f1,
|
||||
F2: &f2,
|
||||
}).BuildWithFlavor(MySQL)
|
||||
|
||||
a.Equal(sql, "UPDATE t SET F1 = ?, F2 = ?")
|
||||
a.Equal(args[0], f1)
|
||||
a.Equal(args[1], &f2)
|
||||
|
||||
result, err := MySQL.Interpolate(sql, args)
|
||||
a.NilError(err)
|
||||
a.Equal(result, "UPDATE t SET F1 = 'foo', F2 = 200")
|
||||
}
|
||||
|
||||
func SomeOtherMapper(string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user