mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
Add CQL support (#1)
* Add CQL flavor. * Add support for CQL argument compilation. * Adopt substests for interpolation tests. * Add support for CQL interpolation. * Add support for CQL blobs. https://docs.datastax.com/en/cql-oss/3.x/cql/cql_reference/blob_r.html * Add CQL timestamp interpolation support. https://docs.datastax.com/en/cql-oss/3.x/cql/cql_reference/timestamp_type_r.html * Add builder test for CQL. * Add support for the NOW function. https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/timeuuid_functions_r.html?hl=now * Add support for CQL update IF statement. https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/cqlUpdate.html#Conditionallyupdatingcolumns * Drop table prefixes for CQL SELECTs. * Switch to single quoting for CQL queries. https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/escape_char_r.html * Update documentation. * Use valid CQL in tests. * Support CQL LIMIT.
This commit is contained in:
committed by
GitHub
parent
68495078ea
commit
0f54cd520c
@@ -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 or SQLite
|
||||
### Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, or CQL
|
||||
|
||||
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.
|
||||
|
||||
@@ -122,7 +122,7 @@ We can wrap any `Builder` with a default flavor through `WithFlavor`.
|
||||
|
||||
To be more verbose, we can use `PostgreSQL.NewSelectBuilder()` to create a `SelectBuilder` with the `PostgreSQL` flavor. All builders can be created in this way.
|
||||
|
||||
Right now, there are only three flavors, `MySQL`, `PostgreSQL`, `SQLServer` and `SQLite`. Open new issue to me to ask for a new flavor if you find it necessary.
|
||||
Right now, there are five flavors, `MySQL`, `PostgreSQL`, `SQLServer`, `SQLite`, and `CQL`. Open new issue to me to ask for a new flavor if you find it necessary.
|
||||
|
||||
### Using `Struct` as a light weight ORM
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
|
||||
}
|
||||
default:
|
||||
switch flavor {
|
||||
case MySQL, SQLite:
|
||||
case MySQL, SQLite, CQL:
|
||||
buf.WriteRune('?')
|
||||
case PostgreSQL:
|
||||
fmt.Fprintf(buf, "$%d", len(values)+1)
|
||||
|
||||
@@ -77,6 +77,21 @@ func TestArgs(t *testing.T) {
|
||||
|
||||
a.Equal(actual, expected)
|
||||
}
|
||||
|
||||
DefaultFlavor = CQL
|
||||
|
||||
for expected, c := range cases {
|
||||
args := new(Args)
|
||||
|
||||
for i := 1; i < len(c); i++ {
|
||||
args.Add(c[i])
|
||||
}
|
||||
|
||||
sql, values := args.Compile(c[0].(string))
|
||||
actual := fmt.Sprintf("%v\n%v", sql, values)
|
||||
|
||||
a.Equal(actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func toPostgreSQL(sql string) string {
|
||||
|
||||
@@ -103,3 +103,24 @@ func TestBuildWithPostgreSQL(t *testing.T) {
|
||||
a.Equal(sql, "SELECT $1 AS col5 LEFT JOIN SELECT col1, col2 FROM t1 WHERE id = $2 AND level > $3 LEFT JOIN SELECT col3, col4 FROM t2 WHERE id = $4 AND level <= $5")
|
||||
a.Equal(args, []interface{}{7890, 1234, 2, 4567, 5})
|
||||
}
|
||||
|
||||
func TestBuildWithCQL(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
ib1 := CQL.NewInsertBuilder()
|
||||
ib1.InsertInto("t1").Cols("col1", "col2").Values(1, 2)
|
||||
|
||||
ib2 := CQL.NewInsertBuilder()
|
||||
ib2.InsertInto("t2").Cols("col3", "col4").Values(3, 4)
|
||||
|
||||
old := DefaultFlavor
|
||||
DefaultFlavor = CQL
|
||||
defer func() {
|
||||
DefaultFlavor = old
|
||||
}()
|
||||
|
||||
sql, args := Build("BEGIN BATCH USING TIMESTAMP $0 $1; $2; APPLY BATCH;", 1481124356754405, ib1, ib2).Build()
|
||||
|
||||
a.Equal(sql, "BEGIN BATCH USING TIMESTAMP ? INSERT INTO t1 (col1, col2) VALUES (?, ?); INSERT INTO t2 (col3, col4) VALUES (?, ?); APPLY BATCH;")
|
||||
a.Equal(args, []interface{}{1481124356754405, 1, 2, 3, 4})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
PostgreSQL
|
||||
SQLite
|
||||
SQLServer
|
||||
CQL
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,6 +50,8 @@ func (f Flavor) String() string {
|
||||
return "SQLite"
|
||||
case SQLServer:
|
||||
return "SQLServer"
|
||||
case CQL:
|
||||
return "CQL"
|
||||
}
|
||||
|
||||
return "<invalid>"
|
||||
@@ -69,6 +72,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
|
||||
return sqliteInterpolate(sql, args...)
|
||||
case SQLServer:
|
||||
return sqlserverInterpolate(sql, args...)
|
||||
case CQL:
|
||||
return cqlInterpolate(sql, args...)
|
||||
}
|
||||
|
||||
return "", ErrInterpolateNotImplemented
|
||||
@@ -127,6 +132,8 @@ func (f Flavor) Quote(name string) string {
|
||||
return fmt.Sprintf("`%s`", name)
|
||||
case PostgreSQL, SQLServer, SQLite:
|
||||
return fmt.Sprintf(`"%s"`, name)
|
||||
case CQL:
|
||||
return fmt.Sprintf("'%s'", name)
|
||||
}
|
||||
|
||||
return name
|
||||
|
||||
@@ -121,3 +121,20 @@ func ExampleFlavor_Interpolate_sqlServer() {
|
||||
// SELECT name FROM user WHERE id <> 1234 AND name = N'Charmy Liu' AND desc LIKE N'%mother\'s day%'
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleFlavor_Interpolate_cql() {
|
||||
sb := CQL.NewSelectBuilder()
|
||||
sb.Select("name").From("user").Where(
|
||||
sb.E("id", 1234),
|
||||
sb.E("name", "Charmy Liu"),
|
||||
)
|
||||
sql, args := sb.Build()
|
||||
query, err := CQL.Interpolate(sql, args)
|
||||
|
||||
fmt.Println(query)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// SELECT name FROM user WHERE id = 1234 AND name = 'Charmy Liu'
|
||||
// <nil>
|
||||
}
|
||||
|
||||
+12
-12
@@ -52,48 +52,48 @@ func ExampleReplaceInto() {
|
||||
func ExampleInsertBuilder() {
|
||||
ib := NewInsertBuilder()
|
||||
ib.InsertInto("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)
|
||||
ib.Cols("id", "name", "status", "created_at", "updated_at")
|
||||
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"), Now())
|
||||
ib.Values(2, "Charmy Liu", 1, 1234567890, Now())
|
||||
|
||||
sql, args := ib.Build()
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// INSERT INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
|
||||
// INSERT INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), NOW()), (?, ?, ?, ?, NOW())
|
||||
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
|
||||
}
|
||||
|
||||
func ExampleInsertBuilder_insertIgnore() {
|
||||
ib := 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)
|
||||
ib.Cols("id", "name", "status", "created_at", "updated_at")
|
||||
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"), Now())
|
||||
ib.Values(2, "Charmy Liu", 1, 1234567890, Now())
|
||||
|
||||
sql, args := ib.Build()
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// INSERT IGNORE INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
|
||||
// INSERT IGNORE INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), NOW()), (?, ?, ?, ?, NOW())
|
||||
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
|
||||
}
|
||||
|
||||
func ExampleInsertBuilder_replaceInto() {
|
||||
ib := NewInsertBuilder()
|
||||
ib.ReplaceInto("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)
|
||||
ib.Cols("id", "name", "status", "created_at", "updated_at")
|
||||
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"), Now())
|
||||
ib.Values(2, "Charmy Liu", 1, 1234567890, Now())
|
||||
|
||||
sql, args := ib.Build()
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// REPLACE INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
|
||||
// REPLACE INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), NOW()), (?, ?, ?, ?, NOW())
|
||||
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -384,6 +384,11 @@ func sqliteInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return mysqlLikeInterpolate(SQLite, query, args...)
|
||||
}
|
||||
|
||||
// cqlInterpolate works the same as MySQL interpolating.
|
||||
func cqlInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return mysqlLikeInterpolate(CQL, query, args...)
|
||||
}
|
||||
|
||||
func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
switch v := arg.(type) {
|
||||
case nil:
|
||||
@@ -453,7 +458,7 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
buf = appendHex(buf, v)
|
||||
buf = append(buf, '\'')
|
||||
|
||||
case SQLServer:
|
||||
case SQLServer, CQL:
|
||||
buf = append(buf, "0x"...)
|
||||
buf = appendHex(buf, v)
|
||||
}
|
||||
@@ -484,6 +489,9 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
|
||||
case SQLServer:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.999999 Z07:00")...)
|
||||
|
||||
case CQL:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.999999Z0700")...)
|
||||
}
|
||||
|
||||
buf = append(buf, '\'')
|
||||
@@ -540,7 +548,11 @@ func quoteStringValue(buf []byte, s string, flavor Flavor) []byte {
|
||||
buf = append(buf, "\\Z"...)
|
||||
|
||||
case '\'':
|
||||
buf = append(buf, "\\'"...)
|
||||
if flavor == CQL {
|
||||
buf = append(buf, "''"...)
|
||||
} else {
|
||||
buf = append(buf, "\\'"...)
|
||||
}
|
||||
|
||||
case '"':
|
||||
buf = append(buf, "\\\""...)
|
||||
|
||||
+39
-5
@@ -1,6 +1,7 @@
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFlavorInterpolate(t *testing.T) {
|
||||
a := assert.New(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)
|
||||
cases := []struct {
|
||||
@@ -137,13 +137,47 @@ 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)},
|
||||
"SELECT * FROM a WHERE name = 'I''m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
CQL,
|
||||
"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,
|
||||
},
|
||||
{
|
||||
CQL,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, 0x49276D206279746573, '2019-04-24 12:23:34.123457+0800', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
CQL,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{CQL},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'CQL'", nil,
|
||||
},
|
||||
{
|
||||
CQL,
|
||||
"SELECT ?", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
CQL,
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
a.Use(&idx, &c)
|
||||
query, err := c.flavor.Interpolate(c.sql, c.args)
|
||||
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)
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ func Raw(expr string) interface{} {
|
||||
return rawArgs{expr}
|
||||
}
|
||||
|
||||
// Now returns a raw value comprising the NOW() function.
|
||||
func Now() interface{} {
|
||||
return Raw("NOW()")
|
||||
}
|
||||
|
||||
type listArgs struct {
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
@@ -310,6 +310,11 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
|
||||
buf.WriteString(strconv.Itoa(sb.offset))
|
||||
}
|
||||
}
|
||||
case CQL:
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
}
|
||||
case PostgreSQL:
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
|
||||
+10
-2
@@ -112,7 +112,7 @@ func ExampleSelectBuilder_join() {
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_limit_offset() {
|
||||
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer}
|
||||
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL}
|
||||
results := make([][]string, len(flavors))
|
||||
sb := NewSelectBuilder()
|
||||
saveResults := func() {
|
||||
@@ -137,13 +137,15 @@ func ExampleSelectBuilder_limit_offset() {
|
||||
// MySQL and SQLite: Ignore offset if the limit is not set.
|
||||
// PostgreSQL: Offset can be set without limit.
|
||||
// SQLServer: Offset can be set without limit.
|
||||
// CQL: Ignore offset.
|
||||
sb.Limit(-1)
|
||||
sb.Offset(0)
|
||||
saveResults()
|
||||
|
||||
// Case #3: limit >= 0 and offset >= 0
|
||||
//
|
||||
// All: Set both limit and offset.
|
||||
// CQL: Ignore offset.
|
||||
// All others: Set both limit and offset.
|
||||
sb.Limit(1)
|
||||
sb.Offset(0)
|
||||
saveResults()
|
||||
@@ -189,6 +191,12 @@ func ExampleSelectBuilder_limit_offset() {
|
||||
// #2: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS
|
||||
// #3: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
||||
// #4: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
||||
//
|
||||
// CQL
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user
|
||||
// #3: SELECT * FROM user LIMIT 1
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_ForUpdate() {
|
||||
|
||||
@@ -114,12 +114,16 @@ func (s *Struct) SelectFromForTag(table string, tag string) *SelectBuilder {
|
||||
buf := &bytes.Buffer{}
|
||||
cols := make([]string, 0, len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
buf.WriteString(table)
|
||||
buf.WriteRune('.')
|
||||
buf.WriteString(field)
|
||||
cols = append(cols, buf.String())
|
||||
buf.Reset()
|
||||
if s.Flavor == CQL {
|
||||
cols = append(cols, fields...)
|
||||
} else {
|
||||
for _, field := range fields {
|
||||
buf.WriteString(table)
|
||||
buf.WriteRune('.')
|
||||
buf.WriteString(field)
|
||||
cols = append(cols, buf.String())
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
sb.Select(cols...)
|
||||
|
||||
@@ -516,6 +516,21 @@ func ExampleStruct_forPostgreSQL() {
|
||||
// [1234]
|
||||
}
|
||||
|
||||
func ExampleStruct_forCQL() {
|
||||
userStruct := NewStruct(new(User)).For(CQL)
|
||||
|
||||
sb := userStruct.SelectFrom("user")
|
||||
sb.Where(sb.E("id", 1234))
|
||||
sql, args := sb.Build()
|
||||
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// SELECT id, name, status FROM user WHERE id = ?
|
||||
// [1234]
|
||||
}
|
||||
|
||||
type structWithQuote struct {
|
||||
A string `db:"aa" fieldopt:"withquote"`
|
||||
B int `db:"-" fieldopt:"withquote"` // fieldopt is ignored as db is "-".
|
||||
@@ -532,6 +547,10 @@ func TestStructWithQuote(t *testing.T) {
|
||||
sql, _ = sb.Build()
|
||||
a.Equal(sql, `SELECT foo."aa", foo.ccc FROM foo`)
|
||||
|
||||
sb = NewStruct(new(structWithQuote)).For(CQL).SelectFrom("foo")
|
||||
sql, _ = sb.Build()
|
||||
a.Equal(sql, "SELECT 'aa', ccc FROM foo")
|
||||
|
||||
ub := NewStruct(new(structWithQuote)).For(MySQL).Update("foo", structWithQuote{A: "aaa"})
|
||||
sql, _ = ub.Build()
|
||||
a.Equal(sql, "UPDATE foo SET `aa` = ?, ccc = ?")
|
||||
@@ -540,6 +559,10 @@ func TestStructWithQuote(t *testing.T) {
|
||||
sql, _ = ub.Build()
|
||||
a.Equal(sql, `UPDATE foo SET "aa" = $1, ccc = $2`)
|
||||
|
||||
ub = NewStruct(new(structWithQuote)).For(CQL).Update("foo", structWithQuote{A: "aaa"})
|
||||
sql, _ = ub.Build()
|
||||
a.Equal(sql, `UPDATE foo SET 'aa' = ?, ccc = ?`)
|
||||
|
||||
ib := NewStruct(new(structWithQuote)).For(MySQL).InsertInto("foo", structWithQuote{A: "aaa"})
|
||||
sql, _ = ib.Build()
|
||||
a.Equal(sql, "INSERT INTO foo (`aa`, ccc) VALUES (?, ?)")
|
||||
@@ -547,6 +570,10 @@ func TestStructWithQuote(t *testing.T) {
|
||||
ib = NewStruct(new(structWithQuote)).For(PostgreSQL).InsertInto("foo", structWithQuote{A: "aaa"})
|
||||
sql, _ = ib.Build()
|
||||
a.Equal(sql, `INSERT INTO foo ("aa", ccc) VALUES ($1, $2)`)
|
||||
|
||||
ib = NewStruct(new(structWithQuote)).For(CQL).InsertInto("foo", structWithQuote{A: "aaa"})
|
||||
sql, _ = ib.Build()
|
||||
a.Equal(sql, "INSERT INTO foo ('aa', ccc) VALUES (?, ?)")
|
||||
}
|
||||
|
||||
type structOmitEmpty struct {
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
updateMarkerAfterWhere
|
||||
updateMarkerAfterOrderBy
|
||||
updateMarkerAfterLimit
|
||||
updateMarkerAfterIf
|
||||
)
|
||||
|
||||
// NewUpdateBuilder creates a new UPDATE builder.
|
||||
@@ -46,6 +47,7 @@ type UpdateBuilder struct {
|
||||
orderByCols []string
|
||||
order string
|
||||
limit int
|
||||
ifExprs []string
|
||||
|
||||
args *Args
|
||||
|
||||
@@ -157,6 +159,13 @@ func (ub *UpdateBuilder) Limit(limit int) *UpdateBuilder {
|
||||
return ub
|
||||
}
|
||||
|
||||
// If sets IF expressions for UPDATE.
|
||||
func (ub *UpdateBuilder) If(andExpr ...string) *UpdateBuilder {
|
||||
ub.ifExprs = append(ub.ifExprs, andExpr...)
|
||||
ub.marker = updateMarkerAfterIf
|
||||
return ub
|
||||
}
|
||||
|
||||
// String returns the compiled UPDATE string.
|
||||
func (ub *UpdateBuilder) String() string {
|
||||
s, _ := ub.Build()
|
||||
@@ -207,6 +216,12 @@ func (ub *UpdateBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
|
||||
ub.injection.WriteTo(buf, updateMarkerAfterLimit)
|
||||
}
|
||||
|
||||
if len(ub.ifExprs) > 0 {
|
||||
buf.WriteString(" IF ")
|
||||
buf.WriteString(strings.Join(ub.ifExprs, " AND "))
|
||||
ub.injection.WriteTo(buf, updateMarkerAfterIf)
|
||||
}
|
||||
|
||||
return ub.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -44,14 +44,18 @@ func ExampleUpdateBuilder() {
|
||||
"modified_at > created_at + "+ub.Var(86400), // It's allowed to write arbitrary SQL.
|
||||
)
|
||||
ub.OrderBy("id").Asc()
|
||||
ub.If(
|
||||
ub.E("type", "sys"),
|
||||
ub.E("ready", true),
|
||||
)
|
||||
|
||||
sql, args := ub.Build()
|
||||
fmt.Println(sql)
|
||||
fmt.Println(args)
|
||||
|
||||
// Output:
|
||||
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ? ORDER BY id ASC
|
||||
// [sys 1234 %Du 1 2 5 86400]
|
||||
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ? ORDER BY id ASC IF type = ? AND ready = ?
|
||||
// [sys 1234 %Du 1 2 5 86400 sys true]
|
||||
}
|
||||
|
||||
func TestUpdateAssignments(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user