Skip to content

Commit

Permalink
executor: set the correct stmtCtx for explain statement (pingcap#11186)
Browse files Browse the repository at this point in the history
  • Loading branch information
XuHuaiyu committed Sep 2, 2019
1 parent 969db3b commit 4cdab80
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 4 deletions.
8 changes: 7 additions & 1 deletion executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 101 additions & 0 deletions executor/explainfor_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
}
6 changes: 3 additions & 3 deletions executor/point_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ");`)
Expand All @@ -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;`)
Expand Down
14 changes: 14 additions & 0 deletions util/testkit/testkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down

0 comments on commit 4cdab80

Please sign in to comment.