Merge branch 'hanyuancheung-feature/support_clickhouse_grammar'

This commit is contained in:
Huan Du
2022-12-15 16:02:19 +08:00
8 changed files with 96 additions and 8 deletions
+2 -2
View File
@@ -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)
@@ -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.
+1 -1
View File
@@ -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)
+11 -3
View File
@@ -16,6 +16,7 @@ const (
PostgreSQL
SQLite
SQLServer
ClickHouse
)
var (
@@ -49,6 +50,8 @@ func (f Flavor) String() string {
return "SQLite"
case SQLServer:
return "SQLServer"
case ClickHouse:
return "ClickHouse"
}
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 ClickHouse:
return clickhouseInterpolate(sql, args...)
}
return "", ErrInterpolateNotImplemented
@@ -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)
@@ -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()))
+16
View File
@@ -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")
+12
View File
@@ -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:
@@ -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, '\'')
@@ -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:
+46
View File
@@ -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 {
+1 -1
View File
@@ -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
View File
@@ -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() {
@@ -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() {