Skip to content

Commit a4ec9c8

Browse files
committed
Merge remote-tracking branch 'upstream/master'
# Conflicts: # README.md # args.go # flavor.go # insert_test.go # interpolate.go # interpolate_test.go # select_test.go # struct.go # struct_test.go
2 parents cb43436 + f5453c8 commit a4ec9c8

File tree

10 files changed

+283
-92
lines changed

10 files changed

+283
-92
lines changed

README.md

Lines changed: 2 additions & 2 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 or SQLite](#build-sql-for-mysql-postgresql-sqlserver-or-sqlite)
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, SQLServer, SQLite, or CQL
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

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, CQL:
236+
case MySQL, SQLite, CQL, ClickHouse:
237237
buf.WriteRune('?')
238238
case PostgreSQL:
239239
fmt.Fprintf(buf, "$%d", len(values)+1)

flavor.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
SQLite
1818
SQLServer
1919
CQL
20+
ClickHouse
2021
)
2122

2223
var (
@@ -52,6 +53,8 @@ func (f Flavor) String() string {
5253
return "SQLServer"
5354
case CQL:
5455
return "CQL"
56+
case ClickHouse:
57+
return "ClickHouse"
5558
}
5659

5760
return "<invalid>"
@@ -74,6 +77,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
7477
return sqlserverInterpolate(sql, args...)
7578
case CQL:
7679
return cqlInterpolate(sql, args...)
80+
case ClickHouse:
81+
return clickhouseInterpolate(sql, args...)
7782
}
7883

7984
return "", ErrInterpolateNotImplemented
@@ -124,11 +129,11 @@ func (f Flavor) NewUnionBuilder() *UnionBuilder {
124129
// Quote adds quote for name to make sure the name can be used safely
125130
// as table name or field name.
126131
//
127-
// * For MySQL, use back quote (`) to quote name;
128-
// * For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
132+
// - For MySQL, use back quote (`) to quote name;
133+
// - For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
129134
func (f Flavor) Quote(name string) string {
130135
switch f {
131-
case MySQL:
136+
case MySQL, ClickHouse:
132137
return fmt.Sprintf("`%s`", name)
133138
case PostgreSQL, SQLServer, SQLite:
134139
return fmt.Sprintf(`"%s"`, name)
@@ -153,6 +158,9 @@ func (f Flavor) PrepareInsertIgnore(table string, ib *InsertBuilder) {
153158
case SQLite:
154159
// see https://www.sqlite.org/lang_insert.html
155160
ib.verb = "INSERT OR IGNORE"
161+
case ClickHouse:
162+
// see https://clickhouse.tech/docs/en/sql-reference/statements/insert-into/
163+
ib.verb = "INSERT"
156164
default:
157165
// panic if the db flavor is not supported
158166
panic(fmt.Errorf("unsupported db flavor: %s", ib.args.Flavor.String()))

insert_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,22 @@ func ExampleInsertBuilder_insertIgnore_sqlite() {
113113
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
114114
}
115115

116+
func ExampleInsertBuilder_insertIgnore_clickhouse() {
117+
ib := ClickHouse.NewInsertBuilder()
118+
ib.InsertIgnoreInto("demo.user")
119+
ib.Cols("id", "name", "status", "created_at")
120+
ib.Values(1, "Huan Du", 1, Raw("UNIX_TIMESTAMP(NOW())"))
121+
ib.Values(2, "Charmy Liu", 1, 1234567890)
122+
123+
sql, args := ib.Build()
124+
fmt.Println(sql)
125+
fmt.Println(args)
126+
127+
// Output:
128+
// INSERT INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
129+
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
130+
}
131+
116132
func ExampleInsertBuilder_replaceInto() {
117133
ib := NewInsertBuilder()
118134
ib.ReplaceInto("demo.user")

interpolate.go

Lines changed: 115 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
package sqlbuilder
55

66
import (
7+
"database/sql/driver"
78
"fmt"
9+
"reflect"
810
"strconv"
911
"time"
1012
"unicode"
@@ -389,83 +391,22 @@ func cqlInterpolate(query string, args ...interface{}) (string, error) {
389391
return mysqlLikeInterpolate(CQL, query, args...)
390392
}
391393

394+
func clickhouseInterpolate(query string, args ...interface{}) (string, error) {
395+
return mysqlLikeInterpolate(ClickHouse, query, args...)
396+
}
397+
392398
func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
393399
switch v := arg.(type) {
394400
case nil:
395401
buf = append(buf, "NULL"...)
396402

397-
case bool:
398-
if v {
399-
buf = append(buf, "TRUE"...)
403+
case driver.Valuer:
404+
if val, err := v.Value(); err != nil {
405+
return nil, err
400406
} else {
401-
buf = append(buf, "FALSE"...)
407+
return encodeValue(buf, val, flavor)
402408
}
403409

404-
case int:
405-
buf = strconv.AppendInt(buf, int64(v), 10)
406-
407-
case int8:
408-
buf = strconv.AppendInt(buf, int64(v), 10)
409-
410-
case int16:
411-
buf = strconv.AppendInt(buf, int64(v), 10)
412-
413-
case int32:
414-
buf = strconv.AppendInt(buf, int64(v), 10)
415-
416-
case int64:
417-
buf = strconv.AppendInt(buf, v, 10)
418-
419-
case uint:
420-
buf = strconv.AppendUint(buf, uint64(v), 10)
421-
422-
case uint8:
423-
buf = strconv.AppendUint(buf, uint64(v), 10)
424-
425-
case uint16:
426-
buf = strconv.AppendUint(buf, uint64(v), 10)
427-
428-
case uint32:
429-
buf = strconv.AppendUint(buf, uint64(v), 10)
430-
431-
case uint64:
432-
buf = strconv.AppendUint(buf, v, 10)
433-
434-
case float32:
435-
buf = strconv.AppendFloat(buf, float64(v), 'g', -1, 32)
436-
437-
case float64:
438-
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
439-
440-
case []byte:
441-
if v == nil {
442-
buf = append(buf, "NULL"...)
443-
break
444-
}
445-
446-
switch flavor {
447-
case MySQL:
448-
buf = append(buf, "_binary"...)
449-
buf = quoteStringValue(buf, *(*string)(unsafe.Pointer(&v)), flavor)
450-
451-
case PostgreSQL:
452-
buf = append(buf, "E'\\\\x"...)
453-
buf = appendHex(buf, v)
454-
buf = append(buf, "'::bytea"...)
455-
456-
case SQLite:
457-
buf = append(buf, "X'"...)
458-
buf = appendHex(buf, v)
459-
buf = append(buf, '\'')
460-
461-
case SQLServer, CQL:
462-
buf = append(buf, "0x"...)
463-
buf = appendHex(buf, v)
464-
}
465-
466-
case string:
467-
buf = quoteStringValue(buf, v, flavor)
468-
469410
case time.Time:
470411
if v.IsZero() {
471412
buf = append(buf, "'0000-00-00'"...)
@@ -492,6 +433,9 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
492433

493434
case CQL:
494435
buf = append(buf, v.Format("2006-01-02 15:04:05.999999Z0700")...)
436+
437+
case ClickHouse:
438+
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
495439
}
496440

497441
buf = append(buf, '\'')
@@ -500,7 +444,108 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
500444
buf = quoteStringValue(buf, v.String(), flavor)
501445

502446
default:
503-
return nil, ErrInterpolateUnsupportedArgs
447+
primative := reflect.ValueOf(arg)
448+
449+
switch k := primative.Kind(); k {
450+
case reflect.Bool:
451+
if primative.Bool() {
452+
buf = append(buf, "TRUE"...)
453+
} else {
454+
buf = append(buf, "FALSE"...)
455+
}
456+
457+
case reflect.Int:
458+
buf = strconv.AppendInt(buf, primative.Int(), 10)
459+
460+
case reflect.Int8:
461+
buf = strconv.AppendInt(buf, primative.Int(), 10)
462+
463+
case reflect.Int16:
464+
buf = strconv.AppendInt(buf, primative.Int(), 10)
465+
466+
case reflect.Int32:
467+
buf = strconv.AppendInt(buf, primative.Int(), 10)
468+
469+
case reflect.Int64:
470+
buf = strconv.AppendInt(buf, primative.Int(), 10)
471+
472+
case reflect.Uint:
473+
buf = strconv.AppendUint(buf, primative.Uint(), 10)
474+
475+
case reflect.Uint8:
476+
buf = strconv.AppendUint(buf, primative.Uint(), 10)
477+
478+
case reflect.Uint16:
479+
buf = strconv.AppendUint(buf, primative.Uint(), 10)
480+
481+
case reflect.Uint32:
482+
buf = strconv.AppendUint(buf, primative.Uint(), 10)
483+
484+
case reflect.Uint64:
485+
buf = strconv.AppendUint(buf, primative.Uint(), 10)
486+
487+
case reflect.Float32:
488+
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 32)
489+
490+
case reflect.Float64:
491+
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 64)
492+
493+
case reflect.String:
494+
buf = quoteStringValue(buf, primative.String(), flavor)
495+
496+
case reflect.Slice, reflect.Array:
497+
if k == reflect.Slice && primative.IsNil() {
498+
buf = append(buf, "NULL"...)
499+
break
500+
}
501+
502+
if elem := primative.Type().Elem(); elem.Kind() != reflect.Uint8 {
503+
return nil, ErrInterpolateUnsupportedArgs
504+
}
505+
506+
var data []byte
507+
508+
// Bytes() will panic if primative is an array and cannot be addressed.
509+
// Copy all bytes to data as a fallback.
510+
if k == reflect.Array && !primative.CanAddr() {
511+
l := primative.Len()
512+
data = make([]byte, l)
513+
514+
for i := 0; i < l; i++ {
515+
data[i] = byte(primative.Index(i).Uint())
516+
}
517+
} else {
518+
data = primative.Bytes()
519+
}
520+
521+
switch flavor {
522+
case MySQL:
523+
buf = append(buf, "_binary"...)
524+
buf = quoteStringValue(buf, *(*string)(unsafe.Pointer(&data)), flavor)
525+
526+
case PostgreSQL:
527+
buf = append(buf, "E'\\\\x"...)
528+
buf = appendHex(buf, data)
529+
buf = append(buf, "'::bytea"...)
530+
531+
case SQLite:
532+
buf = append(buf, "X'"...)
533+
buf = appendHex(buf, data)
534+
buf = append(buf, '\'')
535+
536+
case SQLServer, CQL:
537+
buf = append(buf, "0x"...)
538+
buf = appendHex(buf, data)
539+
540+
case ClickHouse:
541+
buf = append(buf, "unhex('"...)
542+
buf = appendHex(buf, data)
543+
buf = append(buf, "')"...)
544+
}
545+
546+
default:
547+
return nil, ErrInterpolateUnsupportedArgs
548+
}
504549
}
505550

506551
return buf, nil

0 commit comments

Comments
 (0)