diff --git a/.github/workflows/gf.yml b/.github/workflows/gf.yml index 2fe78c1548a..ece472f1213 100644 --- a/.github/workflows/gf.yml +++ b/.github/workflows/gf.yml @@ -76,6 +76,13 @@ jobs: --health-timeout 5s --health-retries 10 + clickhouse-server: + image: yandex/clickhouse-server + ports: + - 9000:9000 + - 8123:8123 + - 9001:9001 + # strategy set strategy: diff --git a/contrib/drivers/clickhouse/clickhouse.go b/contrib/drivers/clickhouse/clickhouse.go index 3b77030a28e..35fd3c006fa 100644 --- a/contrib/drivers/clickhouse/clickhouse.go +++ b/contrib/drivers/clickhouse/clickhouse.go @@ -8,5 +8,300 @@ package clickhouse import ( - _ "github.com/ClickHouse/clickhouse-go" + "context" + "database/sql" + "errors" + "fmt" + "github.com/ClickHouse/clickhouse-go" + "strings" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" +) + +// Driver is the driver for postgresql database. +type Driver struct { + *gdb.Core +} + +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") ) + +func init() { + if err := gdb.Register(`clickhouse`, New()); err != nil { + panic(err) + } +} + +// New create and returns a driver that implements gdb.Driver, which supports operations for clickhouse. +func New() gdb.Driver { + return &Driver{} +} + +// New creates and returns a database object for clickhouse. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &Driver{ + Core: core, + }, nil +} + +// 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" + ) + if config.Link != "" { + source = config.Link + } 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)) + } 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)) + } + db, err := sql.Open(driver, source) + if err != nil { + return nil, err + } + + return db, nil +} + +// Tables retrieves and returns the tables of current schema. +// It's mainly used in cli tool chain for automatically generating the models. +func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { + var result gdb.Result + link, err := d.SlaveLink(schema...) + if err != nil { + return nil, err + } + query := fmt.Sprintf("select name from `system`.tables where database = '%s'", d.GetConfig().Name) + result, err = d.DoSelect(ctx, link, query) + if err != nil { + return + } + for _, m := range result { + tables = append(tables, m["name"].String()) + } + return +} + +// TableFields retrieves and returns the fields' information of specified table of current schema. +// Also see DriverMysql.TableFields. +func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { + charL, charR := d.GetChars() + table = gstr.Trim(table, charL+charR) + if gstr.Contains(table, " ") { + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") + } + useSchema := d.GetSchema() + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] + } + v := tableFieldsMap.GetOrSetFuncLock( + fmt.Sprintf(`clickhouse_table_fields_%s_%s@group:%s`, table, useSchema, d.GetGroup()), + func() interface{} { + var ( + result gdb.Result + link gdb.Link + ) + 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) + result, err = d.DoSelect(ctx, link, getColumnsSql) + if err != nil { + return nil + } + fields = make(map[string]*gdb.TableField) + for _, m := range result { + var ( + isNull = false + fieldType = m["type"].String() + ) + // in clickhouse , filed type like is Nullable(int) + fieldsResult, _ := gregex.MatchString(`^Nullable\((.*?)\)`, fieldType) + if len(fieldsResult) == 2 { + isNull = true + fieldType = fieldsResult[1] + } + fields[m["name"].String()] = &gdb.TableField{ + Index: m["position"].Int(), + Name: m["name"].String(), + Default: m["default_expression"].Val(), + Comment: m["comment"].String(), + //Key: m["Key"].String(), + Type: fieldType, + Null: isNull, + } + } + return fields + }, + ) + if v != nil { + fields = v.(map[string]*gdb.TableField) + } + return +} + +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *Driver) FilteredLink() string { + linkInfo := d.GetConfig().Link + if linkInfo == "" { + return "" + } + s, _ := gregex.ReplaceString( + `(.+?):(.+)@tcp(.+)`, + `$1:xxx@tcp$3`, + linkInfo, + ) + return s +} + +// PingMaster pings the master node to check authentication or keeps the connection alive. +func (d *Driver) PingMaster() error { + conn, err := d.Master() + if err != nil { + return err + } + return d.ping(conn) +} + +// PingSlave pings the slave node to check authentication or keeps the connection alive. +func (d *Driver) PingSlave() error { + conn, err := d.Slave() + if err != nil { + return err + } + return d.ping(conn) +} + +// ping Returns the Clickhouse specific error. +func (d *Driver) ping(conn *sql.DB) error { + err := conn.Ping() + if exception, ok := err.(*clickhouse.Exception); ok { + return errors.New(fmt.Sprintf("[%d]%s", exception.Code, exception.Message)) + } + return err +} + +// DoFilter handles the sql before posts it to database. +func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, originSql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + // replace STD SQL to Clickhouse SQL grammar + // MySQL eg: UPDATE visits SET xxx + // Clickhouse eg: ALTER TABLE visits UPDATE xxx + // MySQL eg: DELETE FROM VISIT + // Clickhouse eg: ALTER TABLE VISIT DELETE WHERE filter_expr + result, err := gregex.MatchString("(?i)^UPDATE|DELETE", originSql) + if err != nil { + return "", nil, err + } + if len(result) != 0 { + sqlSlice := strings.Split(originSql, " ") + if len(sqlSlice) < 3 { + return "", nil, errSQLNull + } + 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 originSql, args, nil +} + +// DoCommit commits current sql and arguments to underlying sql driver. +func (d *Driver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) { + ctx = d.InjectIgnoreResult(ctx) + return d.Core.DoCommit(ctx, in) +} + +func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { + var ( + keys []string // Field names. + valueHolder = make([]string, 0) + ) + // Handle the field names and placeholders. + for k := range list[0] { + keys = append(keys, k) + valueHolder = append(valueHolder, "?") + } + // Prepare the batch result pointer. + var ( + charL, charR = d.Core.GetChars() + keysStr = charL + strings.Join(keys, charR+","+charL) + charR + holderStr = strings.Join(valueHolder, ",") + tx = &gdb.TX{} + stdSqlResult sql.Result + stmt *gdb.Stmt + ) + tx, err = d.Core.Begin(ctx) + if err != nil { + return + } + stmt, err = tx.Prepare(fmt.Sprintf( + "INSERT INTO %s(%s) VALUES (%s)", + d.QuotePrefixTableName(table), keysStr, + holderStr, + )) + if err != nil { + return + } + for i := 0; i < len(list); i++ { + params := []interface{}{} // Values that will be committed to underlying database driver. + for _, k := range keys { + params = append(params, list[i][k]) + } + // Prepare is allowed to execute only once in a transaction opened by clickhouse + stdSqlResult, err = stmt.ExecContext(ctx, params...) + if err != nil { + return stdSqlResult, err + } + } + return stdSqlResult, tx.Commit() +} + +// 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 +} + +// InsertAndGetId Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE. +func (d *Driver) InsertAndGetId(ctx context.Context, table string, data interface{}, batch ...int) (int64, error) { + return 0, errUnsupportedInsertGetId +} + +// Replace Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE. +func (d *Driver) Replace(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) { + return nil, errUnsupportedReplace +} + +func (d *Driver) Begin(ctx context.Context) (tx *gdb.TX, err error) { + return nil, errUnsupportedBegin +} + +func (d *Driver) Transaction(ctx context.Context, f func(ctx context.Context, tx *gdb.TX) error) error { + return errUnsupportedTransaction +} diff --git a/contrib/drivers/clickhouse/clickhouse_test.go b/contrib/drivers/clickhouse/clickhouse_test.go new file mode 100644 index 00000000000..3ea9f3cc22a --- /dev/null +++ b/contrib/drivers/clickhouse/clickhouse_test.go @@ -0,0 +1,254 @@ +package clickhouse + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/grand" +) + +// table DDL +// CREATE TABLE visits +// ( +// id UInt64, +// duration Float64, +// url String, +// created DateTime +//) +// ENGINE = MergeTree() +// PRIMARY KEY id +// ORDER BY id +func InitClickhouse() gdb.DB { + connect, err := gdb.New(gdb.ConfigNode{ + Host: "127.0.0.1", + Port: "9000", + User: "default", + Name: "default", + Type: "clickhouse", + Debug: true, + }) + gtest.AssertNil(err) + gtest.AssertNE(connect, nil) + return connect +} + +func TestDriverClickhouse_Create(t *testing.T) { + gtest.AssertNil(createClickhouseTable(InitClickhouse())) +} + +func createClickhouseTable(connect gdb.DB) error { + sqlStr := "CREATE TABLE IF NOT EXISTS visits (id UInt64,duration Float64,url String,created DateTime) ENGINE = MergeTree() PRIMARY KEY id ORDER BY id" + _, err := connect.Exec(context.Background(), sqlStr) + return err +} + +func dropClickhouseTable(conn gdb.DB) { + sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `visits`") + _, _ = conn.Exec(context.Background(), sqlStr) +} + +func TestDriverClickhouse_New(t *testing.T) { + connect := InitClickhouse() + gtest.AssertNE(connect, nil) + gtest.AssertNil(connect.PingMaster()) + gtest.AssertNil(connect.PingSlave()) +} + +func TestDriverClickhouse_Tables(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + tables, err := connect.Tables(context.Background()) + gtest.AssertNil(err) + gtest.AssertNE(len(tables), 0) +} + +func TestDriverClickhouse_Transaction(t *testing.T) { + connect := InitClickhouse() + defer dropClickhouseTable(connect) + gtest.AssertNE(connect.Transaction(context.Background(), func(ctx context.Context, tx *gdb.TX) error { + return nil + }), nil) +} + +func TestDriverClickhouse_DoDelete(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + _, err := connect.Model("visits").Where("created >", "2021-01-01 00:00:00").Delete() + gtest.AssertNil(err) +} + +func TestDriverClickhouse_DoUpdate(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + _, err := connect.Model("visits").Where("created > ", "2021-01-01 15:15:15").Data(g.Map{ + "created": time.Now().Format("2006-01-02 15:04:05"), + }).Update() + gtest.AssertNil(err) + _, err = connect.Model("visits").Data(g.Map{ + "created": time.Now().Format("2006-01-02 15:04:05"), + }).Update() + gtest.AssertNE(err, nil) + _, err = connect.Model("visits").Update() + gtest.AssertNE(err, nil) +} + +func TestDriverClickhouse_Select(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + data, err := connect.Model("visits").All() + gtest.AssertNil(err) + gtest.AssertEQ(len(data), 0) +} + +func TestDriver_InsertIgnore(t *testing.T) { + connect := InitClickhouse() + _, err := connect.InsertIgnore(context.Background(), "", nil) + gtest.AssertEQ(err, errUnsupportedInsertIgnore) +} + +func TestDriver_InsertAndGetId(t *testing.T) { + connect := InitClickhouse() + _, err := connect.InsertAndGetId(context.Background(), "", nil) + gtest.AssertEQ(err, errUnsupportedInsertGetId) +} + +func TestDriver_Replace(t *testing.T) { + connect := InitClickhouse() + _, err := connect.Replace(context.Background(), "", nil) + gtest.AssertEQ(err, errUnsupportedReplace) +} + +func TestDriverClickhouse_DoInsertOne(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + _, err := connect.Model("visits").Data(g.Map{ + "id": grand.Intn(999), + "duration": float64(grand.Intn(999)), + "url": gconv.String(grand.Intn(999)), + "created": time.Now().Format("2006-01-02 15:04:05"), + }).Insert() + gtest.AssertNil(err) +} + +func TestDriver_DoInsertMany(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + defer dropClickhouseTable(connect) + tx, err := connect.Begin(context.Background()) + gtest.AssertEQ(err, errUnsupportedBegin) + gtest.AssertNil(tx) +} + +func TestDriverClickhouse_DoInsert(t *testing.T) { + connect := InitClickhouse() + gtest.AssertEQ(createClickhouseTable(connect), nil) + type insertItem struct { + Id int `orm:"id"` + Duration float64 `orm:"duration"` + Url string `orm:"url"` + Created string `orm:"created"` + } + var ( + insertUrl = "https://goframe.org" + total = 0 + item = insertItem{ + Id: 0, + Duration: 1, + Url: insertUrl, + Created: time.Now().Format("2006-01-02 15:04:05"), + } + ) + _, err := connect.Model("visits").Data(item).Insert() + gtest.AssertNil(err) + _, err = connect.Model("visits").Data(item).Save() + gtest.AssertNil(err) + total, err = connect.Model("visits").Count() + gtest.AssertNil(err) + gtest.AssertEQ(total, 2) + list := []*insertItem{} + for i := 0; i < 50; i++ { + list = append(list, &insertItem{ + Id: grand.Intn(999), + Duration: float64(grand.Intn(999)), + Url: insertUrl, + Created: time.Now().Format("2006-01-02 15:04:05"), + }) + } + _, err = connect.Model("visits").Data(list).Insert() + gtest.AssertNil(err) + _, err = connect.Model("visits").Data(list).Save() + gtest.AssertNil(err) + total, err = connect.Model("visits").Count() + gtest.AssertNil(err) + gtest.AssertEQ(total, 102) + dropClickhouseTable(connect) +} + +func TestDriverClickhouse_DoExec(t *testing.T) { + connect := InitClickhouse() + gtest.AssertNil(createClickhouseTable(connect)) + defer dropClickhouseTable(connect) + sqlStr := "OPTIMIZE table visits" + _, err := connect.Exec(context.Background(), sqlStr) + gtest.AssertNil(err) +} + +func TestDriver_DoFilter(t *testing.T) { + rawSQL := "select * from visits where 1 = 1" + this := Driver{} + replaceSQL, _, err := this.DoFilter(nil, nil, rawSQL, nil) + gtest.AssertNil(err) + gtest.AssertEQ(rawSQL, replaceSQL) + rawSQL = "update visit set url = '1'" + replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, nil) + gtest.AssertNil(err) + // this SQL can't run ,clickhouse will report an error because there is no WHERE statement + gtest.AssertEQ(replaceSQL, "ALTER TABLE visit update url = '1'") + rawSQL = "delete from visit" + replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, nil) + gtest.AssertNil(err) + // this SQL can't run ,clickhouse will report an error because there is no WHERE statement + gtest.AssertEQ(replaceSQL, "ALTER TABLE visit delete") + + rawSQL = "update visit set url = '1' where url = '0'" + replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, nil) + gtest.AssertNil(err) + // this SQL can't run ,clickhouse will report an error because there is no WHERE statement + gtest.AssertEQ(replaceSQL, "ALTER TABLE visit update url = '1' where url = '0'") + rawSQL = "delete from visit where url='0'" + replaceSQL, _, err = this.DoFilter(nil, nil, rawSQL, nil) + gtest.AssertNil(err) + // this SQL can't run ,clickhouse will report an error because there is no WHERE statement + gtest.AssertEQ(replaceSQL, "ALTER TABLE visit delete where url='0'") +} + +func TestDriver_TableFields(t *testing.T) { + connect := InitClickhouse() + gtest.AssertNil(createClickhouseTable(connect)) + defer dropClickhouseTable(connect) + field, err := connect.TableFields(context.Background(), "visits") + gtest.AssertNil(err) + gtest.AssertEQ(len(field), 4) + gtest.AssertNQ(field, nil) +} + +func TestDriver_OpenLink(t *testing.T) { + connect, err := gdb.New(gdb.ConfigNode{ + Link: "clickhouse://default@127.0.0.1:9000/default?dial_timeout=200ms&max_execution_time=60&skip_verify=true&secure=false&compress=true", + Type: "clickhouse", + }) + gtest.AssertNil(err) + gtest.AssertNE(connect, nil) + gtest.AssertNil(connect.PingMaster()) +} diff --git a/contrib/drivers/clickhouse/go.mod b/contrib/drivers/clickhouse/go.mod index 45192d7aa2d..886a68985df 100644 --- a/contrib/drivers/clickhouse/go.mod +++ b/contrib/drivers/clickhouse/go.mod @@ -4,10 +4,7 @@ go 1.15 require ( github.com/ClickHouse/clickhouse-go v1.5.2 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.1.0 // indirect - github.com/stretchr/testify v1.7.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + github.com/gogf/gf/v2 v2.0.0-00010101000000-000000000000 ) +replace github.com/gogf/gf/v2 => ../../../ diff --git a/contrib/drivers/clickhouse/go.sum b/contrib/drivers/clickhouse/go.sum index 4221d34244b..db27d0498c2 100644 --- a/contrib/drivers/clickhouse/go.sum +++ b/contrib/drivers/clickhouse/go.sum @@ -1,32 +1,166 @@ +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/ClickHouse/clickhouse-go v1.5.2 h1:yXgaOZ8WEHrd+okvZXjzulSt1zS33nM4ujfx9lVncl8= github.com/ClickHouse/clickhouse-go v1.5.2/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE= +github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= +go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= +go.opentelemetry.io/otel/sdk v1.0.0 h1:BNPMYUONPNbLneMttKSjQhOTlFLOD9U22HNG1KrIN2Y= +go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= +go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4= +go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/database/gdb/gdb_core_ctx.go b/database/gdb/gdb_core_ctx.go index d41db81334d..458d1527efd 100644 --- a/database/gdb/gdb_core_ctx.go +++ b/database/gdb/gdb_core_ctx.go @@ -26,6 +26,12 @@ type internalCtxData struct { const ( internalCtxDataKeyInCtx gctx.StrKey = "InternalCtxData" + + // `ignoreResultKeyInCtx` is a mark for some db drivers that do not support `RowsAffected` function, + // for example: `clickhouse`. The `clickhouse` does not support fetching insert/update results, + // but returns errors when execute `RowsAffected`. It here ignores the calling of `RowsAffected` + // to avoid triggering errors, rather than ignoring errors after they are triggered. + ignoreResultInCtx gctx.StrKey = "IgnoreResult" ) func (c *Core) injectInternalCtxData(ctx context.Context) context.Context { @@ -38,6 +44,22 @@ func (c *Core) injectInternalCtxData(ctx context.Context) context.Context { }) } +func (c *Core) InjectIgnoreResult(ctx context.Context) context.Context { + if ctx.Value(ignoreResultInCtx) != nil { + return ctx + } + return context.WithValue(ctx, ignoreResultInCtx, &internalCtxData{ + DB: c.db, + }) +} + +func (c *Core) getIgnoreResultFromCtx(ctx context.Context) *internalCtxData { + if v := ctx.Value(ignoreResultInCtx); v != nil { + return v.(*internalCtxData) + } + return nil +} + func (c *Core) getInternalCtxDataFromCtx(ctx context.Context) *internalCtxData { if v := ctx.Value(internalCtxDataKeyInCtx); v != nil { return v.(*internalCtxData) diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go index af90cb1698c..c474dd51d81 100644 --- a/database/gdb/gdb_core_underlying.go +++ b/database/gdb/gdb_core_underlying.go @@ -11,6 +11,9 @@ import ( "context" "database/sql" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "github.com/gogf/gf/v2" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/errors/gcode" @@ -18,8 +21,6 @@ import ( "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/guid" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" ) // Query commits one query SQL to underlying driver and returns the execution result. @@ -260,7 +261,7 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp } // Result handling. switch { - case sqlResult != nil: + case sqlResult != nil && c.getIgnoreResultFromCtx(ctx) == nil: rowsAffected, err = sqlResult.RowsAffected() out.Result = sqlResult