Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CQL support #85

Merged
merged 6 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- [Usage](#usage)
- [Basic usage](#basic-usage)
- [Pre-defined SQL builders](#pre-defined-sql-builders)
- [Build SQL for MySQL, PostgreSQL, SQLServer, SQLite or ClickHouse](#build-sql-for-mysql-postgresql-sqlserve-sqlite-or-clickhouse)
- [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)
Expand Down Expand Up @@ -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, SQLServe, SQLite or ClickHouse
### 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.

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion args.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
}
default:
switch flavor {
case MySQL, SQLite, ClickHouse:
case MySQL, SQLite, CQL, ClickHouse:
buf.WriteRune('?')
case PostgreSQL:
fmt.Fprintf(buf, "$%d", len(values)+1)
Expand Down
15 changes: 15 additions & 0 deletions args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
}
7 changes: 7 additions & 0 deletions flavor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
PostgreSQL
SQLite
SQLServer
CQL
ClickHouse
)

Expand Down Expand Up @@ -50,6 +51,8 @@ func (f Flavor) String() string {
return "SQLite"
case SQLServer:
return "SQLServer"
case CQL:
return "CQL"
case ClickHouse:
return "ClickHouse"
}
Expand All @@ -72,6 +75,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...)
case ClickHouse:
return clickhouseInterpolate(sql, args...)
}
Expand Down Expand Up @@ -132,6 +137,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
Expand Down
17 changes: 17 additions & 0 deletions flavor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 6 additions & 6 deletions insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func ExampleReplaceInto() {
func ExampleInsertBuilder() {
ib := NewInsertBuilder()
ib.InsertInto("demo.user")
ib.Cols("id", "name", "status", "created_at")
ib.Cols("id", "name", "status", "created_at", "updated_at")
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
ib.Values(2, "Charmy Liu", 1, 1234567890)

Expand All @@ -61,14 +61,14 @@ func ExampleInsertBuilder() {
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())), (?, ?, ?, ?)
// [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.Cols("id", "name", "status", "created_at", "updated_at")
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
ib.Values(2, "Charmy Liu", 1, 1234567890)

Expand All @@ -77,7 +77,7 @@ func ExampleInsertBuilder_insertIgnore() {
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())), (?, ?, ?, ?)
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
}

Expand Down Expand Up @@ -132,7 +132,7 @@ func ExampleInsertBuilder_insertIgnore_clickhouse() {
func ExampleInsertBuilder_replaceInto() {
ib := NewInsertBuilder()
ib.ReplaceInto("demo.user")
ib.Cols("id", "name", "status", "created_at")
ib.Cols("id", "name", "status", "created_at", "updated_at")
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
ib.Values(2, "Charmy Liu", 1, 1234567890)

Expand All @@ -141,7 +141,7 @@ func ExampleInsertBuilder_replaceInto() {
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())), (?, ?, ?, ?)
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
}

Expand Down
16 changes: 14 additions & 2 deletions interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,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 clickhouseInterpolate(query string, args ...interface{}) (string, error) {
return mysqlLikeInterpolate(ClickHouse, query, args...)
}
Expand Down Expand Up @@ -426,6 +431,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")...)

case ClickHouse:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
}
Expand Down Expand Up @@ -525,7 +533,7 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
buf = appendHex(buf, data)
buf = append(buf, '\'')

case SQLServer:
case SQLServer, CQL:
buf = append(buf, "0x"...)
buf = appendHex(buf, data)

Expand Down Expand Up @@ -585,7 +593,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, "\\\""...)
Expand Down
44 changes: 38 additions & 6 deletions interpolate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlbuilder
import (
"database/sql/driver"
"errors"
"fmt"
"strconv"
"testing"
"time"
Expand All @@ -19,7 +20,6 @@ func (v errorValuer) Value() (driver.Value, error) {
}

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)
byteArr := [...]byte{'f', 'o', 'o'}
Expand Down Expand Up @@ -163,7 +163,36 @@ 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,
},
{
ClickHouse,
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
Expand Down Expand Up @@ -212,10 +241,13 @@ func TestFlavorInterpolate(t *testing.T) {
}

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())
})
}
}
5 changes: 5 additions & 0 deletions select.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ")
Expand Down
12 changes: 10 additions & 2 deletions select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func ExampleSelectBuilder_join() {
}

func ExampleSelectBuilder_limit_offset() {
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, ClickHouse}
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL, ClickHouse}
results := make([][]string, len(flavors))
sb := NewSelectBuilder()
saveResults := func() {
Expand All @@ -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()
Expand Down Expand Up @@ -190,6 +192,12 @@ func ExampleSelectBuilder_limit_offset() {
// #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
//
// ClickHouse
// #1: SELECT * FROM user
// #2: SELECT * FROM user
Expand Down
6 changes: 4 additions & 2 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder)
cols := make([]string, 0, len(tagged.ForRead))

for _, sf := range tagged.ForRead {
buf.WriteString(table)
buf.WriteRune('.')
if s.Flavor != CQL {
buf.WriteString(table)
buf.WriteRune('.')
}
buf.WriteString(sf.NameForSelect(s.Flavor))

cols = append(cols, buf.String())
Expand Down
Loading