diff --git a/ddl/db_partition_test.go b/ddl/db_partition_test.go index 606aa6fa67063..b597b0ce95700 100644 --- a/ddl/db_partition_test.go +++ b/ddl/db_partition_test.go @@ -26,6 +26,7 @@ import ( "github.com/pingcap/parser/model" tmysql "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/ddl/testutil" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" @@ -38,7 +39,6 @@ import ( "github.com/pingcap/tidb/util/admin" "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/testkit" - "github.com/pingcap/tidb/util/testutil" ) func (s *testIntegrationSuite) TestCreateTableWithPartition(c *C) { diff --git a/ddl/db_test.go b/ddl/db_test.go index 6a63a181ff67e..5b4458dc5798d 100644 --- a/ddl/db_test.go +++ b/ddl/db_test.go @@ -32,6 +32,7 @@ import ( tmysql "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/ddl" + testddlutil "github.com/pingcap/tidb/ddl/testutil" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" @@ -608,7 +609,7 @@ func (s *testDBSuite) testAddIndex(c *C, testPartition bool, createTableSQL stri s.mustExec(c, sql) otherKeys = append(otherKeys, v) - testutil.SessionExecInGoroutine(c, s.store, "create index c3_index on test_add_index (c3)", done) + testddlutil.SessionExecInGoroutine(c, s.store, "create index c3_index on test_add_index (c3)", done) deletedKeys := make(map[int]struct{}) @@ -753,7 +754,7 @@ func (s *testDBSuite) TestDropIndex(c *C) { } c.Assert(c3idx, NotNil) - testutil.SessionExecInGoroutine(c, s.store, "drop index c3_index on test_drop_index", done) + testddlutil.SessionExecInGoroutine(c, s.store, "drop index c3_index on test_drop_index", done) ticker := time.NewTicker(s.lease / 2) defer ticker.Stop() @@ -924,7 +925,7 @@ func (s *testDBSuite) testAddColumn(c *C) { s.mustExec(c, "insert into t2 values (?, ?, ?)", i, i, i) } - testutil.SessionExecInGoroutine(c, s.store, "alter table t2 add column c4 int default -1", done) + testddlutil.SessionExecInGoroutine(c, s.store, "alter table t2 add column c4 int default -1", done) ticker := time.NewTicker(s.lease / 2) defer ticker.Stop() @@ -1059,7 +1060,7 @@ func (s *testDBSuite) testDropColumn(c *C) { } // get c4 column id - testutil.SessionExecInGoroutine(c, s.store, "alter table t2 drop column c4", done) + testddlutil.SessionExecInGoroutine(c, s.store, "alter table t2 drop column c4", done) ticker := time.NewTicker(s.lease / 2) defer ticker.Stop() @@ -1118,9 +1119,9 @@ func (s *testDBSuite) TestDropColumn(c *C) { for i := 0; i < num/2; i++ { multiDDL = append(multiDDL, "alter table t2 add column c4 int", "alter table t2 drop column c4") } - execMultiSQLInGoroutine(c, s.store, "drop_col_db", multiDDL, ddlDone) + testddlutil.ExecMultiSQLInGoroutine(c, s.store, "drop_col_db", multiDDL, ddlDone) for i := 0; i < num; i++ { - execMultiSQLInGoroutine(c, s.store, "drop_col_db", []string{"insert into t2 set c1 = 1, c2 = 1, c3 = 1, c4 = 1"}, dmlDone) + testddlutil.ExecMultiSQLInGoroutine(c, s.store, "drop_col_db", []string{"insert into t2 set c1 = 1, c2 = 1, c3 = 1, c4 = 1"}, dmlDone) } for i := 0; i < num; i++ { select { @@ -1484,7 +1485,7 @@ func (s *testDBSuite) TestAddNotNullColumn(c *C) { s.tk.MustExec("create table tnn (c1 int primary key auto_increment, c2 int)") s.tk.MustExec("insert tnn (c2) values (0)" + strings.Repeat(",(0)", 99)) done := make(chan error, 1) - testutil.SessionExecInGoroutine(c, s.store, "alter table tnn add column c3 int not null default 3", done) + testddlutil.SessionExecInGoroutine(c, s.store, "alter table tnn add column c3 int not null default 3", done) updateCnt := 0 out: for { diff --git a/ddl/failtest/fail_db_test.go b/ddl/failtest/fail_db_test.go index 552f00d22fa0d..491704bca3eb9 100644 --- a/ddl/failtest/fail_db_test.go +++ b/ddl/failtest/fail_db_test.go @@ -28,6 +28,7 @@ import ( "github.com/pingcap/parser" "github.com/pingcap/parser/model" "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/ddl/testutil" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/session" @@ -37,7 +38,6 @@ import ( "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" - "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { diff --git a/ddl/testutil/testutil.go b/ddl/testutil/testutil.go new file mode 100644 index 0000000000000..1d9e224bc6854 --- /dev/null +++ b/ddl/testutil/testutil.go @@ -0,0 +1,44 @@ +package testutil + +import ( + "context" + + "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/session" +) + +// SessionExecInGoroutine export for testing. +func SessionExecInGoroutine(c *check.C, s kv.Storage, sql string, done chan error) { + ExecMultiSQLInGoroutine(c, s, "test_db", []string{sql}, done) +} + +// ExecMultiSQLInGoroutine exports for testing. +func ExecMultiSQLInGoroutine(c *check.C, s kv.Storage, dbName string, multiSQL []string, done chan error) { + go func() { + se, err := session.CreateSession4Test(s) + if err != nil { + done <- errors.Trace(err) + return + } + defer se.Close() + _, err = se.Execute(context.Background(), "use "+dbName) + if err != nil { + done <- errors.Trace(err) + return + } + for _, sql := range multiSQL { + rs, err := se.Execute(context.Background(), sql) + if err != nil { + done <- errors.Trace(err) + return + } + if rs != nil { + done <- errors.Errorf("RecordSet should be empty.") + return + } + done <- nil + } + }() +} diff --git a/docs/tidb_http_api.md b/docs/tidb_http_api.md index a65f64bae4b49..26af33b9d17ac 100644 --- a/docs/tidb_http_api.md +++ b/docs/tidb_http_api.md @@ -20,6 +20,12 @@ curl http://{TiDBIP}:10080/regions/meta ``` +1. Get the table/index of hot regions + + ```shell + curl http://{TiDBIP}:10080/regions/hot + ``` + 1. Get the information of a specific region by ID ```shell diff --git a/executor/prepared.go b/executor/prepared.go index d2821417a77a0..28445b1c770da 100644 --- a/executor/prepared.go +++ b/executor/prepared.go @@ -114,7 +114,11 @@ func (e *PrepareExec) Next(ctx context.Context, chk *chunk.Chunk) error { } else { p := parser.New() p.EnableWindowFunc(vars.EnableWindowFunction) - stmts, err = p.Parse(e.sqlText, charset, collation) + var warns []error + stmts, warns, err = p.Parse(e.sqlText, charset, collation) + for _, warn := range warns { + e.ctx.GetSessionVars().StmtCtx.AppendWarning(warn) + } } if err != nil { return errors.Trace(err) diff --git a/expression/builtin_encryption.go b/expression/builtin_encryption.go index 2a6020d964e8c..cf4744efb8d0e 100644 --- a/expression/builtin_encryption.go +++ b/expression/builtin_encryption.go @@ -93,6 +93,9 @@ var aesModes = map[string]*aesModeAttr{ "aes-128-cbc": {"cbc", 16, true}, "aes-192-cbc": {"cbc", 24, true}, "aes-256-cbc": {"cbc", 32, true}, + "aes-128-cfb": {"cfb", 16, true}, + "aes-192-cfb": {"cfb", 24, true}, + "aes-256-cfb": {"cfb", 32, true}, } type aesDecryptFunctionClass struct { @@ -209,6 +212,8 @@ func (b *builtinAesDecryptIVSig) evalString(row chunk.Row) (string, bool, error) switch b.modeName { case "cbc": plainText, err = encrypt.AESDecryptWithCBC([]byte(cryptStr), key, []byte(iv)) + case "cfb": + plainText, err = encrypt.AESDecryptWithCFB([]byte(cryptStr), key, []byte(iv)) default: return "", true, errors.Errorf("unsupported block encryption mode - %v", b.modeName) } @@ -332,6 +337,8 @@ func (b *builtinAesEncryptIVSig) evalString(row chunk.Row) (string, bool, error) switch b.modeName { case "cbc": cipherText, err = encrypt.AESEncryptWithCBC([]byte(str), key, []byte(iv)) + case "cfb": + cipherText, err = encrypt.AESEncryptWithCFB([]byte(str), key, []byte(iv)) default: return "", true, errors.Errorf("unsupported block encryption mode - %v", b.modeName) } diff --git a/expression/builtin_encryption_test.go b/expression/builtin_encryption_test.go index 9c2e17d2c8453..e66b9053dc2cb 100644 --- a/expression/builtin_encryption_test.go +++ b/expression/builtin_encryption_test.go @@ -101,6 +101,13 @@ var aesTests = []struct { {"aes-256-cbc", "pingcap", []interface{}{"1234567890123456", "1234567890123456"}, "5D0E22C1E77523AEF5C3E10B65653C8F"}, {"aes-256-cbc", "pingcap", []interface{}{"12345678901234561234567890123456", "1234567890123456"}, "A26BA27CA4BE9D361D545AA84A17002D"}, {"aes-256-cbc", "pingcap", []interface{}{"1234567890123456", "12345678901234561234567890123456"}, "5D0E22C1E77523AEF5C3E10B65653C8F"}, + // test for cfb + {"aes-128-cfb", "pingcap", []interface{}{"1234567890123456", "1234567890123456"}, "0515A36BBF3DE0"}, + {"aes-128-cfb", "pingcap", []interface{}{"123456789012345678901234", "1234567890123456"}, "C2A93A93818546"}, + {"aes-192-cfb", "pingcap", []interface{}{"1234567890123456", "1234567890123456"}, "FE09DCCF14D458"}, + {"aes-256-cfb", "pingcap", []interface{}{"1234567890123456", "1234567890123456"}, "2E70FCAC0C0834"}, + {"aes-256-cfb", "pingcap", []interface{}{"12345678901234561234567890123456", "1234567890123456"}, "83E2B30A71F011"}, + {"aes-256-cfb", "pingcap", []interface{}{"1234567890123456", "12345678901234561234567890123456"}, "2E70FCAC0C0834"}, } func (s *testEvaluatorSuite) TestAESEncrypt(c *C) { diff --git a/expression/integration_test.go b/expression/integration_test.go index 1e6b24702f599..5e5f73d7770f9 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -3697,6 +3697,16 @@ func (s *testIntegrationSuite) TestDecimalMul(c *C) { res.Check(testkit.Rows("0.55125221922461136")) } +func (s *testIntegrationSuite) TestUnknowHintIgnore(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("USE test") + tk.MustExec("create table t(a int)") + tk.MustQuery("select /*+ unkown_hint(c1)*/ 1").Check(testkit.Rows("1")) + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 line 1 column 28 near \" 1\" (total length 30)")) + _, err := tk.Exec("select 1 from /*+ test1() */ t") + c.Assert(err, NotNil) +} + func (s *testIntegrationSuite) TestValuesInNonInsertStmt(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec(`use test;`) diff --git a/expression/simple_rewriter.go b/expression/simple_rewriter.go index d0674fb87b33f..7172b699e85ee 100644 --- a/expression/simple_rewriter.go +++ b/expression/simple_rewriter.go @@ -37,7 +37,10 @@ type simpleRewriter struct { // The expression string must only reference the column in table Info. func ParseSimpleExprWithTableInfo(ctx sessionctx.Context, exprStr string, tableInfo *model.TableInfo) (Expression, error) { exprStr = "select " + exprStr - stmts, err := parser.New().Parse(exprStr, "", "") + stmts, warns, err := parser.New().Parse(exprStr, "", "") + for _, warn := range warns { + ctx.GetSessionVars().StmtCtx.AppendWarning(warn) + } if err != nil { return nil, err } @@ -72,7 +75,10 @@ func RewriteSimpleExprWithTableInfo(ctx sessionctx.Context, tbl *model.TableInfo // The expression string must only reference the column in the given schema. func ParseSimpleExprsWithSchema(ctx sessionctx.Context, exprStr string, schema *Schema) ([]Expression, error) { exprStr = "select " + exprStr - stmts, err := parser.New().Parse(exprStr, "", "") + stmts, warns, err := parser.New().Parse(exprStr, "", "") + for _, warn := range warns { + ctx.GetSessionVars().StmtCtx.AppendWarning(warn) + } if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 6fa2dae0af852..2d1fbfa2eb872 100644 --- a/go.mod +++ b/go.mod @@ -48,8 +48,8 @@ require ( github.com/pingcap/errors v0.11.0 github.com/pingcap/gofail v0.0.0-20181217135706-6a951c1e42c3 github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e - github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26 - github.com/pingcap/parser v0.0.0-20181218071912-deacf026787e + github.com/pingcap/kvproto v0.0.0-20181203065228-c14302da291c + github.com/pingcap/parser v0.0.0-20181221050948-947d4ab3052f github.com/pingcap/pd v2.1.0-rc.4+incompatible github.com/pingcap/tidb-tools v2.1.1-0.20181218072513-b2235d442b06+incompatible github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323 diff --git a/go.sum b/go.sum index 7aea00b386ac4..76b54a1e80e40 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,7 @@ github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sql-driver/mysql v0.0.0-20170715192408-3955978caca4 h1:3DFRjZdCDhzvxDf0U6/1qAryzOqD7Y5iAj0DJRRl1bs= github.com/go-sql-driver/mysql v0.0.0-20170715192408-3955978caca4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/gogo/protobuf v0.0.0-20180717141946-636bf0302bc9/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= @@ -57,6 +58,7 @@ github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= @@ -123,14 +125,15 @@ github.com/pingcap/gofail v0.0.0-20181217135706-6a951c1e42c3 h1:04yuCf5NMvLU8rB2 github.com/pingcap/gofail v0.0.0-20181217135706-6a951c1e42c3/go.mod h1:DazNTg0PTldtpsQiT9I5tVJwV1onHMKBBgXzmJUlMns= github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e h1:P73/4dPCL96rGrobssy1nVy2VaVpNCuLpCbr+FEaTA8= github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw= -github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26 h1:JK4VLNYbSn36QSbCnqALi2ySXdH0DfcMssT/zmLf4Ls= -github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26/go.mod h1:0gwbe1F2iBIjuQ9AH0DbQhL+Dpr5GofU8fgYyXk+ykk= -github.com/pingcap/parser v0.0.0-20181218071912-deacf026787e h1:jKibIs55HR7OMo62uhjA6Bfx3GK+rbHK4Gfd4/8aTzk= -github.com/pingcap/parser v0.0.0-20181218071912-deacf026787e/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= +github.com/pingcap/kvproto v0.0.0-20181203065228-c14302da291c h1:Qf5St5XGwKgKQLar9lEXoeO0hJMVaFBj3JqvFguWtVg= +github.com/pingcap/kvproto v0.0.0-20181203065228-c14302da291c/go.mod h1:Ja9XPjot9q4/3JyCZodnWDGNXt4pKemhIYCvVJM7P24= +github.com/pingcap/parser v0.0.0-20181221050948-947d4ab3052f h1:X2ZRYBERoJ5VDp7CdtXWfwbKqbeYn2kkdGA0b5/VwJ8= +github.com/pingcap/parser v0.0.0-20181221050948-947d4ab3052f/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= github.com/pingcap/pd v2.1.0-rc.4+incompatible h1:/buwGk04aHO5odk/+O8ZOXGs4qkUjYTJ2UpCJXna8NE= github.com/pingcap/pd v2.1.0-rc.4+incompatible/go.mod h1:nD3+EoYes4+aNNODO99ES59V83MZSI+dFbhyr667a0E= github.com/pingcap/tidb-tools v2.1.1-0.20181218072513-b2235d442b06+incompatible h1:Bsd+NHosPVowEGB3BCx+2d8wUQGDTXSSC5ljeNS6cXo= github.com/pingcap/tidb-tools v2.1.1-0.20181218072513-b2235d442b06+incompatible/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM= +github.com/pingcap/tipb v0.0.0-20170310053819-1043caee48da/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI= github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323 h1:mRKKzRjDNaUNPnAkPAHnRqpNmwNWBX1iA+hxlmvQ93I= github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -193,6 +196,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -202,6 +206,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= @@ -210,9 +215,14 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5P golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI= golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9 h1:T3nuFyXXDj5KXX9CqQm/r/YEL4Gua01s/ZEdfdLyJ2c= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f h1:FU37niK8AQ59mHcskRyQL7H0ErSeNh650vdcj8HqdSI= +google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v0.0.0-20180607172857-7a6a684ca69e/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/server/http_handler.go b/server/http_handler.go index 8bf47757ab4f5..5b5a4d373d43a 100644 --- a/server/http_handler.go +++ b/server/http_handler.go @@ -23,6 +23,7 @@ import ( "math" "net/http" "net/url" + "sort" "strconv" "strings" "sync/atomic" @@ -74,8 +75,11 @@ const qTableID = "table_id" const qLimit = "limit" const ( + protocol = "http://" headerContentType = "Content-Type" contentTypeJSON = "application/json" + hotRead = "/pd/api/v1/hotspot/regions/read" + hotWrite = "/pd/api/v1/hotspot/regions/write" ) type kvStore interface { @@ -304,6 +308,149 @@ func (t *tikvHandlerTool) getAllHistoryDDL() ([]*model.Job, error) { return jobs, nil } +func (t *tikvHandlerTool) scrapeHotInfo(rw string) (map[tblIndex]regionMetric, error) { + regionMetrics, err := t.fetchHotRegion(rw) + if err != nil { + return nil, err + } + + tblIdx, err := t.fetchRegionTableIndex(regionMetrics) + if err != nil { + return nil, err + } + return tblIdx, nil +} + +// storeHotRegionInfos records all hog region stores. +// it's the response of PD. +type storeHotRegionInfos struct { + AsPeer map[uint64]*hotRegionsStat `json:"as_peer"` + AsLeader map[uint64]*hotRegionsStat `json:"as_leader"` +} + +// hotRegions records echo store's hot region. +// it's the response of PD. +type hotRegionsStat struct { + RegionsStat []regionStat `json:"statistics"` +} + +// regionStat records each hot region's statistics +// it's the response of PD. +type regionStat struct { + RegionID uint64 `json:"region_id"` + FlowBytes uint64 `json:"flow_bytes"` + HotDegree int `json:"hot_degree"` +} + +// regionMetric presents the final metric output entry. +type regionMetric struct { + FlowBytes uint64 `json:"flow_bytes"` + MaxHotDegree int `json:"max_hot_degree"` + Count int `json:"region_count"` +} + +// tblIndex presents the aggregate key that combined with db,table,index +type tblIndex struct { + DbName string `json:"db_name"` + TableName string `json:"table_name"` + IndexName string `json:"index_name"` +} + +func (t *tikvHandlerTool) fetchHotRegion(rw string) (map[uint64]regionMetric, error) { + etcd, ok := t.store.(domain.EtcdBackend) + if !ok { + return nil, errors.New("not implemented") + } + pdHosts := etcd.EtcdAddrs() + if len(pdHosts) == 0 { + return nil, errors.New("pd unavailable") + } + req, err := http.NewRequest("GET", protocol+pdHosts[0]+rw, nil) + if err != nil { + return nil, errors.Trace(err) + } + timeout, cancelFunc := context.WithTimeout(context.Background(), 50*time.Millisecond) + resp, err := http.DefaultClient.Do(req.WithContext(timeout)) + cancelFunc() + if err != nil { + return nil, errors.Trace(err) + } + defer func() { + err = resp.Body.Close() + if err != nil { + log.Error(err) + } + }() + var regionResp storeHotRegionInfos + err = json.NewDecoder(resp.Body).Decode(®ionResp) + if err != nil { + return nil, errors.Trace(err) + } + metric := make(map[uint64]regionMetric) + for _, hotRegions := range regionResp.AsLeader { + for _, region := range hotRegions.RegionsStat { + metric[region.RegionID] = regionMetric{FlowBytes: region.FlowBytes, MaxHotDegree: region.HotDegree} + } + } + return metric, nil +} + +func (t *tikvHandlerTool) fetchRegionTableIndex(metrics map[uint64]regionMetric) (map[tblIndex]regionMetric, error) { + schema, err := t.schema() + if err != nil { + return nil, err + } + + idxMetrics := make(map[tblIndex]regionMetric) + for regionID, regionMetric := range metrics { + region, err := t.regionCache.LocateRegionByID(tikv.NewBackoffer(context.Background(), 500), regionID) + if err != nil { + log.Error(err) + continue + } + + hotRange, err := NewRegionFrameRange(region) + if err != nil { + return nil, err + } + + f := t.findTableIndexOfRegion(schema, hotRange) + if f != nil { + idx := tblIndex{DbName: f.DBName, TableName: f.TableName, IndexName: f.IndexName} + metric, exists := idxMetrics[idx] + if !exists { + metric = regionMetric + metric.Count++ + idxMetrics[idx] = metric + } else { + metric.FlowBytes += regionMetric.FlowBytes + if metric.MaxHotDegree < regionMetric.MaxHotDegree { + metric.MaxHotDegree = regionMetric.MaxHotDegree + } + metric.Count++ + } + } + } + + return idxMetrics, nil +} + +func (t *tikvHandlerTool) findTableIndexOfRegion(schema infoschema.InfoSchema, hotRange *RegionFrameRange) *FrameItem { + for _, db := range schema.AllSchemas() { + for _, tbl := range db.Tables { + if f := hotRange.getRecordFrame(tbl.ID, db.Name.O, tbl.Name.O); f != nil { + return f + } + for _, idx := range tbl.Indices { + if f := hotRange.getIndexFrame(tbl.ID, idx.ID, db.Name.O, tbl.Name.O, idx.Name.O); f != nil { + return f + } + } + } + } + return nil +} + // settingsHandler is the handler for list tidb server settings. type settingsHandler struct { } @@ -957,26 +1104,73 @@ func (h tableHandler) handleDiskUsageRequest(schema infoschema.InfoSchema, tbl t writeData(w, stats.StorageSize) } +type hotRegion struct { + tblIndex + regionMetric +} +type hotRegions []hotRegion + +func (rs hotRegions) Len() int { + return len(rs) +} + +func (rs hotRegions) Less(i, j int) bool { + return rs[i].MaxHotDegree > rs[j].MaxHotDegree || (rs[i].MaxHotDegree == rs[j].MaxHotDegree && rs[i].FlowBytes > rs[j].FlowBytes) +} + +func (rs hotRegions) Swap(i, j int) { + rs[i], rs[j] = rs[j], rs[i] +} + // ServeHTTP handles request of get region by ID. func (h regionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // parse and check params params := mux.Vars(req) if _, ok := params[pRegionID]; !ok { - startKey := []byte{'m'} - endKey := []byte{'n'} + router := mux.CurrentRoute(req).GetName() + if router == "RegionsMeta" { + startKey := []byte{'m'} + endKey := []byte{'n'} - recordRegionIDs, err := h.regionCache.ListRegionIDsInKeyRange(tikv.NewBackoffer(context.Background(), 500), startKey, endKey) - if err != nil { - writeError(w, err) + recordRegionIDs, err := h.regionCache.ListRegionIDsInKeyRange(tikv.NewBackoffer(context.Background(), 500), startKey, endKey) + if err != nil { + writeError(w, err) + return + } + + recordRegions, err := h.getRegionsMeta(recordRegionIDs) + if err != nil { + writeError(w, err) + return + } + writeData(w, recordRegions) return } - - recordRegions, err := h.getRegionsMeta(recordRegionIDs) - if err != nil { - writeError(w, err) + if router == "RegionHot" { + hotRead, err := h.scrapeHotInfo(hotRead) + if err != nil { + writeError(w, err) + return + } + hotWrite, err := h.scrapeHotInfo(hotWrite) + if err != nil { + writeError(w, err) + return + } + asSortedEntry := func(metric map[tblIndex]regionMetric) hotRegions { + hs := make(hotRegions, 0, len(metric)) + for key, value := range metric { + hs = append(hs, hotRegion{key, value}) + } + sort.Sort(hs) + return hs + } + writeData(w, map[string]interface{}{ + "write": asSortedEntry(hotWrite), + "read": asSortedEntry(hotRead), + }) return } - writeData(w, recordRegions) return } diff --git a/server/http_status.go b/server/http_status.go index f88807af06b55..c1720f6db5d29 100644 --- a/server/http_status.go +++ b/server/http_status.go @@ -75,6 +75,7 @@ func (s *Server) startHTTPServer() { router.Handle("/tables/{db}/{table}/stop-scatter", tableHandler{tikvHandlerTool, opStopTableScatter}) router.Handle("/tables/{db}/{table}/disk-usage", tableHandler{tikvHandlerTool, opTableDiskUsage}) router.Handle("/regions/meta", regionHandler{tikvHandlerTool}).Name("RegionsMeta") + router.Handle("/regions/hot", regionHandler{tikvHandlerTool}).Name("RegionHot") router.Handle("/regions/{regionID}", regionHandler{tikvHandlerTool}) router.Handle("/mvcc/key/{db}/{table}/{handle}", mvccTxnHandler{tikvHandlerTool, opMvccGetByKey}) router.Handle("/mvcc/txn/{startTS}/{db}/{table}", mvccTxnHandler{tikvHandlerTool, opMvccGetByTxn}) diff --git a/session/session.go b/session/session.go index 1838880eaeff8..814da0c20de46 100644 --- a/session/session.go +++ b/session/session.go @@ -810,7 +810,7 @@ func (s *session) SetGlobalSysVar(name, value string) error { return errors.Trace(err) } -func (s *session) ParseSQL(ctx context.Context, sql, charset, collation string) ([]ast.StmtNode, error) { +func (s *session) ParseSQL(ctx context.Context, sql, charset, collation string) ([]ast.StmtNode, []error, error) { if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { span1 := span.Tracer().StartSpan("session.ParseSQL", opentracing.ChildOf(span.Context())) defer span1.Finish() @@ -885,7 +885,7 @@ func (s *session) execute(ctx context.Context, sql string) (recordSets []sqlexec // Step1: Compile query string to abstract syntax trees(ASTs). startTS := time.Now() - stmtNodes, err := s.ParseSQL(ctx, sql, charsetInfo, collation) + stmtNodes, warns, err := s.ParseSQL(ctx, sql, charsetInfo, collation) if err != nil { s.rollbackOnError(ctx) log.Warnf("con:%d parse error:\n%v\n%s", connID, err, sql) @@ -922,6 +922,10 @@ func (s *session) execute(ctx context.Context, sql string) (recordSets []sqlexec // return the first recordset if client doesn't support ClientMultiResults. recordSets = recordSets[:1] } + + for _, warn := range warns { + s.sessionVars.StmtCtx.AppendWarning(warn) + } return recordSets, nil } diff --git a/session/tidb.go b/session/tidb.go index 7ef039281bf0b..3df30dc955367 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -125,7 +125,10 @@ func Parse(ctx sessionctx.Context, src string) ([]ast.StmtNode, error) { p := parser.New() p.EnableWindowFunc(ctx.GetSessionVars().EnableWindowFunction) p.SetSQLMode(ctx.GetSessionVars().SQLMode) - stmts, err := p.Parse(src, charset, collation) + stmts, warns, err := p.Parse(src, charset, collation) + for _, warn := range warns { + ctx.GetSessionVars().StmtCtx.AppendWarning(warn) + } if err != nil { log.Warnf("compiling %s, error: %v", src, err) return nil, errors.Trace(err) diff --git a/store/mockstore/mocktikv/rpc.go b/store/mockstore/mocktikv/rpc.go index 2832152df4de0..14e8ce5a952a7 100644 --- a/store/mockstore/mocktikv/rpc.go +++ b/store/mockstore/mocktikv/rpc.go @@ -475,7 +475,11 @@ func (h *rpcHandler) handleKvRawScan(req *kvrpcpb.RawScanRequest) *kvrpcpb.RawSc }, } } - pairs := rawKV.RawScan(req.GetStartKey(), h.endKey, int(req.GetLimit())) + endKey := h.endKey + if len(req.EndKey) > 0 && (len(endKey) == 0 || bytes.Compare(req.EndKey, endKey) < 0) { + endKey = req.EndKey + } + pairs := rawKV.RawScan(req.GetStartKey(), endKey, int(req.GetLimit())) return &kvrpcpb.RawScanResponse{ Kvs: convertToPbPairs(pairs), } diff --git a/store/tikv/rawkv.go b/store/tikv/rawkv.go index d0c7dda7874f5..9b6023b5e62bf 100644 --- a/store/tikv/rawkv.go +++ b/store/tikv/rawkv.go @@ -268,9 +268,12 @@ func (c *RawKVClient) DeleteRange(startKey []byte, endKey []byte) error { return nil } -// Scan queries continuous kv pairs, starts from startKey, up to limit pairs. -// If you want to exclude the startKey, append a '\0' to the key: `Scan(append(startKey, '\0'), limit)`. -func (c *RawKVClient) Scan(startKey []byte, limit int) (keys [][]byte, values [][]byte, err error) { +// Scan queries continuous kv pairs in range [startKey, endKey), up to limit pairs. +// If endKey is empty, it means unbounded. +// If you want to exclude the startKey or include the endKey, append a '\0' to the key. For example, to scan +// (startKey, endKey], you can write: +// `Scan(append(startKey, '\0'), append(endKey, '\0'), limit)`. +func (c *RawKVClient) Scan(startKey, endKey []byte, limit int) (keys [][]byte, values [][]byte, err error) { start := time.Now() defer func() { metrics.TiKVRawkvCmdHistogram.WithLabelValues("raw_scan").Observe(time.Since(start).Seconds()) }() @@ -283,6 +286,7 @@ func (c *RawKVClient) Scan(startKey []byte, limit int) (keys [][]byte, values [] Type: tikvrpc.CmdRawScan, RawScan: &kvrpcpb.RawScanRequest{ StartKey: startKey, + EndKey: endKey, Limit: uint32(limit - len(keys)), }, } diff --git a/store/tikv/rawkv_test.go b/store/tikv/rawkv_test.go index 3eccaa36cb40b..9309f302d5630 100644 --- a/store/tikv/rawkv_test.go +++ b/store/tikv/rawkv_test.go @@ -103,7 +103,17 @@ func (s *testRawKVSuite) mustBatchDelete(c *C, keys [][]byte) { } func (s *testRawKVSuite) mustScan(c *C, startKey string, limit int, expect ...string) { - keys, values, err := s.client.Scan([]byte(startKey), limit) + keys, values, err := s.client.Scan([]byte(startKey), nil, limit) + c.Assert(err, IsNil) + c.Assert(len(keys)*2, Equals, len(expect)) + for i := range keys { + c.Assert(string(keys[i]), Equals, expect[i*2]) + c.Assert(string(values[i]), Equals, expect[i*2+1]) + } +} + +func (s *testRawKVSuite) mustScanRange(c *C, startKey string, endKey string, limit int, expect ...string) { + keys, values, err := s.client.Scan([]byte(startKey), []byte(endKey), limit) c.Assert(err, IsNil) c.Assert(len(keys)*2, Equals, len(expect)) for i := range keys { @@ -127,7 +137,7 @@ func (s *testRawKVSuite) mustDeleteRange(c *C, startKey, endKey []byte, expected } func (s *testRawKVSuite) checkData(c *C, expected map[string]string) { - keys, values, err := s.client.Scan([]byte(""), len(expected)+1) + keys, values, err := s.client.Scan([]byte(""), nil, len(expected)+1) c.Assert(err, IsNil) c.Assert(len(expected), Equals, len(keys)) @@ -203,6 +213,11 @@ func (s *testRawKVSuite) TestScan(c *C) { s.mustScan(c, "", 10, "k1", "v1", "k3", "v3", "k5", "v5", "k7", "v7") s.mustScan(c, "k2", 2, "k3", "v3", "k5", "v5") s.mustScan(c, "k2", 3, "k3", "v3", "k5", "v5", "k7", "v7") + s.mustScanRange(c, "", "k1", 1) + s.mustScanRange(c, "k1", "k3", 2, "k1", "v1") + s.mustScanRange(c, "k1", "k5", 10, "k1", "v1", "k3", "v3") + s.mustScanRange(c, "k1", "k5\x00", 10, "k1", "v1", "k3", "v3", "k5", "v5") + s.mustScanRange(c, "k5\x00", "k5\x00\x00", 10) } check() diff --git a/table/tables/gen_expr.go b/table/tables/gen_expr.go index 85ea540eac85b..ba11e7e7b4ab3 100644 --- a/table/tables/gen_expr.go +++ b/table/tables/gen_expr.go @@ -61,7 +61,7 @@ func (nr *nameResolver) Leave(inNode ast.Node) (node ast.Node, ok bool) { func parseExpression(expr string) (node ast.ExprNode, err error) { expr = fmt.Sprintf("select %s", expr) charset, collation := charset.GetDefaultCharsetAndCollate() - stmts, err := parser.New().Parse(expr, charset, collation) + stmts, _, err := parser.New().Parse(expr, charset, collation) if err == nil { node = stmts[0].(*ast.SelectStmt).Fields.Fields[0].Expr } diff --git a/util/encrypt/aes.go b/util/encrypt/aes.go index 76f93f60a5afa..435f3bdc7c6ee 100644 --- a/util/encrypt/aes.go +++ b/util/encrypt/aes.go @@ -173,6 +173,30 @@ func AESDecryptWithCBC(cryptStr, key []byte, iv []byte) ([]byte, error) { return aesDecrypt(cryptStr, mode) } +// AESEncryptWithCFB decrypts data using AES with CFB mode. +func AESEncryptWithCFB(cryptStr, key []byte, iv []byte) ([]byte, error) { + cb, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + cfb := cipher.NewCFBEncrypter(cb, iv) + crypted := make([]byte, len(cryptStr)) + cfb.XORKeyStream(crypted, cryptStr) + return crypted, nil +} + +// AESDecryptWithCFB decrypts data using AES with CFB mode. +func AESDecryptWithCFB(cryptStr, key []byte, iv []byte) ([]byte, error) { + cb, err := aes.NewCipher(key) + if err != nil { + return nil, errors.Trace(err) + } + cfb := cipher.NewCFBDecrypter(cb, []byte(iv)) + dst := make([]byte, len(cryptStr)) + cfb.XORKeyStream(dst, cryptStr) + return dst, nil +} + // aesDecrypt decrypts data using AES. func aesDecrypt(cryptStr []byte, mode cipher.BlockMode) ([]byte, error) { blockSize := mode.BlockSize() diff --git a/util/encrypt/aes_test.go b/util/encrypt/aes_test.go index f93b83fde4307..14cd1e4b77fa2 100644 --- a/util/encrypt/aes_test.go +++ b/util/encrypt/aes_test.go @@ -342,6 +342,75 @@ func (s *testEncryptSuite) TestAESDecryptWithCBC(c *C) { } } +func (s *testEncryptSuite) TestAESEncryptWithOFB(c *C) { + defer testleak.AfterTest(c)() + tests := []struct { + str string + key string + iv string + expect string + isError bool + }{ + // 128 bits key + {"pingcap", "1234567890123456", "1234567890123456", "0515A36BBF3DE0", false}, + {"pingcap123", "1234567890123456", "1234567890123456", "0515A36BBF3DE0DBE9DD", false}, + // 192 bits key + {"pingcap", "123456789012345678901234", "1234567890123456", "45A57592449893", false}, // 192 bit + // negtive cases: invalid key length + {"pingcap", "12345678901234567", "1234567890123456", "", true}, + {"pingcap", "123456789012345", "1234567890123456", "", true}, + } + + for _, t := range tests { + str := []byte(t.str) + key := []byte(t.key) + iv := []byte(t.iv) + + crypted, err := AESEncryptWithCFB(str, key, iv) + if t.isError { + c.Assert(err, NotNil, Commentf("%v", t)) + continue + } + c.Assert(err, IsNil, Commentf("%v", t)) + result := toHex(crypted) + c.Assert(result, Equals, t.expect, Commentf("%v", t)) + } +} + +func (s *testEncryptSuite) TestAESDecryptWithCFB(c *C) { + defer testleak.AfterTest(c)() + tests := []struct { + str string + key string + iv string + expect string + isError bool + }{ + // 128 bits key + {"0515A36BBF3DE0", "1234567890123456", "1234567890123456", "pingcap", false}, + {"0515A36BBF3DE0DBE9DD", "1234567890123456", "1234567890123456", "pingcap123", false}, + // 192 bits key + {"45A57592449893", "123456789012345678901234", "1234567890123456", "pingcap", false}, // 192 bit + // negtive cases: invalid key length + {"pingcap", "12345678901234567", "1234567890123456", "", true}, + {"pingcap", "123456789012345", "1234567890123456", "", true}, + } + + for _, t := range tests { + str, _ := hex.DecodeString(t.str) + key := []byte(t.key) + iv := []byte(t.iv) + + plainText, err := AESDecryptWithCFB(str, key, iv) + if t.isError { + c.Assert(err, NotNil, Commentf("%v", t)) + continue + } + c.Assert(err, IsNil, Commentf("%v", t)) + c.Assert(string(plainText), Equals, t.expect, Commentf("%v", t)) + } +} + func (s *testEncryptSuite) TestDeriveKeyMySQL(c *C) { defer testleak.AfterTest(c)() diff --git a/util/testutil/testutil.go b/util/testutil/testutil.go index 657a6ab3028f5..003756b0fd405 100644 --- a/util/testutil/testutil.go +++ b/util/testutil/testutil.go @@ -14,14 +14,10 @@ package testutil import ( - "context" "fmt" "strings" "github.com/pingcap/check" - "github.com/pingcap/errors" - "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" ) @@ -112,36 +108,3 @@ func RowsWithSep(sep string, args ...string) [][]interface{} { } return rows } - -// SessionExecInGoroutine export for testing -func SessionExecInGoroutine(c *check.C, s kv.Storage, sql string, done chan error) { - execMultiSQLInGoroutine(c, s, "test_db", []string{sql}, done) -} - -func execMultiSQLInGoroutine(c *check.C, s kv.Storage, dbName string, multiSQL []string, done chan error) { - go func() { - se, err := session.CreateSession4Test(s) - if err != nil { - done <- errors.Trace(err) - return - } - defer se.Close() - _, err = se.Execute(context.Background(), "use "+dbName) - if err != nil { - done <- errors.Trace(err) - return - } - for _, sql := range multiSQL { - rs, err := se.Execute(context.Background(), sql) - if err != nil { - done <- errors.Trace(err) - return - } - if rs != nil { - done <- errors.Errorf("RecordSet should be empty.") - return - } - done <- nil - } - }() -}