diff --git a/executor/executor.go b/executor/executor.go index 95c876147a326..5e8664e0baab2 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -1346,13 +1346,19 @@ func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) { default: sc.MemTracker.SetActionOnExceed(&memory.LogOnExceed{}) } - if execStmt, ok := s.(*ast.ExecuteStmt); ok { s, err = getPreparedStmt(execStmt, vars) if err != nil { return } } + // execute missed stmtID uses empty sql + sc.OriginalSQL = s.Text() + if explainStmt, ok := s.(*ast.ExplainStmt); ok { + sc.InExplainStmt = true + sc.CastStrToIntStrict = true + s = explainStmt.Stmt + } // TODO: Many same bool variables here. // We should set only two variables ( // IgnoreErr and StrictSQLMode) to avoid setting the same bool variables and diff --git a/executor/explainfor_test.go b/executor/explainfor_test.go new file mode 100644 index 0000000000000..632874180495b --- /dev/null +++ b/executor/explainfor_test.go @@ -0,0 +1,101 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package executor_test + +import ( + "fmt" + + . "github.com/pingcap/check" + "github.com/pingcap/parser/auth" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/testkit" +) + +// mockSessionManager is a mocked session manager which is used for test. +type mockSessionManager1 struct { + PS []*util.ProcessInfo +} + +// ShowProcessList implements the SessionManager.ShowProcessList interface. +func (msm *mockSessionManager1) ShowProcessList() map[uint64]*util.ProcessInfo { + ret := make(map[uint64]*util.ProcessInfo) + for _, item := range msm.PS { + ret[item.ID] = item + } + return ret +} + +func (msm *mockSessionManager1) GetProcessInfo(id uint64) (*util.ProcessInfo, bool) { + for _, item := range msm.PS { + if item.ID == id { + return item, true + } + } + return &util.ProcessInfo{}, false +} + +// Kill implements the SessionManager.Kill interface. +func (msm *mockSessionManager1) Kill(cid uint64, query bool) { + +} + +func (s *testSuite) TestExplainFor(c *C) { + tkRoot := testkit.NewTestKitWithInit(c, s.store) + tkUser := testkit.NewTestKitWithInit(c, s.store) + tkRoot.MustExec("create table t1(c1 int, c2 int)") + tkRoot.MustExec("create table t2(c1 int, c2 int)") + tkRoot.MustExec("create user tu@'%'") + tkRoot.Se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost", CurrentUser: true, AuthUsername: "root", AuthHostname: "%"}, nil, []byte("012345678901234567890")) + tkUser.Se.Auth(&auth.UserIdentity{Username: "tu", Hostname: "localhost", CurrentUser: true, AuthUsername: "tu", AuthHostname: "%"}, nil, []byte("012345678901234567890")) + + tkRoot.MustQuery("select * from t1;") + tkRootProcess := tkRoot.Se.ShowProcess() + ps := []*util.ProcessInfo{tkRootProcess} + tkRoot.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + tkUser.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + tkRoot.MustQuery(fmt.Sprintf("explain for connection %d", tkRootProcess.ID)).Check(testkit.Rows( + "TableReader_5 10000.00 root data:TableScan_4", + "└─TableScan_4 10000.00 cop table:t1, range:[-inf,+inf], keep order:false, stats:pseudo", + )) + err := tkUser.ExecToErr(fmt.Sprintf("explain for connection %d", tkRootProcess.ID)) + c.Check(core.ErrAccessDenied.Equal(err), IsTrue) + err = tkUser.ExecToErr("explain for connection 42") + c.Check(core.ErrNoSuchThread.Equal(err), IsTrue) + + tkRootProcess.Plan = nil + ps = []*util.ProcessInfo{tkRootProcess} + tkRoot.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + tkRoot.MustExec(fmt.Sprintf("explain for connection %d", tkRootProcess.ID)) +} + +func (s *testSuite) TestIssue11124(c *C) { + tk := testkit.NewTestKitWithInit(c, s.store) + tk2 := testkit.NewTestKitWithInit(c, s.store) + tk.MustExec("create table kankan1(id int, name text);") + tk.MustExec("create table kankan2(id int, h1 text);") + tk.MustExec("insert into kankan1 values(1, 'a'), (2, 'a');") + tk.MustExec("insert into kankan2 values(2, 'z');") + tk.MustQuery("select t1.id from kankan1 t1 left join kankan2 t2 on t1.id = t2.id where (case when t1.name='b' then 'case2' when t1.name='a' then 'case1' else NULL end) = 'case1'") + tkRootProcess := tk.Se.ShowProcess() + ps := []*util.ProcessInfo{tkRootProcess} + tk.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + tk2.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + + rs := tk.MustQuery("explain select t1.id from kankan1 t1 left join kankan2 t2 on t1.id = t2.id where (case when t1.name='b' then 'case2' when t1.name='a' then 'case1' else NULL end) = 'case1'").Rows() + rs2 := tk2.MustQuery(fmt.Sprintf("explain for connection %d", tkRootProcess.ID)).Rows() + for i := range rs { + c.Assert(rs[i], DeepEquals, rs2[i]) + } +} diff --git a/executor/point_get_test.go b/executor/point_get_test.go index cc7550e4f55bb..0508543240b12 100644 --- a/executor/point_get_test.go +++ b/executor/point_get_test.go @@ -176,7 +176,7 @@ func (s *testPointGetSuite) TestIndexLookupCharPK(c *C) { // Test truncate with sql mode `PAD_CHAR_TO_FULL_LENGTH`. tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) tk.MustIndexLookup(`select * from t tmp where a = "aa";`).Check(testkit.Rows(`aa bb`)) - tk.MustIndexLookup(`select * from t tmp where a = "aab";`).Check(testkit.Rows()) + tk.MustTableDual(`select * from t tmp where a = "aab";`).Check(testkit.Rows()) tk.MustExec(`truncate table t;`) tk.MustExec(`insert into t values("a ", "b ");`) @@ -189,9 +189,9 @@ func (s *testPointGetSuite) TestIndexLookupCharPK(c *C) { // Test trailing spaces with sql mode `PAD_CHAR_TO_FULL_LENGTH`. tk.MustExec(`set @@sql_mode="PAD_CHAR_TO_FULL_LENGTH";`) - tk.MustIndexLookup(`select * from t tmp where a = "a";`).Check(testkit.Rows()) + tk.MustTableDual(`select * from t tmp where a = "a";`).Check(testkit.Rows()) tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows(`a b`)) - tk.MustIndexLookup(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) + tk.MustTableDual(`select * from t tmp where a = "a ";`).Check(testkit.Rows()) // Test CHAR BINARY. tk.MustExec(`drop table if exists t;`) diff --git a/util/testkit/testkit.go b/util/testkit/testkit.go index fa7d22ae0921f..5ff04b8193de4 100644 --- a/util/testkit/testkit.go +++ b/util/testkit/testkit.go @@ -185,6 +185,20 @@ func (tk *TestKit) MustIndexLookup(sql string, args ...interface{}) *Result { return tk.MustQuery(sql, args...) } +// MustTableDual checks whether the plan for the sql is TableDual. +func (tk *TestKit) MustTableDual(sql string, args ...interface{}) *Result { + rs := tk.MustQuery("explain "+sql, args...) + hasTableDual := false + for i := range rs.rows { + if strings.Contains(rs.rows[i][0], "TableDual") { + hasTableDual = true + break + } + } + tk.c.Assert(hasTableDual, check.IsTrue) + return tk.MustQuery(sql, args...) +} + // MustPointGet checks whether the plan for the sql is Point_Get. func (tk *TestKit) MustPointGet(sql string, args ...interface{}) *Result { rs := tk.MustQuery("explain "+sql, args...)