diff --git a/executor/show.go b/executor/show.go index de77e55b7c78b..5b5372eb27f28 100644 --- a/executor/show.go +++ b/executor/show.go @@ -382,7 +382,7 @@ func (e *ShowExec) fetchShowColumns(ctx context.Context) error { if tb.Meta().IsView() { // Because view's undertable's column could change or recreate, so view's column type may change overtime. // To avoid this situation we need to generate a logical plan and extract current column types from Schema. - planBuilder := plannercore.NewPlanBuilder(e.ctx, e.is) + planBuilder := plannercore.NewPlanBuilder(e.ctx, e.is, &plannercore.BlockHintProcessor{}) viewLogicalPlan, err := planBuilder.BuildDataSourceFromView(ctx, e.DBName, tb.Meta()) if err != nil { return err diff --git a/go.mod b/go.mod index bebad3f4aed82..6413c20dd1b09 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e github.com/pingcap/kvproto v0.0.0-20190724165112-ec9df5f208a7 github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596 - github.com/pingcap/parser v0.0.0-20190822024127-41d48df05864 + github.com/pingcap/parser v0.0.0-20190828080649-a621a0f9b06c github.com/pingcap/pd v0.0.0-20190712044914-75a1f9f3062b github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible github.com/pingcap/tipb v0.0.0-20190806070524-16909e03435e diff --git a/go.sum b/go.sum index 68eccb6f85f2e..87f81d487e605 100644 --- a/go.sum +++ b/go.sum @@ -165,8 +165,8 @@ github.com/pingcap/kvproto v0.0.0-20190724165112-ec9df5f208a7/go.mod h1:QMdbTAXC github.com/pingcap/log v0.0.0-20190214045112-b37da76f67a7/go.mod h1:xsfkWVaFVV5B8e1K9seWfyJWFrIhbtUTAD8NV1Pq3+w= github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596 h1:t2OQTpPJnrPDGlvA+3FwJptMTt6MEPdzK1Wt99oaefQ= github.com/pingcap/log v0.0.0-20190307075452-bd41d9273596/go.mod h1:WpHUKhNZ18v116SvGrmjkA9CBhYmuUTKL+p8JC9ANEw= -github.com/pingcap/parser v0.0.0-20190822024127-41d48df05864 h1:ohPZBjGBLoRaaJi9UFYSgDs2dTty9YEyF+TWs7DBo58= -github.com/pingcap/parser v0.0.0-20190822024127-41d48df05864/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= +github.com/pingcap/parser v0.0.0-20190828080649-a621a0f9b06c h1:VcQ60QkH7Vv98dbedNKDEO7RfUw+6TE59r/lQokNz/k= +github.com/pingcap/parser v0.0.0-20190828080649-a621a0f9b06c/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= github.com/pingcap/pd v0.0.0-20190712044914-75a1f9f3062b h1:oS9PftxQqgcRouKhhdaB52tXhVLEP7Ng3Qqsd6Z18iY= github.com/pingcap/pd v0.0.0-20190712044914-75a1f9f3062b/go.mod h1:3DlDlFT7EF64A1bmb/tulZb6wbPSagm5G4p1AlhaEDs= github.com/pingcap/tidb-tools v2.1.3-0.20190321065848-1e8b48f5c168+incompatible h1:MkWCxgZpJBgY2f4HtwWMMFzSBb3+JPzeJgF3VrXE/bU= diff --git a/planner/core/expression_rewriter.go b/planner/core/expression_rewriter.go index a2e260a7914ca..a5ae866b6d032 100644 --- a/planner/core/expression_rewriter.go +++ b/planner/core/expression_rewriter.go @@ -47,7 +47,7 @@ func evalAstExpr(sctx sessionctx.Context, expr ast.ExprNode) (types.Datum, error if sctx.GetSessionVars().TxnCtx.InfoSchema != nil { is = sctx.GetSessionVars().TxnCtx.InfoSchema.(infoschema.InfoSchema) } - b := NewPlanBuilder(sctx, is) + b := NewPlanBuilder(sctx, is, &BlockHintProcessor{}) fakePlan := LogicalTableDual{}.Init(sctx) newExpr, _, err := b.rewrite(context.TODO(), expr, fakePlan, nil, true) if err != nil { diff --git a/planner/core/hints.go b/planner/core/hints.go new file mode 100644 index 0000000000000..8b88473f78ec7 --- /dev/null +++ b/planner/core/hints.go @@ -0,0 +1,156 @@ +// 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 core + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pingcap/errors" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/format" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/sessionctx" +) + +// BlockHintProcessor processes hints at different level of sql statement. +type BlockHintProcessor struct { + QbNameMap map[string]int // Map from query block name to select stmt offset. + QbHints map[int][]*ast.TableOptimizerHint // Group all hints at same query block. + Ctx sessionctx.Context + selectStmtOffset int +} + +// Enter implements Visitor interface. +func (p *BlockHintProcessor) Enter(in ast.Node) (ast.Node, bool) { + switch node := in.(type) { + case *ast.UpdateStmt: + p.checkQueryBlockHints(node.TableHints, 0) + case *ast.DeleteStmt: + p.checkQueryBlockHints(node.TableHints, 0) + case *ast.SelectStmt: + p.selectStmtOffset++ + node.QueryBlockOffset = p.selectStmtOffset + p.checkQueryBlockHints(node.TableHints, node.QueryBlockOffset) + } + return in, false +} + +// Leave implements Visitor interface. +func (p *BlockHintProcessor) Leave(in ast.Node) (ast.Node, bool) { + return in, true +} + +const hintQBName = "qb_name" + +// checkQueryBlockHints checks the validity of query blocks and records the map of query block name to select offset. +func (p *BlockHintProcessor) checkQueryBlockHints(hints []*ast.TableOptimizerHint, offset int) { + var qbName string + for _, hint := range hints { + if hint.HintName.L != hintQBName { + continue + } + if qbName != "" { + p.Ctx.GetSessionVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("There are more than two query names in same query block,, using the first one %s", qbName))) + } else { + qbName = hint.QBName.L + } + } + if qbName == "" { + return + } + if p.QbNameMap == nil { + p.QbNameMap = make(map[string]int) + } + if _, ok := p.QbNameMap[qbName]; ok { + p.Ctx.GetSessionVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("Duplicate query block name %s, only the first one is effective", qbName))) + } else { + p.QbNameMap[qbName] = offset + } +} + +const ( + defaultUpdateBlockName = "upd_1" + defaultDeleteBlockName = "del_1" + defaultSelectBlockPrefix = "sel_" +) + +type nodeType int + +const ( + typeUpdate nodeType = iota + typeDelete + typeSelect +) + +// getBlockName finds the offset of query block name. It use 0 as offset for top level update or delete, +// -1 for invalid block name. +func (p *BlockHintProcessor) getBlockOffset(blockName model.CIStr, nodeType nodeType) int { + if p.QbNameMap != nil { + level, ok := p.QbNameMap[blockName.L] + if ok { + return level + } + } + // Handle the default query block name. + if nodeType == typeUpdate && blockName.L == defaultUpdateBlockName { + return 0 + } + if nodeType == typeDelete && blockName.L == defaultDeleteBlockName { + return 0 + } + if nodeType == typeSelect && strings.HasPrefix(blockName.L, defaultSelectBlockPrefix) { + suffix := blockName.L[len(defaultSelectBlockPrefix):] + level, err := strconv.ParseInt(suffix, 10, 64) + if err != nil || level > int64(p.selectStmtOffset) { + return -1 + } + return int(level) + } + return -1 +} + +// getHintOffset gets the offset of stmt that the hints take effects. +func (p *BlockHintProcessor) getHintOffset(hint *ast.TableOptimizerHint, nodeType nodeType, currentOffset int) int { + if hint.QBName.L != "" { + return p.getBlockOffset(hint.QBName, nodeType) + } + return currentOffset +} + +// getCurrentStmtHints extracts all hints that take effects at current stmt. +func (p *BlockHintProcessor) getCurrentStmtHints(hints []*ast.TableOptimizerHint, nodeType nodeType, currentOffset int) []*ast.TableOptimizerHint { + if p.QbHints == nil { + p.QbHints = make(map[int][]*ast.TableOptimizerHint) + } + for _, hint := range hints { + if hint.HintName.L == hintQBName { + continue + } + offset := p.getHintOffset(hint, nodeType, currentOffset) + if offset < 0 { + var sb strings.Builder + ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb) + err := hint.Restore(ctx) + // There won't be any error for optimizer hint. + if err == nil { + p.Ctx.GetSessionVars().StmtCtx.AppendWarning(errors.New(fmt.Sprintf("Hint %s is ignored due to unknown query block name", sb.String()))) + } + continue + } + p.QbHints[offset] = append(p.QbHints[offset], hint) + } + return p.QbHints[currentOffset] +} diff --git a/planner/core/indexmerge_test.go b/planner/core/indexmerge_test.go index d6ac524c28fa5..dbebec9bcca5e 100644 --- a/planner/core/indexmerge_test.go +++ b/planner/core/indexmerge_test.go @@ -110,7 +110,7 @@ func (s *testIndexMergeSuite) TestIndexMergePathGenerateion(c *C) { stmt, err := s.ParseOneStmt(tc.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) if err != nil { c.Assert(err.Error(), Equals, tc.idxMergeDigest, comment) diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 0325371de95fd..e6f6aa49eb8d3 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1950,7 +1950,8 @@ func (b *PlanBuilder) unfoldWildStar(p LogicalPlan, selectFields []*ast.SelectFi return resultList, nil } -func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool { +func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, nodeType nodeType, currentLevel int) bool { + hints = b.hintProcessor.getCurrentStmtHints(hints, nodeType, currentLevel) var ( sortMergeTables, INLJTables, hashJoinTables []hintTableInfo indexHintList []indexHintInfo @@ -2023,7 +2024,7 @@ func (b *PlanBuilder) TableHints() *tableHintInfo { } func (b *PlanBuilder) buildSelect(ctx context.Context, sel *ast.SelectStmt) (p LogicalPlan, err error) { - if b.pushTableHints(sel.TableHints) { + if b.pushTableHints(sel.TableHints, typeSelect, sel.QueryBlockOffset) { // table hints are only visible in the current SELECT statement. defer b.popTableHints() } @@ -2666,7 +2667,7 @@ func buildColumns2Handle( } func (b *PlanBuilder) buildUpdate(ctx context.Context, update *ast.UpdateStmt) (Plan, error) { - if b.pushTableHints(update.TableHints) { + if b.pushTableHints(update.TableHints, typeUpdate, 0) { // table hints are only visible in the current UPDATE statement. defer b.popTableHints() } @@ -2906,7 +2907,7 @@ func extractTableAsNameForUpdate(p LogicalPlan, asNames map[*model.TableInfo][]* } func (b *PlanBuilder) buildDelete(ctx context.Context, delete *ast.DeleteStmt) (Plan, error) { - if b.pushTableHints(delete.TableHints) { + if b.pushTableHints(delete.TableHints, typeDelete, 0) { // table hints are only visible in the current DELETE statement. defer b.popTableHints() } diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 35806e53022a5..812e7cf0fa378 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -1808,7 +1808,7 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) builder.ctx.GetSessionVars().HashJoinConcurrency = 1 _, err = builder.Build(context.TODO(), stmt) c.Assert(err, IsNil, comment) @@ -1923,7 +1923,7 @@ func (s *testPlanSuite) TestUnion(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) plan, err := builder.Build(ctx, stmt) if tt.err { c.Assert(err, NotNil) @@ -2052,7 +2052,7 @@ func (s *testPlanSuite) TestTopNPushDown(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) c.Assert(err, IsNil) p, err = logicalOptimize(ctx, builder.optFlag, p.(LogicalPlan)) @@ -2175,7 +2175,7 @@ func (s *testPlanSuite) TestOuterJoinEliminator(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) c.Assert(err, IsNil) p, err = logicalOptimize(ctx, builder.optFlag, p.(LogicalPlan)) @@ -2203,7 +2203,7 @@ func (s *testPlanSuite) TestSelectView(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) c.Assert(err, IsNil) p, err = logicalOptimize(ctx, builder.optFlag, p.(LogicalPlan)) @@ -2488,7 +2488,7 @@ func (s *testPlanSuite) TestWindowFunction(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) if err != nil { c.Assert(err.Error(), Equals, tt.result, comment) @@ -2570,7 +2570,7 @@ func (s *testPlanSuite) TestSkylinePruning(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) if err != nil { c.Assert(err.Error(), Equals, tt.result, comment) @@ -2659,7 +2659,7 @@ func (s *testPlanSuite) TestUpdateEQCond(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) Preprocess(s.ctx, stmt, s.is) - builder := NewPlanBuilder(MockContext(), s.is) + builder := NewPlanBuilder(MockContext(), s.is, &BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) c.Assert(err, IsNil) p, err = logicalOptimize(ctx, builder.optFlag, p.(LogicalPlan)) diff --git a/planner/core/optimizer.go b/planner/core/optimizer.go index 142e1b7203045..52a624ba87456 100644 --- a/planner/core/optimizer.go +++ b/planner/core/optimizer.go @@ -77,7 +77,7 @@ type logicalOptRule interface { func BuildLogicalPlan(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (Plan, error) { sctx.GetSessionVars().PlanID = 0 sctx.GetSessionVars().PlanColumnID = 0 - builder := NewPlanBuilder(sctx, is) + builder := NewPlanBuilder(sctx, is, &BlockHintProcessor{}) p, err := builder.Build(ctx, node) if err != nil { return nil, err diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index d8b3ca5ae729d..74ba9ea2b1fb6 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -1772,6 +1772,72 @@ func (s *testPlanSuite) TestIndexHint(c *C) { } else { c.Assert(warnings, HasLen, 0, comment) } + } +} + +func (s *testPlanSuite) TestQueryBlockHint(c *C) { + defer testleak.AfterTest(c)() + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer func() { + dom.Close() + store.Close() + }() + se, err := session.CreateSession4Test(store) + c.Assert(err, IsNil) + _, err = se.Execute(context.Background(), "use test") + c.Assert(err, IsNil) + + tests := []struct { + sql string + plan string + }{ + { + sql: "select /*+ SM_JOIN(@sel_1 t1), INL_JOIN(@sel_2 t3) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(test.t2.a,test.t3.c)}(test.t1.a,test.t2.a)->Projection", + }, + { + sql: "select /*+ SM_JOIN(@sel_1 t1), INL_JOIN(@qb t3) */ t1.a, t1.b from t t1, (select /*+ QB_NAME(qb) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(test.t2.a,test.t3.c)}(test.t1.a,test.t2.a)->Projection", + }, + { + sql: "select /*+ HASH_JOIN(@sel_1 t1), SM_JOIN(@sel_2 t2) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "RightHashJoin{TableReader(Table(t))->MergeInnerJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(test.t2.a,test.t3.c)}(test.t1.a,test.t2.a)->Projection", + }, + { + sql: "select /*+ HASH_JOIN(@sel_1 t1), SM_JOIN(@qb t2) */ t1.a, t1.b from t t1, (select /*+ QB_NAME(qb) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "RightHashJoin{TableReader(Table(t))->MergeInnerJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(test.t2.a,test.t3.c)}(test.t1.a,test.t2.a)->Projection", + }, + { + sql: "select /*+ INL_JOIN(@sel_1 t1), HASH_JOIN(@sel_2 t2) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "IndexJoin{TableReader(Table(t))->LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}(test.t2.a,test.t3.c)}(test.t2.a,test.t1.a)->Projection", + }, + { + sql: "select /*+ INL_JOIN(@sel_1 t1), HASH_JOIN(@qb t2) */ t1.a, t1.b from t t1, (select /*+ QB_NAME(qb) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", + plan: "IndexJoin{TableReader(Table(t))->LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}(test.t2.a,test.t3.c)}(test.t2.a,test.t1.a)->Projection", + }, + { + sql: "select /*+ HASH_AGG(@sel_1), STREAM_AGG(@sel_2) */ count(*) from t t1 where t1.a < (select count(*) from t t2 where t1.a > t2.a)", + plan: "Apply{TableReader(Table(t))->TableReader(Table(t)->Sel([gt(test.t1.a, test.t2.a)])->StreamAgg)->StreamAgg->Sel([not(isnull(count(*)))])}->HashAgg", + }, + { + sql: "select /*+ STREAM_AGG(@sel_1), HASH_AGG(@qb) */ count(*) from t t1 where t1.a < (select /*+ QB_NAME(qb) */ count(*) from t t2 where t1.a > t2.a)", + plan: "Apply{TableReader(Table(t))->TableReader(Table(t)->Sel([gt(test.t1.a, test.t2.a)])->HashAgg)->HashAgg->Sel([not(isnull(count(*)))])}->StreamAgg", + }, + { + sql: "select /*+ HASH_AGG(@sel_2) */ a, (select count(*) from t t1 where t1.b > t.a) from t where b > (select b from t t2 where t2.b = t.a limit 1)", + plan: "Apply{Apply{TableReader(Table(t))->TableReader(Table(t)->Sel([eq(test.t2.b, test.t.a)])->Limit)->Limit}->TableReader(Table(t)->Sel([gt(test.t1.b, test.t.a)])->HashAgg)->HashAgg}->Projection", + }, + } + ctx := context.TODO() + for i, tt := range tests { + comment := Commentf("case:%v sql: %s", i, tt.sql) + stmt, err := s.ParseOneStmt(tt.sql, "", "") + c.Assert(err, IsNil, comment) + + p, err := planner.Optimize(ctx, se, stmt, s.is) + c.Assert(err, IsNil, comment) + c.Assert(core.ToString(p), Equals, tt.plan, comment) } } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 73b2162efe7f6..eaa70b419cd44 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -208,6 +208,8 @@ type PlanBuilder struct { // If it's a join, we pop its children's out then merge them and push the new map to stack. // If we meet a subquery, it's clearly that it's a independent problem so we just pop one map out when we finish building the subquery. handleHelper *handleColHelper + + hintProcessor *BlockHintProcessor } type handleColHelper struct { @@ -284,12 +286,13 @@ func (b *PlanBuilder) GetOptFlag() uint64 { } // NewPlanBuilder creates a new PlanBuilder. -func NewPlanBuilder(sctx sessionctx.Context, is infoschema.InfoSchema) *PlanBuilder { +func NewPlanBuilder(sctx sessionctx.Context, is infoschema.InfoSchema, processor *BlockHintProcessor) *PlanBuilder { return &PlanBuilder{ - ctx: sctx, - is: is, - colMapper: make(map[*ast.ColumnNameExpr]int), - handleHelper: &handleColHelper{id2HandleMapStack: make([]map[int64][]*expression.Column, 0)}, + ctx: sctx, + is: is, + colMapper: make(map[*ast.ColumnNameExpr]int), + handleHelper: &handleColHelper{id2HandleMapStack: make([]map[int64][]*expression.Column, 0)}, + hintProcessor: processor, } } diff --git a/planner/core/planbuilder_test.go b/planner/core/planbuilder_test.go index 9c6a90ea6addb..c8495a2ae5d59 100644 --- a/planner/core/planbuilder_test.go +++ b/planner/core/planbuilder_test.go @@ -96,7 +96,7 @@ func (s *testPlanBuilderSuite) TestGetPathByIndexName(c *C) { } func (s *testPlanBuilderSuite) TestRewriterPool(c *C) { - builder := NewPlanBuilder(MockContext(), nil) + builder := NewPlanBuilder(MockContext(), nil, &BlockHintProcessor{}) // Make sure PlanBuilder.getExpressionRewriter() provides clean rewriter from pool. // First, pick one rewriter from the pool and make it dirty. @@ -149,7 +149,7 @@ func (s *testPlanBuilderSuite) TestDisableFold(c *C) { stmt := st.(*ast.SelectStmt) expr := stmt.Fields.Fields[0].Expr - builder := NewPlanBuilder(ctx, nil) + builder := NewPlanBuilder(ctx, nil, &BlockHintProcessor{}) builder.rewriterCounter++ rewriter := builder.getExpressionRewriter(context.TODO(), nil) c.Assert(rewriter, NotNil) diff --git a/planner/core/prepare_test.go b/planner/core/prepare_test.go index 6b53eb65a06a6..fb195868c54cd 100644 --- a/planner/core/prepare_test.go +++ b/planner/core/prepare_test.go @@ -163,7 +163,7 @@ func (s *testPlanSuite) TestPrepareCacheDeferredFunction(c *C) { stmt, err := s.ParseOneStmt(sql1, "", "") c.Check(err, IsNil) is := tk.Se.GetSessionVars().TxnCtx.InfoSchema.(infoschema.InfoSchema) - builder := core.NewPlanBuilder(tk.Se, is) + builder := core.NewPlanBuilder(tk.Se, is, &core.BlockHintProcessor{}) p, err := builder.Build(ctx, stmt) c.Check(err, IsNil) execPlan, ok := p.(*core.Execute) diff --git a/planner/optimize.go b/planner/optimize.go index 4502f39db2876..b44ba76ff5491 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -62,7 +62,9 @@ func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in // build logical plan sctx.GetSessionVars().PlanID = 0 sctx.GetSessionVars().PlanColumnID = 0 - builder := plannercore.NewPlanBuilder(sctx, is) + hintProcessor := &plannercore.BlockHintProcessor{Ctx: sctx} + node.Accept(hintProcessor) + builder := plannercore.NewPlanBuilder(sctx, is, hintProcessor) p, err := builder.Build(ctx, node) if err != nil { return nil, err