Skip to content

Commit 674f3da

Browse files
authored
Merge pull request #85 from datastax-ext/master
Add CQL support
2 parents f5453c8 + 28a0bcf commit 674f3da

File tree

13 files changed

+168
-22
lines changed

13 files changed

+168
-22
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- [Usage](#usage)
1010
- [Basic usage](#basic-usage)
1111
- [Pre-defined SQL builders](#pre-defined-sql-builders)
12-
- [Build SQL for MySQL, PostgreSQL, SQLServer, SQLite or ClickHouse](#build-sql-for-mysql-postgresql-sqlserve-sqlite-or-clickhouse)
12+
- [Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, CQL, or ClickHouse](#build-sql-for-mysql-postgresql-sqlserve-sqlite-or-clickhouse)
1313
- [Using `Struct` as a light weight ORM](#using-struct-as-a-light-weight-orm)
1414
- [Nested SQL](#nested-sql)
1515
- [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.
110110

111111
To learn how to use builders, check out [examples on GoDoc](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#pkg-examples).
112112

113-
### Build SQL for MySQL, PostgreSQL, SQLServe, SQLite or ClickHouse
113+
### Build SQL for MySQL, PostgreSQL, SQLServer, SQLite, CQL, or ClickHouse
114114

115115
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.
116116

@@ -122,7 +122,7 @@ We can wrap any `Builder` with a default flavor through `WithFlavor`.
122122

123123
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.
124124

125-
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.
125+
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.
126126

127127
### Using `Struct` as a light weight ORM
128128

args.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
233233
}
234234
default:
235235
switch flavor {
236-
case MySQL, SQLite, ClickHouse:
236+
case MySQL, SQLite, CQL, ClickHouse:
237237
buf.WriteRune('?')
238238
case PostgreSQL:
239239
fmt.Fprintf(buf, "$%d", len(values)+1)

args_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ func TestArgs(t *testing.T) {
7777

7878
a.Equal(actual, expected)
7979
}
80+
81+
DefaultFlavor = CQL
82+
83+
for expected, c := range cases {
84+
args := new(Args)
85+
86+
for i := 1; i < len(c); i++ {
87+
args.Add(c[i])
88+
}
89+
90+
sql, values := args.Compile(c[0].(string))
91+
actual := fmt.Sprintf("%v\n%v", sql, values)
92+
93+
a.Equal(actual, expected)
94+
}
8095
}
8196

8297
func toPostgreSQL(sql string) string {

builder_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,24 @@ func TestBuildWithPostgreSQL(t *testing.T) {
103103
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")
104104
a.Equal(args, []interface{}{7890, 1234, 2, 4567, 5})
105105
}
106+
107+
func TestBuildWithCQL(t *testing.T) {
108+
a := assert.New(t)
109+
110+
ib1 := CQL.NewInsertBuilder()
111+
ib1.InsertInto("t1").Cols("col1", "col2").Values(1, 2)
112+
113+
ib2 := CQL.NewInsertBuilder()
114+
ib2.InsertInto("t2").Cols("col3", "col4").Values(3, 4)
115+
116+
old := DefaultFlavor
117+
DefaultFlavor = CQL
118+
defer func() {
119+
DefaultFlavor = old
120+
}()
121+
122+
sql, args := Build("BEGIN BATCH USING TIMESTAMP $0 $1; $2; APPLY BATCH;", 1481124356754405, ib1, ib2).Build()
123+
124+
a.Equal(sql, "BEGIN BATCH USING TIMESTAMP ? INSERT INTO t1 (col1, col2) VALUES (?, ?); INSERT INTO t2 (col3, col4) VALUES (?, ?); APPLY BATCH;")
125+
a.Equal(args, []interface{}{1481124356754405, 1, 2, 3, 4})
126+
}

flavor.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
PostgreSQL
1717
SQLite
1818
SQLServer
19+
CQL
1920
ClickHouse
2021
)
2122

@@ -50,6 +51,8 @@ func (f Flavor) String() string {
5051
return "SQLite"
5152
case SQLServer:
5253
return "SQLServer"
54+
case CQL:
55+
return "CQL"
5356
case ClickHouse:
5457
return "ClickHouse"
5558
}
@@ -72,6 +75,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
7275
return sqliteInterpolate(sql, args...)
7376
case SQLServer:
7477
return sqlserverInterpolate(sql, args...)
78+
case CQL:
79+
return cqlInterpolate(sql, args...)
7580
case ClickHouse:
7681
return clickhouseInterpolate(sql, args...)
7782
}
@@ -132,6 +137,8 @@ func (f Flavor) Quote(name string) string {
132137
return fmt.Sprintf("`%s`", name)
133138
case PostgreSQL, SQLServer, SQLite:
134139
return fmt.Sprintf(`"%s"`, name)
140+
case CQL:
141+
return fmt.Sprintf("'%s'", name)
135142
}
136143

137144
return name

flavor_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,20 @@ func ExampleFlavor_Interpolate_sqlServer() {
121121
// SELECT name FROM user WHERE id <> 1234 AND name = N'Charmy Liu' AND desc LIKE N'%mother\'s day%'
122122
// <nil>
123123
}
124+
125+
func ExampleFlavor_Interpolate_cql() {
126+
sb := CQL.NewSelectBuilder()
127+
sb.Select("name").From("user").Where(
128+
sb.E("id", 1234),
129+
sb.E("name", "Charmy Liu"),
130+
)
131+
sql, args := sb.Build()
132+
query, err := CQL.Interpolate(sql, args)
133+
134+
fmt.Println(query)
135+
fmt.Println(err)
136+
137+
// Output:
138+
// SELECT name FROM user WHERE id = 1234 AND name = 'Charmy Liu'
139+
// <nil>
140+
}

insert_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func ExampleReplaceInto() {
5252
func ExampleInsertBuilder() {
5353
ib := NewInsertBuilder()
5454
ib.InsertInto("demo.user")
55-
ib.Cols("id", "name", "status", "created_at")
55+
ib.Cols("id", "name", "status", "created_at", "updated_at")
5656
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
5757
ib.Values(2, "Charmy Liu", 1, 1234567890)
5858

@@ -61,14 +61,14 @@ func ExampleInsertBuilder() {
6161
fmt.Println(args)
6262

6363
// Output:
64-
// INSERT INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
64+
// INSERT INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
6565
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
6666
}
6767

6868
func ExampleInsertBuilder_insertIgnore() {
6969
ib := NewInsertBuilder()
7070
ib.InsertIgnoreInto("demo.user")
71-
ib.Cols("id", "name", "status", "created_at")
71+
ib.Cols("id", "name", "status", "created_at", "updated_at")
7272
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
7373
ib.Values(2, "Charmy Liu", 1, 1234567890)
7474

@@ -77,7 +77,7 @@ func ExampleInsertBuilder_insertIgnore() {
7777
fmt.Println(args)
7878

7979
// Output:
80-
// INSERT IGNORE INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
80+
// INSERT IGNORE INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
8181
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
8282
}
8383

@@ -132,7 +132,7 @@ func ExampleInsertBuilder_insertIgnore_clickhouse() {
132132
func ExampleInsertBuilder_replaceInto() {
133133
ib := NewInsertBuilder()
134134
ib.ReplaceInto("demo.user")
135-
ib.Cols("id", "name", "status", "created_at")
135+
ib.Cols("id", "name", "status", "created_at", "updated_at")
136136
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
137137
ib.Values(2, "Charmy Liu", 1, 1234567890)
138138

@@ -141,7 +141,7 @@ func ExampleInsertBuilder_replaceInto() {
141141
fmt.Println(args)
142142

143143
// Output:
144-
// REPLACE INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
144+
// REPLACE INTO demo.user (id, name, status, created_at, updated_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
145145
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
146146
}
147147

interpolate.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ func sqliteInterpolate(query string, args ...interface{}) (string, error) {
386386
return mysqlLikeInterpolate(SQLite, query, args...)
387387
}
388388

389+
// cqlInterpolate works the same as MySQL interpolating.
390+
func cqlInterpolate(query string, args ...interface{}) (string, error) {
391+
return mysqlLikeInterpolate(CQL, query, args...)
392+
}
393+
389394
func clickhouseInterpolate(query string, args ...interface{}) (string, error) {
390395
return mysqlLikeInterpolate(ClickHouse, query, args...)
391396
}
@@ -426,6 +431,9 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
426431
case SQLServer:
427432
buf = append(buf, v.Format("2006-01-02 15:04:05.999999 Z07:00")...)
428433

434+
case CQL:
435+
buf = append(buf, v.Format("2006-01-02 15:04:05.999999Z0700")...)
436+
429437
case ClickHouse:
430438
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
431439
}
@@ -525,7 +533,7 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
525533
buf = appendHex(buf, data)
526534
buf = append(buf, '\'')
527535

528-
case SQLServer:
536+
case SQLServer, CQL:
529537
buf = append(buf, "0x"...)
530538
buf = appendHex(buf, data)
531539

@@ -585,7 +593,11 @@ func quoteStringValue(buf []byte, s string, flavor Flavor) []byte {
585593
buf = append(buf, "\\Z"...)
586594

587595
case '\'':
588-
buf = append(buf, "\\'"...)
596+
if flavor == CQL {
597+
buf = append(buf, "''"...)
598+
} else {
599+
buf = append(buf, "\\'"...)
600+
}
589601

590602
case '"':
591603
buf = append(buf, "\\\""...)

interpolate_test.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package sqlbuilder
33
import (
44
"database/sql/driver"
55
"errors"
6+
"fmt"
67
"strconv"
78
"testing"
89
"time"
@@ -19,7 +20,6 @@ func (v errorValuer) Value() (driver.Value, error) {
1920
}
2021

2122
func TestFlavorInterpolate(t *testing.T) {
22-
a := assert.New(t)
2323
dt := time.Date(2019, 4, 24, 12, 23, 34, 123456789, time.FixedZone("CST", 8*60*60)) // 2019-04-24 12:23:34.987654321 CST
2424
_, errOutOfRange := strconv.ParseInt("12345678901234567890", 10, 32)
2525
byteArr := [...]byte{'f', 'o', 'o'}
@@ -163,7 +163,36 @@ func TestFlavorInterpolate(t *testing.T) {
163163
"SELECT @p1", nil,
164164
"", ErrInterpolateMissingArgs,
165165
},
166-
166+
{
167+
CQL,
168+
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
169+
"SELECT * FROM a WHERE name = 'I''m fine' AND state IN (42, 8, -16, 32, 64)", nil,
170+
},
171+
{
172+
CQL,
173+
"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"},
174+
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"''', '?', 42, 8, 16, 32, 64)", nil,
175+
},
176+
{
177+
CQL,
178+
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
179+
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, 0x49276D206279746573, '2019-04-24 12:23:34.123457+0800', '0000-00-00', NULL", nil,
180+
},
181+
{
182+
CQL,
183+
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{CQL},
184+
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'CQL'", nil,
185+
},
186+
{
187+
CQL,
188+
"SELECT ?", nil,
189+
"", ErrInterpolateMissingArgs,
190+
},
191+
{
192+
CQL,
193+
"SELECT ?", []interface{}{complex(1, 2)},
194+
"", ErrInterpolateUnsupportedArgs,
195+
},
167196
{
168197
ClickHouse,
169198
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
@@ -212,10 +241,13 @@ func TestFlavorInterpolate(t *testing.T) {
212241
}
213242

214243
for idx, c := range cases {
215-
a.Use(&idx, &c)
216-
query, err := c.Flavor.Interpolate(c.SQL, c.Args)
244+
t.Run(fmt.Sprintf("%s: %s", c.Flavor.String(), c.Query), func(t *testing.T) {
245+
a := assert.New(t)
246+
a.Use(&idx, &c)
247+
query, err := c.Flavor.Interpolate(c.SQL, c.Args)
217248

218-
a.Equal(query, c.Query)
219-
a.Assert(err == c.Err || err.Error() == c.Err.Error())
249+
a.Equal(query, c.Query)
250+
a.Assert(err == c.Err || err.Error() == c.Err.Error())
251+
})
220252
}
221253
}

select.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
310310
buf.WriteString(strconv.Itoa(sb.offset))
311311
}
312312
}
313+
case CQL:
314+
if sb.limit >= 0 {
315+
buf.WriteString(" LIMIT ")
316+
buf.WriteString(strconv.Itoa(sb.limit))
317+
}
313318
case PostgreSQL:
314319
if sb.limit >= 0 {
315320
buf.WriteString(" LIMIT ")

0 commit comments

Comments
 (0)