Skip to content

Commit

Permalink
Merge branch 'hanyuancheung-feature/support_clickhouse_grammar'
Browse files Browse the repository at this point in the history
  • Loading branch information
huandu committed Dec 15, 2022
2 parents f299327 + f8230fd commit f5453c8
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 8 deletions.
4 changes: 2 additions & 2 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 or SQLite](#build-sql-for-mysql-postgresql-sqlserver-or-sqlite)
- [Build SQL for MySQL, PostgreSQL, SQLServer, SQLite 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, SQLServer or SQLite
### Build SQL for MySQL, PostgreSQL, SQLServe, SQLite 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 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:
case MySQL, SQLite, ClickHouse:
buf.WriteRune('?')
case PostgreSQL:
fmt.Fprintf(buf, "$%d", len(values)+1)
Expand Down
14 changes: 11 additions & 3 deletions flavor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
PostgreSQL
SQLite
SQLServer
ClickHouse
)

var (
Expand Down Expand Up @@ -49,6 +50,8 @@ func (f Flavor) String() string {
return "SQLite"
case SQLServer:
return "SQLServer"
case ClickHouse:
return "ClickHouse"
}

return "<invalid>"
Expand All @@ -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 ClickHouse:
return clickhouseInterpolate(sql, args...)
}

return "", ErrInterpolateNotImplemented
Expand Down Expand Up @@ -119,11 +124,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)
Expand All @@ -146,6 +151,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()))
Expand Down
16 changes: 16 additions & 0 deletions insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 12 additions & 0 deletions interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ func sqliteInterpolate(query string, args ...interface{}) (string, error) {
return mysqlLikeInterpolate(SQLite, 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:
Expand Down Expand Up @@ -421,6 +425,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 ClickHouse:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
}

buf = append(buf, '\'')
Expand Down Expand Up @@ -521,6 +528,11 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
case SQLServer:
buf = append(buf, "0x"...)
buf = appendHex(buf, data)

case ClickHouse:
buf = append(buf, "unhex('"...)
buf = appendHex(buf, data)
buf = append(buf, "')"...)
}

default:
Expand Down
46 changes: 46 additions & 0 deletions interpolate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,52 @@ func TestFlavorInterpolate(t *testing.T) {
"SELECT @p1", nil,
"", ErrInterpolateMissingArgs,
},

{
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 {
Expand Down
2 changes: 1 addition & 1 deletion select.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
8 changes: 7 additions & 1 deletion 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}
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, ClickHouse}
results := make([][]string, len(flavors))
sb := NewSelectBuilder()
saveResults := func() {
Expand Down Expand Up @@ -189,6 +189,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
//
// 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() {
Expand Down

0 comments on commit f5453c8

Please sign in to comment.