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

upgrade ClickHouse dependencies to V2 #1772

Merged
merged 22 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from 15 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
3 changes: 1 addition & 2 deletions contrib/drivers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ Note:
- It does not support `InsertIgnore/InsertGetId` features.
- It does not support `Save/Replace` features.
- It does not support `Transaction` feature.
- It does not support `Transaction` feature.

- It does not support `RowsAffected` feature.

# Custom Drivers

Expand Down
136 changes: 97 additions & 39 deletions contrib/drivers/clickhouse/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
"net/url"
"strings"

"github.com/ClickHouse/clickhouse-go"

"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
Expand All @@ -33,12 +33,18 @@ type Driver struct {
var (
// tableFieldsMap caches the table information retrieved from database.
tableFieldsMap = gmap.New(true)
errUnsupportedInsertIgnore = errors.New("unsupported method: InsertIgnore")
errUnsupportedInsertGetId = errors.New("unsupported method: InsertGetId")
errUnsupportedReplace = errors.New("unsupported method: Replace")
errUnsupportedBegin = errors.New("unsupported method: Begin")
errUnsupportedTransaction = errors.New("unsupported method: Transaction")
errSQLNull = errors.New("SQL cannot be null")
errUnsupportedInsertIgnore = errors.New("unsupported method:InsertIgnore")
errUnsupportedInsertGetId = errors.New("unsupported method:InsertGetId")
errUnsupportedReplace = errors.New("unsupported method:Replace")
errUnsupportedBegin = errors.New("unsupported method:Begin")
errUnsupportedTransaction = errors.New("unsupported method:Transaction")
)

const (
updateFilterPattern = `(?i)UPDATE[\s]+?(\w+[\.]?\w+)[\s]+?SET`
deleteFilterPattern = `(?i)DELETE[\s]+?FROM[\s]+?(\w+[\.]?\w+)`
filterTypePattern = `(?i)^UPDATE|DELETE`
replaceSchemaPattern = `@(.+?)/([\w\.\-]+)+`
)

func init() {
Expand All @@ -63,26 +69,30 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
// Open creates and returns an underlying sql.DB object for clickhouse.
func (d *Driver) Open(config *gdb.ConfigNode) (*sql.DB, error) {
var (
source string
driver = "clickhouse"
)
// clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
if config.Link != "" {
source = config.Link
// Custom changing the schema in runtime.
if config.Name != "" {
source, _ = gregex.ReplaceString(`@(.+?)/([\w\.\-]+)+`, "@$1/"+config.Name, source)
config.Link, _ = gregex.ReplaceString(replaceSchemaPattern, "@$1/"+config.Name, config.Link)
} else {
// If no schema, the link is matched for replacement
dbName, _ := gregex.MatchString(replaceSchemaPattern, config.Link)
if len(dbName) > 0 {
config.Name = dbName[len(dbName)-1]
}
}
} else if config.Pass != "" {
source = fmt.Sprintf(
"clickhouse://%s:%s@%s:%s/%s?charset=%s&debug=%s",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset, gconv.String(config.Debug))
config.Link = fmt.Sprintf(
"clickhouse://%s:%s@%s:%s/%s?charset=%s&debug=%t",
config.User, url.PathEscape(config.Pass), config.Host, config.Port, config.Name, config.Charset, config.Debug)
} else {
source = fmt.Sprintf(
"clickhouse://%s@%s:%s/%s?charset=%s&debug=%s",
config.User, config.Host, config.Port, config.Name, config.Charset, gconv.String(config.Debug))
config.Link = fmt.Sprintf(
"clickhouse://%s@%s:%s/%s?charset=%s&debug=%t",
config.User, config.Host, config.Port, config.Name, config.Charset, config.Debug)
}
db, err := sql.Open(driver, source)
db, err := sql.Open(driver, config.Link)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -133,7 +143,7 @@ func (d *Driver) TableFields(
if link, err = d.SlaveLink(useSchema); err != nil {
return nil
}
getColumnsSql := fmt.Sprintf("select name,position,default_expression,comment from `system`.columns c where database = '%s' and `table` = '%s'", d.GetConfig().Name, table)
getColumnsSql := fmt.Sprintf("select name,position,default_expression,comment,type from `system`.columns c where database = '%s' and `table` = '%s'", d.GetConfig().Name, table)
result, err = d.DoSelect(ctx, link, getColumnsSql)
if err != nil {
return nil
Expand Down Expand Up @@ -215,29 +225,54 @@ func (d *Driver) ping(conn *sql.DB) error {
func (d *Driver) DoFilter(
ctx context.Context, link gdb.Link, originSql string, args []interface{},
) (newSql string, newArgs []interface{}, err error) {
// It replaces STD SQL to Clickhouse SQL grammar.
// MySQL eg: UPDATE `table` SET xxx
// Clickhouse eg: ALTER TABLE `table` UPDATE xxx
// MySQL eg: DELETE FROM `table`
// Clickhouse eg: ALTER TABLE `table` DELETE WHERE filter_expr
result, err := gregex.MatchString("(?i)^UPDATE|DELETE", originSql)
if err != nil {
return "", nil, err
if len(args) == 0 {
return originSql, args, nil
}
if len(result) != 0 {
sqlSlice := strings.Split(originSql, " ")
if len(sqlSlice) < 3 {
return "", nil, errSQLNull

var index int
// Convert placeholder char '?' to string "$x".
originSql, _ = gregex.ReplaceStringFunc(`\?`, originSql, func(s string) string {
index++
return fmt.Sprintf(`$%d`, index)
})

// Only SQL generated through the framework is processed.
if !d.GetNeedParsedSqlFromCtx(ctx) {
return originSql, args, nil
}

// replace STD SQL to Clickhouse SQL grammar
var (
modeRes, _ = gregex.MatchString(filterTypePattern, strings.TrimSpace(originSql))
)
if len(modeRes) == 0 {
return originSql, args, nil
}

// Only delete/ UPDATE statements require filter
switch strings.ToUpper(modeRes[0]) {
case "UPDATE":
// MySQL eg: UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]
// Clickhouse eg: ALTER TABLE [db.]table UPDATE column1 = expr1 [, ...] WHERE filter_expr
newSql, err = gregex.ReplaceStringFuncMatch(updateFilterPattern, originSql, func(s []string) string {
return fmt.Sprintf("ALTER TABLE %s UPDATE", s[1])
})
if err != nil {
return "", nil, err
}
ck := []string{"ALTER", "TABLE"}
switch strings.ToUpper(result[0]) {
case "UPDATE":
sqlSlice = append(append(append(ck, sqlSlice[1]), result[0]), sqlSlice[3:]...)
return strings.Join(sqlSlice, " "), args, nil
case "DELETE":
sqlSlice = append(append(append(ck, sqlSlice[2]), result[0]), sqlSlice[3:]...)
return strings.Join(sqlSlice, " "), args, nil
return newSql, args, nil

case "DELETE":
// MySQL eg: DELETE FROM table_name [WHERE Clause]
// Clickhouse eg: ALTER TABLE [db.]table [ON CLUSTER cluster] DELETE WHERE filter_expr
newSql, err = gregex.ReplaceStringFuncMatch(deleteFilterPattern, originSql, func(s []string) string {
return fmt.Sprintf("ALTER TABLE %s DELETE", s[1])
})
if err != nil {
return "", nil, err
}
return newSql, args, nil

}
return originSql, args, nil
}
Expand Down Expand Up @@ -295,6 +330,29 @@ func (d *Driver) DoInsert(
return stdSqlResult, tx.Commit()
}

// ConvertDataForRecord converting for any data that will be inserted into table/collection as a record.
func (d *Driver) ConvertDataForRecord(ctx context.Context, value interface{}) map[string]interface{} {
// Clickhouse does not need to preprocess the value and can be inserted directly
// So it is not processed here
return gconv.Map(value, gdb.OrmTagForStruct)
}

func (d *Driver) ConvertDataForRecordValue(ctx context.Context, value interface{}) interface{} {
// Clickhouse does not need to preprocess the value and can be inserted directly
// So it is not processed here
return value
}

func (d *Driver) DoDelete(ctx context.Context, link gdb.Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
ctx = d.InjectNeedParsedSql(ctx)
return d.Core.DoDelete(ctx, link, table, condition, args...)
}

func (d *Driver) DoUpdate(ctx context.Context, link gdb.Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
ctx = d.InjectNeedParsedSql(ctx)
return d.Core.DoUpdate(ctx, link, table, data, condition, args...)
}

// InsertIgnore Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
func (d *Driver) InsertIgnore(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) {
return nil, errUnsupportedInsertIgnore
Expand Down
Loading