diff --git a/bindinfo/bind_test.go b/bindinfo/bind_test.go index 26a8175692333..5079b5605f713 100644 --- a/bindinfo/bind_test.go +++ b/bindinfo/bind_test.go @@ -498,6 +498,33 @@ func (s *testSuite) TestCapturePlanBaseline(c *C) { c.Assert(rows[0][1], Equals, "SELECT /*+ USE_INDEX(@`sel_1` `test`.`t` )*/ * FROM `t` WHERE `a`>10") } +func (s *testSuite) TestCaptureBaselinesDefaultDB(c *C) { + tk := testkit.NewTestKit(c, s.store) + s.cleanBindingEnv(tk) + stmtsummary.StmtSummaryByDigestMap.Clear() + tk.MustExec(" set @@tidb_capture_plan_baselines = on") + defer func() { + tk.MustExec(" set @@tidb_capture_plan_baselines = off") + }() + tk.MustExec("use test") + tk.MustExec("drop database if exists spm") + tk.MustExec("create database spm") + tk.MustExec("create table spm.t(a int, index idx_a(a))") + c.Assert(tk.Se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + tk.MustExec("select * from spm.t ignore index(idx_a) where a > 10") + tk.MustExec("select * from spm.t ignore index(idx_a) where a > 10") + tk.MustExec("admin capture bindings") + rows := tk.MustQuery("show global bindings").Rows() + c.Assert(len(rows), Equals, 1) + // Default DB should be "" when all columns have explicit database name. + c.Assert(rows[0][2], Equals, "") + c.Assert(rows[0][3], Equals, "using") + tk.MustExec("use spm") + tk.MustExec("select * from spm.t where a > 10") + // Should use TableScan because of the "ignore index" binding. + c.Assert(len(tk.Se.GetSessionVars().StmtCtx.IndexNames), Equals, 0) +} + func (s *testSuite) TestUseMultiplyBindings(c *C) { tk := testkit.NewTestKit(c, s.store) s.cleanBindingEnv(tk) diff --git a/bindinfo/handle.go b/bindinfo/handle.go index 86cb1f282cdb4..5fa3c136929b5 100644 --- a/bindinfo/handle.go +++ b/bindinfo/handle.go @@ -37,6 +37,7 @@ import ( driver "github.com/pingcap/tidb/types/parser_driver" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" + utilparser "github.com/pingcap/tidb/util/parser" "github.com/pingcap/tidb/util/sqlexec" "github.com/pingcap/tidb/util/stmtsummary" "github.com/pingcap/tidb/util/timeutil" @@ -529,7 +530,8 @@ func (h *BindHandle) CaptureBaselines() { continue } normalizedSQL, digiest := parser.NormalizeDigest(sqls[i]) - if r := h.GetBindRecord(digiest, normalizedSQL, schemas[i]); r != nil && r.HasUsingBinding() { + dbName := utilparser.GetDefaultDB(stmt, schemas[i]) + if r := h.GetBindRecord(digiest, normalizedSQL, dbName); r != nil && r.HasUsingBinding() { continue } h.sctx.Lock() @@ -559,7 +561,7 @@ func (h *BindHandle) CaptureBaselines() { Collation: collation, } // We don't need to pass the `sctx` because they are used to generate hints and we already filled hints in. - err = h.AddBindRecord(nil, &BindRecord{OriginalSQL: normalizedSQL, Db: schemas[i], Bindings: []Binding{binding}}) + err = h.AddBindRecord(nil, &BindRecord{OriginalSQL: normalizedSQL, Db: dbName, Bindings: []Binding{binding}}) if err != nil { logutil.BgLogger().Info("capture baseline failed", zap.String("SQL", sqls[i]), zap.Error(err)) } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 9d2a9c77a5b18..67e98ef31107f 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -46,6 +46,7 @@ import ( util2 "github.com/pingcap/tidb/util" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" + utilparser "github.com/pingcap/tidb/util/parser" "github.com/pingcap/tidb/util/ranger" "github.com/pingcap/tidb/util/set" @@ -558,7 +559,7 @@ func (b *PlanBuilder) buildDropBindPlan(v *ast.DropBindingStmt) (Plan, error) { SQLBindOp: OpSQLBindDrop, NormdOrigSQL: parser.Normalize(v.OriginSel.Text()), IsGlobal: v.GlobalScope, - Db: getDefaultDB(b.ctx, v.OriginSel), + Db: utilparser.GetDefaultDB(v.OriginSel, b.ctx.GetSessionVars().CurrentDB), } if v.HintedSel != nil { p.BindSQL = v.HintedSel.Text() @@ -575,7 +576,7 @@ func (b *PlanBuilder) buildCreateBindPlan(v *ast.CreateBindingStmt) (Plan, error BindSQL: v.HintedSel.Text(), IsGlobal: v.GlobalScope, BindStmt: v.HintedSel, - Db: getDefaultDB(b.ctx, v.OriginSel), + Db: utilparser.GetDefaultDB(v.OriginSel, b.ctx.GetSessionVars().CurrentDB), Charset: charSet, Collation: collation, } @@ -583,34 +584,6 @@ func (b *PlanBuilder) buildCreateBindPlan(v *ast.CreateBindingStmt) (Plan, error return p, nil } -func getDefaultDB(ctx sessionctx.Context, sel ast.StmtNode) string { - implicitDB := &implicitDatabase{} - sel.Accept(implicitDB) - if implicitDB.hasImplicit { - return ctx.GetSessionVars().CurrentDB - } - return "" -} - -type implicitDatabase struct { - hasImplicit bool -} - -func (i *implicitDatabase) Enter(in ast.Node) (out ast.Node, skipChildren bool) { - switch x := in.(type) { - case *ast.TableName: - if x.Schema.L == "" { - i.hasImplicit = true - } - return in, true - } - return in, false -} - -func (i *implicitDatabase) Leave(in ast.Node) (out ast.Node, ok bool) { - return in, true -} - // detectSelectAgg detects an aggregate function or GROUP BY clause. func (b *PlanBuilder) detectSelectAgg(sel *ast.SelectStmt) bool { if sel.GroupBy != nil { diff --git a/util/parser/ast.go b/util/parser/ast.go new file mode 100644 index 0000000000000..e7dfeec1a9474 --- /dev/null +++ b/util/parser/ast.go @@ -0,0 +1,47 @@ +// Copyright 2020 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 parser + +import ( + "github.com/pingcap/parser/ast" +) + +// GetDefaultDB checks if all columns in the AST have explicit DBName. If not, return specified DBName. +func GetDefaultDB(sel ast.StmtNode, dbName string) string { + implicitDB := &implicitDatabase{} + sel.Accept(implicitDB) + if implicitDB.hasImplicit { + return dbName + } + return "" +} + +type implicitDatabase struct { + hasImplicit bool +} + +func (i *implicitDatabase) Enter(in ast.Node) (out ast.Node, skipChildren bool) { + switch x := in.(type) { + case *ast.TableName: + if x.Schema.L == "" { + i.hasImplicit = true + } + return in, true + } + return in, i.hasImplicit +} + +func (i *implicitDatabase) Leave(in ast.Node) (out ast.Node, ok bool) { + return in, true +}