From d18ee3f68abca35f63f7bd564d3b364634cf76e7 Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Wed, 20 Dec 2023 15:25:55 +0800 Subject: [PATCH] planner: move hint code to two separate code files (#49597) ref pingcap/tidb#48875 --- pkg/planner/core/BUILD.bazel | 1 + pkg/planner/core/hint_utils.go | 416 +++++++++++++++++ pkg/planner/core/hints.go | 553 ++++++++++++----------- pkg/planner/core/logical_plan_builder.go | 95 ---- pkg/planner/core/planbuilder.go | 330 -------------- 5 files changed, 710 insertions(+), 685 deletions(-) create mode 100644 pkg/planner/core/hint_utils.go diff --git a/pkg/planner/core/BUILD.bazel b/pkg/planner/core/BUILD.bazel index 844335cf3e..f22ae41ccc 100644 --- a/pkg/planner/core/BUILD.bazel +++ b/pkg/planner/core/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "fragment.go", "handle_cols.go", "hashcode.go", + "hint_utils.go", "hints.go", "indexmerge_path.go", "initialize.go", diff --git a/pkg/planner/core/hint_utils.go b/pkg/planner/core/hint_utils.go new file mode 100644 index 0000000000..0bcf0f6c1d --- /dev/null +++ b/pkg/planner/core/hint_utils.go @@ -0,0 +1,416 @@ +// Copyright 2023 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, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "bytes" + "fmt" + "strings" + + "github.com/pingcap/tidb/pkg/kv" + "github.com/pingcap/tidb/pkg/parser/ast" + "github.com/pingcap/tidb/pkg/parser/model" + "github.com/pingcap/tidb/pkg/sessionctx" + utilhint "github.com/pingcap/tidb/pkg/util/hint" +) + +// GenHintsFromFlatPlan generates hints from a FlatPhysicalPlan. +func GenHintsFromFlatPlan(flat *FlatPhysicalPlan) []*ast.TableOptimizerHint { + if len(flat.Main) == 0 { + return nil + } + nodeTp := utilhint.TypeSelect + switch flat.Main[0].Origin.(type) { + case *Update: + nodeTp = utilhint.TypeUpdate + case *Delete: + nodeTp = utilhint.TypeDelete + } + var hints []*ast.TableOptimizerHint + selectPlan, _ := flat.Main.GetSelectPlan() + if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan { + return nil + } + for _, op := range selectPlan { + p := op.Origin.(PhysicalPlan) + hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints) + } + for _, cte := range flat.CTEs { + for i, op := range cte { + if i == 0 || !op.IsRoot { + continue + } + p := op.Origin.(PhysicalPlan) + hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints) + } + } + return removeDuplicatedHints(hints) +} + +// GenHintsFromPhysicalPlan generates hints from physical plan. +func GenHintsFromPhysicalPlan(p Plan) []*ast.TableOptimizerHint { + flat := FlattenPhysicalPlan(p, false) + return GenHintsFromFlatPlan(flat) +} + +func getTableName(tblName model.CIStr, asName *model.CIStr) model.CIStr { + if asName != nil && asName.L != "" { + return *asName + } + return tblName +} + +func extractTableAsName(p PhysicalPlan) (*model.CIStr, *model.CIStr) { + if len(p.Children()) > 1 { + return nil, nil + } + switch x := p.(type) { + case *PhysicalTableReader: + ts := x.TablePlans[0].(*PhysicalTableScan) + if ts.TableAsName.L != "" { + return &ts.DBName, ts.TableAsName + } + return &ts.DBName, &ts.Table.Name + case *PhysicalIndexReader: + is := x.IndexPlans[0].(*PhysicalIndexScan) + if is.TableAsName.L != "" { + return &is.DBName, is.TableAsName + } + return &is.DBName, &is.Table.Name + case *PhysicalIndexLookUpReader: + is := x.IndexPlans[0].(*PhysicalIndexScan) + if is.TableAsName.L != "" { + return &is.DBName, is.TableAsName + } + return &is.DBName, &is.Table.Name + case *PhysicalSort, *PhysicalSelection, *PhysicalUnionScan, *PhysicalProjection: + return extractTableAsName(p.Children()[0]) + } + return nil, nil +} + +func getJoinHints(sctx sessionctx.Context, joinType string, parentOffset int, nodeType utilhint.NodeType, children ...PhysicalPlan) (res []*ast.TableOptimizerHint) { + if parentOffset == -1 { + return res + } + for _, child := range children { + blockOffset := child.SelectBlockOffset() + if blockOffset == -1 { + continue + } + var dbName, tableName *model.CIStr + if blockOffset != parentOffset { + var blockAsNames []ast.HintTable + if p := sctx.GetSessionVars().PlannerSelectBlockAsName.Load(); p != nil { + blockAsNames = *p + } + if blockOffset >= len(blockAsNames) { + continue + } + hintTable := blockAsNames[blockOffset] + // For sub-queries like `(select * from t) t1`, t1 should belong to its surrounding select block. + dbName, tableName, blockOffset = &hintTable.DBName, &hintTable.TableName, parentOffset + } else { + dbName, tableName = extractTableAsName(child) + } + if tableName == nil || tableName.L == "" { + continue + } + qbName, err := utilhint.GenerateQBName(nodeType, blockOffset) + if err != nil { + continue + } + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(joinType), + Tables: []ast.HintTable{{DBName: *dbName, TableName: *tableName}}, + }) + break + } + return res +} + +func genHintsFromSingle(p PhysicalPlan, nodeType utilhint.NodeType, storeType kv.StoreType, res []*ast.TableOptimizerHint) []*ast.TableOptimizerHint { + qbName, err := utilhint.GenerateQBName(nodeType, p.SelectBlockOffset()) + if err != nil { + return res + } + switch pp := p.(type) { + case *PhysicalLimit, *PhysicalTopN: + if storeType == kv.TiKV { + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintLimitToCop), + }) + } + case *PhysicalTableReader: + tbl, ok := pp.TablePlans[0].(*PhysicalTableScan) + if !ok { + return res + } + if tbl.StoreType == kv.TiFlash { + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintReadFromStorage), + HintData: model.NewCIStr(kv.TiFlash.Name()), + Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, + }) + } else { + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintUseIndex), + Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, + }) + if tbl.Table.PKIsHandle || tbl.Table.IsCommonHandle { // it's a primary key + orderHint := HintOrderIndex + if !tbl.KeepOrder { + orderHint = HintNoOrderIndex + } + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(orderHint), + Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, + Indexes: []model.CIStr{model.NewCIStr("primary")}, + }) + } + } + case *PhysicalIndexLookUpReader: + index := pp.IndexPlans[0].(*PhysicalIndexScan) + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintUseIndex), + Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, + Indexes: []model.CIStr{index.Index.Name}, + }) + orderHint := HintOrderIndex + if !index.KeepOrder { + orderHint = HintNoOrderIndex + } + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(orderHint), + Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, + Indexes: []model.CIStr{index.Index.Name}, + }) + case *PhysicalIndexReader: + index := pp.IndexPlans[0].(*PhysicalIndexScan) + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintUseIndex), + Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, + Indexes: []model.CIStr{index.Index.Name}, + }) + orderHint := HintOrderIndex + if !index.KeepOrder { + orderHint = HintNoOrderIndex + } + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(orderHint), + Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, + Indexes: []model.CIStr{index.Index.Name}, + }) + case *PhysicalIndexMergeReader: + indexs := make([]model.CIStr, 0, 2) + var tableName model.CIStr + var tableAsName *model.CIStr + for _, partialPlan := range pp.PartialPlans { + if index, ok := partialPlan[0].(*PhysicalIndexScan); ok { + indexs = append(indexs, index.Index.Name) + tableName = index.Table.Name + tableAsName = index.TableAsName + } else { + indexName := model.NewCIStr("PRIMARY") + indexs = append(indexs, indexName) + } + } + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintIndexMerge), + Tables: []ast.HintTable{{TableName: getTableName(tableName, tableAsName)}}, + Indexes: indexs, + }) + case *PhysicalHashAgg: + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintHashAgg), + }) + if storeType == kv.TiKV { + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintAggToCop), + }) + } + case *PhysicalStreamAgg: + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintStreamAgg), + }) + if storeType == kv.TiKV { + res = append(res, &ast.TableOptimizerHint{ + QBName: qbName, + HintName: model.NewCIStr(HintAggToCop), + }) + } + case *PhysicalMergeJoin: + res = append(res, getJoinHints(p.SCtx(), HintSMJ, p.SelectBlockOffset(), nodeType, pp.children...)...) + case *PhysicalHashJoin: + // TODO: support the hash_join_build and hash_join_probe hint for auto capture + res = append(res, getJoinHints(p.SCtx(), HintHJ, p.SelectBlockOffset(), nodeType, pp.children...)...) + case *PhysicalIndexJoin: + res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) + case *PhysicalIndexMergeJoin: + res = append(res, getJoinHints(p.SCtx(), HintINLMJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) + case *PhysicalIndexHashJoin: + res = append(res, getJoinHints(p.SCtx(), HintINLHJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) + } + return res +} + +func removeDuplicatedHints(hints []*ast.TableOptimizerHint) []*ast.TableOptimizerHint { + if len(hints) < 2 { + return hints + } + m := make(map[string]struct{}, len(hints)) + res := make([]*ast.TableOptimizerHint, 0, len(hints)) + for _, hint := range hints { + key := utilhint.RestoreTableOptimizerHint(hint) + if _, ok := m[key]; ok { + continue + } + m[key] = struct{}{} + res = append(res, hint) + } + return res +} + +func tableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTables []ast.HintTable, p *utilhint.BlockHintProcessor, currentOffset int) []hintTableInfo { + if len(hintTables) == 0 { + return nil + } + hintTableInfos := make([]hintTableInfo, 0, len(hintTables)) + defaultDBName := model.NewCIStr(ctx.GetSessionVars().CurrentDB) + isInapplicable := false + for _, hintTable := range hintTables { + tableInfo := hintTableInfo{ + dbName: hintTable.DBName, + tblName: hintTable.TableName, + partitions: hintTable.PartitionList, + selectOffset: p.GetHintOffset(hintTable.QBName, currentOffset), + } + if tableInfo.dbName.L == "" { + tableInfo.dbName = defaultDBName + } + switch hintName { + case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ, HintLeading: + if len(tableInfo.partitions) > 0 { + isInapplicable = true + } + } + hintTableInfos = append(hintTableInfos, tableInfo) + } + if isInapplicable { + ctx.GetSessionVars().StmtCtx.AppendWarning( + fmt.Errorf("Optimizer Hint %s is inapplicable on specified partitions", + restore2JoinHint(hintName, hintTableInfos))) + return nil + } + return hintTableInfos +} + +func restore2TableHint(hintTables ...hintTableInfo) string { + buffer := bytes.NewBufferString("") + for i, table := range hintTables { + buffer.WriteString(table.tblName.L) + if len(table.partitions) > 0 { + buffer.WriteString(" PARTITION(") + for j, partition := range table.partitions { + if j > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(partition.L) + } + buffer.WriteString(")") + } + if i < len(hintTables)-1 { + buffer.WriteString(", ") + } + } + return buffer.String() +} + +func restore2JoinHint(hintType string, hintTables []hintTableInfo) string { + if len(hintTables) == 0 { + return strings.ToUpper(hintType) + } + buffer := bytes.NewBufferString("/*+ ") + buffer.WriteString(strings.ToUpper(hintType)) + buffer.WriteString("(") + buffer.WriteString(restore2TableHint(hintTables...)) + buffer.WriteString(") */") + return buffer.String() +} + +func restore2IndexHint(hintType string, hintIndex indexHintInfo) string { + buffer := bytes.NewBufferString("/*+ ") + buffer.WriteString(strings.ToUpper(hintType)) + buffer.WriteString("(") + buffer.WriteString(restore2TableHint(hintTableInfo{ + dbName: hintIndex.dbName, + tblName: hintIndex.tblName, + partitions: hintIndex.partitions, + })) + if hintIndex.indexHint != nil && len(hintIndex.indexHint.IndexNames) > 0 { + for i, indexName := range hintIndex.indexHint.IndexNames { + if i > 0 { + buffer.WriteString(",") + } + buffer.WriteString(" " + indexName.L) + } + } + buffer.WriteString(") */") + return buffer.String() +} + +func restore2StorageHint(tiflashTables, tikvTables []hintTableInfo) string { + buffer := bytes.NewBufferString("/*+ ") + buffer.WriteString(strings.ToUpper(HintReadFromStorage)) + buffer.WriteString("(") + if len(tiflashTables) > 0 { + buffer.WriteString("tiflash[") + buffer.WriteString(restore2TableHint(tiflashTables...)) + buffer.WriteString("]") + if len(tikvTables) > 0 { + buffer.WriteString(", ") + } + } + if len(tikvTables) > 0 { + buffer.WriteString("tikv[") + buffer.WriteString(restore2TableHint(tikvTables...)) + buffer.WriteString("]") + } + buffer.WriteString(") */") + return buffer.String() +} + +func extractUnmatchedTables(hintTables []hintTableInfo) []string { + var tableNames []string + for _, table := range hintTables { + if !table.matched { + tableNames = append(tableNames, table.tblName.O) + } + } + return tableNames +} diff --git a/pkg/planner/core/hints.go b/pkg/planner/core/hints.go index 2eafe2aefe..1b371fb134 100644 --- a/pkg/planner/core/hints.go +++ b/pkg/planner/core/hints.go @@ -15,279 +15,312 @@ package core import ( - "github.com/pingcap/tidb/pkg/kv" + "fmt" + "strings" + "github.com/pingcap/tidb/pkg/parser/ast" "github.com/pingcap/tidb/pkg/parser/model" - "github.com/pingcap/tidb/pkg/sessionctx" - utilhint "github.com/pingcap/tidb/pkg/util/hint" ) -// GenHintsFromFlatPlan generates hints from a FlatPhysicalPlan. -func GenHintsFromFlatPlan(flat *FlatPhysicalPlan) []*ast.TableOptimizerHint { - if len(flat.Main) == 0 { +// Hint flags listed here are used by PlanBuilder.subQueryHintFlags. +const ( + // TiDBMergeJoin is hint enforce merge join. + TiDBMergeJoin = "tidb_smj" + // HintSMJ is hint enforce merge join. + HintSMJ = "merge_join" + // HintNoMergeJoin is the hint to enforce the query not to use merge join. + HintNoMergeJoin = "no_merge_join" + + // TiDBBroadCastJoin indicates applying broadcast join by force. + TiDBBroadCastJoin = "tidb_bcj" + // HintBCJ indicates applying broadcast join by force. + HintBCJ = "broadcast_join" + // HintShuffleJoin indicates applying shuffle join by force. + HintShuffleJoin = "shuffle_join" + + // HintStraightJoin causes TiDB to join tables in the order in which they appear in the FROM clause. + HintStraightJoin = "straight_join" + // HintLeading specifies the set of tables to be used as the prefix in the execution plan. + HintLeading = "leading" + + // TiDBIndexNestedLoopJoin is hint enforce index nested loop join. + TiDBIndexNestedLoopJoin = "tidb_inlj" + // HintINLJ is hint enforce index nested loop join. + HintINLJ = "inl_join" + // HintINLHJ is hint enforce index nested loop hash join. + HintINLHJ = "inl_hash_join" + // HintINLMJ is hint enforce index nested loop merge join. + HintINLMJ = "inl_merge_join" + // HintNoIndexJoin is the hint to enforce the query not to use index join. + HintNoIndexJoin = "no_index_join" + // HintNoIndexHashJoin is the hint to enforce the query not to use index hash join. + HintNoIndexHashJoin = "no_index_hash_join" + // HintNoIndexMergeJoin is the hint to enforce the query not to use index merge join. + HintNoIndexMergeJoin = "no_index_merge_join" + // TiDBHashJoin is hint enforce hash join. + TiDBHashJoin = "tidb_hj" + // HintNoHashJoin is the hint to enforce the query not to use hash join. + HintNoHashJoin = "no_hash_join" + // HintHJ is hint enforce hash join. + HintHJ = "hash_join" + // HintHashJoinBuild is hint enforce hash join's build side + HintHashJoinBuild = "hash_join_build" + // HintHashJoinProbe is hint enforce hash join's probe side + HintHashJoinProbe = "hash_join_probe" + // HintHashAgg is hint enforce hash aggregation. + HintHashAgg = "hash_agg" + // HintStreamAgg is hint enforce stream aggregation. + HintStreamAgg = "stream_agg" + // HintMPP1PhaseAgg enforces the optimizer to use the mpp-1phase aggregation. + HintMPP1PhaseAgg = "mpp_1phase_agg" + // HintMPP2PhaseAgg enforces the optimizer to use the mpp-2phase aggregation. + HintMPP2PhaseAgg = "mpp_2phase_agg" + // HintUseIndex is hint enforce using some indexes. + HintUseIndex = "use_index" + // HintIgnoreIndex is hint enforce ignoring some indexes. + HintIgnoreIndex = "ignore_index" + // HintForceIndex make optimizer to use this index even if it thinks a table scan is more efficient. + HintForceIndex = "force_index" + // HintOrderIndex is hint enforce using some indexes and keep the index's order. + HintOrderIndex = "order_index" + // HintNoOrderIndex is hint enforce using some indexes and not keep the index's order. + HintNoOrderIndex = "no_order_index" + // HintAggToCop is hint enforce pushing aggregation to coprocessor. + HintAggToCop = "agg_to_cop" + // HintReadFromStorage is hint enforce some tables read from specific type of storage. + HintReadFromStorage = "read_from_storage" + // HintTiFlash is a label represents the tiflash storage type. + HintTiFlash = "tiflash" + // HintTiKV is a label represents the tikv storage type. + HintTiKV = "tikv" + // HintIndexMerge is a hint to enforce using some indexes at the same time. + HintIndexMerge = "use_index_merge" + // HintTimeRange is a hint to specify the time range for metrics summary tables + HintTimeRange = "time_range" + // HintIgnorePlanCache is a hint to enforce ignoring plan cache + HintIgnorePlanCache = "ignore_plan_cache" + // HintLimitToCop is a hint enforce pushing limit or topn to coprocessor. + HintLimitToCop = "limit_to_cop" + // HintMerge is a hint which can switch turning inline for the CTE. + HintMerge = "merge" + // HintSemiJoinRewrite is a hint to force we rewrite the semi join operator as much as possible. + HintSemiJoinRewrite = "semi_join_rewrite" + // HintNoDecorrelate indicates a LogicalApply not to be decorrelated. + HintNoDecorrelate = "no_decorrelate" + + // HintMemoryQuota sets the memory limit for a query + HintMemoryQuota = "memory_quota" + // HintUseToja is a hint to optimize `in (select ...)` subquery into `join` + HintUseToja = "use_toja" + // HintNoIndexMerge is a hint to disable index merge + HintNoIndexMerge = "no_index_merge" + // HintMaxExecutionTime specifies the max allowed execution time in milliseconds + HintMaxExecutionTime = "max_execution_time" + + // HintFlagSemiJoinRewrite corresponds to HintSemiJoinRewrite. + HintFlagSemiJoinRewrite uint64 = 1 << iota + // HintFlagNoDecorrelate corresponds to HintNoDecorrelate. + HintFlagNoDecorrelate +) + +type indexNestedLoopJoinTables struct { + inljTables []hintTableInfo + inlhjTables []hintTableInfo + inlmjTables []hintTableInfo +} + +type tableHintInfo struct { + indexNestedLoopJoinTables + noIndexJoinTables indexNestedLoopJoinTables + sortMergeJoinTables []hintTableInfo + broadcastJoinTables []hintTableInfo + shuffleJoinTables []hintTableInfo + hashJoinTables []hintTableInfo + noHashJoinTables []hintTableInfo + noMergeJoinTables []hintTableInfo + indexHintList []indexHintInfo + tiflashTables []hintTableInfo + tikvTables []hintTableInfo + aggHints aggHintInfo + indexMergeHintList []indexHintInfo + timeRangeHint ast.HintTimeRange + limitHints limitHintInfo + MergeHints MergeHintInfo + leadingJoinOrder []hintTableInfo + hjBuildTables []hintTableInfo + hjProbeTables []hintTableInfo +} + +type limitHintInfo struct { + preferLimitToCop bool +} + +// MergeHintInfo ...one bool flag for cte +type MergeHintInfo struct { + preferMerge bool +} + +type hintTableInfo struct { + dbName model.CIStr + tblName model.CIStr + partitions []model.CIStr + selectOffset int + matched bool +} + +type indexHintInfo struct { + dbName model.CIStr + tblName model.CIStr + partitions []model.CIStr + indexHint *ast.IndexHint + // Matched indicates whether this index hint + // has been successfully applied to a DataSource. + // If an indexHintInfo is not matched after building + // a Select statement, we will generate a warning for it. + matched bool +} + +func (hint *indexHintInfo) match(dbName, tblName model.CIStr) bool { + return hint.tblName.L == tblName.L && + (hint.dbName.L == dbName.L || + hint.dbName.L == "*") // for universal bindings, e.g. *.t +} + +func (hint *indexHintInfo) hintTypeString() string { + switch hint.indexHint.HintType { + case ast.HintUse: + return "use_index" + case ast.HintIgnore: + return "ignore_index" + case ast.HintForce: + return "force_index" + } + return "" +} + +// indexString formats the indexHint as dbName.tableName[, indexNames]. +func (hint *indexHintInfo) indexString() string { + var indexListString string + indexList := make([]string, len(hint.indexHint.IndexNames)) + for i := range hint.indexHint.IndexNames { + indexList[i] = hint.indexHint.IndexNames[i].L + } + if len(indexList) > 0 { + indexListString = fmt.Sprintf(", %s", strings.Join(indexList, ", ")) + } + return fmt.Sprintf("%s.%s%s", hint.dbName, hint.tblName, indexListString) +} + +type aggHintInfo struct { + preferAggType uint + preferAggToCop bool +} + +func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.sortMergeJoinTables) +} + +func (info *tableHintInfo) ifPreferBroadcastJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.broadcastJoinTables) +} + +func (info *tableHintInfo) ifPreferShuffleJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.shuffleJoinTables) +} + +func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.hashJoinTables) +} + +func (info *tableHintInfo) ifPreferNoHashJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.noHashJoinTables) +} + +func (info *tableHintInfo) ifPreferNoMergeJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.noMergeJoinTables) +} + +func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.hjBuildTables) +} + +func (info *tableHintInfo) ifPreferHJProbe(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.hjProbeTables) +} + +func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables) +} + +func (info *tableHintInfo) ifPreferINLHJ(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlhjTables) +} + +func (info *tableHintInfo) ifPreferINLMJ(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlmjTables) +} + +func (info *tableHintInfo) ifPreferNoIndexJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.noIndexJoinTables.inljTables) +} + +func (info *tableHintInfo) ifPreferNoIndexHashJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.noIndexJoinTables.inlhjTables) +} + +func (info *tableHintInfo) ifPreferNoIndexMergeJoin(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.noIndexJoinTables.inlmjTables) +} + +func (info *tableHintInfo) ifPreferTiFlash(tableName *hintTableInfo) *hintTableInfo { + if tableName == nil { return nil } - nodeTp := utilhint.TypeSelect - switch flat.Main[0].Origin.(type) { - case *Update: - nodeTp = utilhint.TypeUpdate - case *Delete: - nodeTp = utilhint.TypeDelete + for i, tbl := range info.tiflashTables { + if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset { + info.tiflashTables[i].matched = true + return &tbl + } } - var hints []*ast.TableOptimizerHint - selectPlan, _ := flat.Main.GetSelectPlan() - if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan { + return nil +} + +func (info *tableHintInfo) ifPreferTiKV(tableName *hintTableInfo) *hintTableInfo { + if tableName == nil { return nil } - for _, op := range selectPlan { - p := op.Origin.(PhysicalPlan) - hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints) + for i, tbl := range info.tikvTables { + if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset { + info.tikvTables[i].matched = true + return &tbl + } } - for _, cte := range flat.CTEs { - for i, op := range cte { - if i == 0 || !op.IsRoot { + return nil +} + +// matchTableName checks whether the hint hit the need. +// Only need either side matches one on the list. +// Even though you can put 2 tables on the list, +// it doesn't mean optimizer will reorder to make them +// join directly. +// Which it joins on with depend on sequence of traverse +// and without reorder, user might adjust themselves. +// This is similar to MySQL hints. +func (*tableHintInfo) matchTableName(tables []*hintTableInfo, hintTables []hintTableInfo) bool { + hintMatched := false + for _, table := range tables { + for i, curEntry := range hintTables { + if table == nil { continue } - p := op.Origin.(PhysicalPlan) - hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints) - } - } - return removeDuplicatedHints(hints) -} - -// GenHintsFromPhysicalPlan generates hints from physical plan. -func GenHintsFromPhysicalPlan(p Plan) []*ast.TableOptimizerHint { - flat := FlattenPhysicalPlan(p, false) - return GenHintsFromFlatPlan(flat) -} - -func getTableName(tblName model.CIStr, asName *model.CIStr) model.CIStr { - if asName != nil && asName.L != "" { - return *asName - } - return tblName -} - -func extractTableAsName(p PhysicalPlan) (*model.CIStr, *model.CIStr) { - if len(p.Children()) > 1 { - return nil, nil - } - switch x := p.(type) { - case *PhysicalTableReader: - ts := x.TablePlans[0].(*PhysicalTableScan) - if ts.TableAsName.L != "" { - return &ts.DBName, ts.TableAsName - } - return &ts.DBName, &ts.Table.Name - case *PhysicalIndexReader: - is := x.IndexPlans[0].(*PhysicalIndexScan) - if is.TableAsName.L != "" { - return &is.DBName, is.TableAsName - } - return &is.DBName, &is.Table.Name - case *PhysicalIndexLookUpReader: - is := x.IndexPlans[0].(*PhysicalIndexScan) - if is.TableAsName.L != "" { - return &is.DBName, is.TableAsName - } - return &is.DBName, &is.Table.Name - case *PhysicalSort, *PhysicalSelection, *PhysicalUnionScan, *PhysicalProjection: - return extractTableAsName(p.Children()[0]) - } - return nil, nil -} - -func getJoinHints(sctx sessionctx.Context, joinType string, parentOffset int, nodeType utilhint.NodeType, children ...PhysicalPlan) (res []*ast.TableOptimizerHint) { - if parentOffset == -1 { - return res - } - for _, child := range children { - blockOffset := child.SelectBlockOffset() - if blockOffset == -1 { - continue - } - var dbName, tableName *model.CIStr - if blockOffset != parentOffset { - var blockAsNames []ast.HintTable - if p := sctx.GetSessionVars().PlannerSelectBlockAsName.Load(); p != nil { - blockAsNames = *p - } - if blockOffset >= len(blockAsNames) { - continue - } - hintTable := blockAsNames[blockOffset] - // For sub-queries like `(select * from t) t1`, t1 should belong to its surrounding select block. - dbName, tableName, blockOffset = &hintTable.DBName, &hintTable.TableName, parentOffset - } else { - dbName, tableName = extractTableAsName(child) - } - if tableName == nil || tableName.L == "" { - continue - } - qbName, err := utilhint.GenerateQBName(nodeType, blockOffset) - if err != nil { - continue - } - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(joinType), - Tables: []ast.HintTable{{DBName: *dbName, TableName: *tableName}}, - }) - break - } - return res -} - -func genHintsFromSingle(p PhysicalPlan, nodeType utilhint.NodeType, storeType kv.StoreType, res []*ast.TableOptimizerHint) []*ast.TableOptimizerHint { - qbName, err := utilhint.GenerateQBName(nodeType, p.SelectBlockOffset()) - if err != nil { - return res - } - switch pp := p.(type) { - case *PhysicalLimit, *PhysicalTopN: - if storeType == kv.TiKV { - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintLimitToCop), - }) - } - case *PhysicalTableReader: - tbl, ok := pp.TablePlans[0].(*PhysicalTableScan) - if !ok { - return res - } - if tbl.StoreType == kv.TiFlash { - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintReadFromStorage), - HintData: model.NewCIStr(kv.TiFlash.Name()), - Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, - }) - } else { - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintUseIndex), - Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, - }) - if tbl.Table.PKIsHandle || tbl.Table.IsCommonHandle { // it's a primary key - orderHint := HintOrderIndex - if !tbl.KeepOrder { - orderHint = HintNoOrderIndex - } - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(orderHint), - Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}}, - Indexes: []model.CIStr{model.NewCIStr("primary")}, - }) + if (curEntry.dbName.L == table.dbName.L || curEntry.dbName.L == "*") && + curEntry.tblName.L == table.tblName.L && + table.selectOffset == curEntry.selectOffset { + hintTables[i].matched = true + hintMatched = true + break } } - case *PhysicalIndexLookUpReader: - index := pp.IndexPlans[0].(*PhysicalIndexScan) - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintUseIndex), - Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, - Indexes: []model.CIStr{index.Index.Name}, - }) - orderHint := HintOrderIndex - if !index.KeepOrder { - orderHint = HintNoOrderIndex - } - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(orderHint), - Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, - Indexes: []model.CIStr{index.Index.Name}, - }) - case *PhysicalIndexReader: - index := pp.IndexPlans[0].(*PhysicalIndexScan) - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintUseIndex), - Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, - Indexes: []model.CIStr{index.Index.Name}, - }) - orderHint := HintOrderIndex - if !index.KeepOrder { - orderHint = HintNoOrderIndex - } - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(orderHint), - Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}}, - Indexes: []model.CIStr{index.Index.Name}, - }) - case *PhysicalIndexMergeReader: - indexs := make([]model.CIStr, 0, 2) - var tableName model.CIStr - var tableAsName *model.CIStr - for _, partialPlan := range pp.PartialPlans { - if index, ok := partialPlan[0].(*PhysicalIndexScan); ok { - indexs = append(indexs, index.Index.Name) - tableName = index.Table.Name - tableAsName = index.TableAsName - } else { - indexName := model.NewCIStr("PRIMARY") - indexs = append(indexs, indexName) - } - } - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintIndexMerge), - Tables: []ast.HintTable{{TableName: getTableName(tableName, tableAsName)}}, - Indexes: indexs, - }) - case *PhysicalHashAgg: - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintHashAgg), - }) - if storeType == kv.TiKV { - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintAggToCop), - }) - } - case *PhysicalStreamAgg: - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintStreamAgg), - }) - if storeType == kv.TiKV { - res = append(res, &ast.TableOptimizerHint{ - QBName: qbName, - HintName: model.NewCIStr(HintAggToCop), - }) - } - case *PhysicalMergeJoin: - res = append(res, getJoinHints(p.SCtx(), HintSMJ, p.SelectBlockOffset(), nodeType, pp.children...)...) - case *PhysicalHashJoin: - // TODO: support the hash_join_build and hash_join_probe hint for auto capture - res = append(res, getJoinHints(p.SCtx(), HintHJ, p.SelectBlockOffset(), nodeType, pp.children...)...) - case *PhysicalIndexJoin: - res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) - case *PhysicalIndexMergeJoin: - res = append(res, getJoinHints(p.SCtx(), HintINLMJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) - case *PhysicalIndexHashJoin: - res = append(res, getJoinHints(p.SCtx(), HintINLHJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) } - return res -} - -func removeDuplicatedHints(hints []*ast.TableOptimizerHint) []*ast.TableOptimizerHint { - if len(hints) < 2 { - return hints - } - m := make(map[string]struct{}, len(hints)) - res := make([]*ast.TableOptimizerHint, 0, len(hints)) - for _, hint := range hints { - key := utilhint.RestoreTableOptimizerHint(hint) - if _, ok := m[key]; ok { - continue - } - m[key] = struct{}{} - res = append(res, hint) - } - return res + return hintMatched } diff --git a/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index 2d241056f2..5841af4469 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -69,101 +69,6 @@ import ( "github.com/pingcap/tipb/go-tipb" ) -const ( - // TiDBMergeJoin is hint enforce merge join. - TiDBMergeJoin = "tidb_smj" - // HintSMJ is hint enforce merge join. - HintSMJ = "merge_join" - // HintNoMergeJoin is the hint to enforce the query not to use merge join. - HintNoMergeJoin = "no_merge_join" - - // TiDBBroadCastJoin indicates applying broadcast join by force. - TiDBBroadCastJoin = "tidb_bcj" - // HintBCJ indicates applying broadcast join by force. - HintBCJ = "broadcast_join" - // HintShuffleJoin indicates applying shuffle join by force. - HintShuffleJoin = "shuffle_join" - - // HintStraightJoin causes TiDB to join tables in the order in which they appear in the FROM clause. - HintStraightJoin = "straight_join" - // HintLeading specifies the set of tables to be used as the prefix in the execution plan. - HintLeading = "leading" - - // TiDBIndexNestedLoopJoin is hint enforce index nested loop join. - TiDBIndexNestedLoopJoin = "tidb_inlj" - // HintINLJ is hint enforce index nested loop join. - HintINLJ = "inl_join" - // HintINLHJ is hint enforce index nested loop hash join. - HintINLHJ = "inl_hash_join" - // HintINLMJ is hint enforce index nested loop merge join. - HintINLMJ = "inl_merge_join" - // HintNoIndexJoin is the hint to enforce the query not to use index join. - HintNoIndexJoin = "no_index_join" - // HintNoIndexHashJoin is the hint to enforce the query not to use index hash join. - HintNoIndexHashJoin = "no_index_hash_join" - // HintNoIndexMergeJoin is the hint to enforce the query not to use index merge join. - HintNoIndexMergeJoin = "no_index_merge_join" - // TiDBHashJoin is hint enforce hash join. - TiDBHashJoin = "tidb_hj" - // HintNoHashJoin is the hint to enforce the query not to use hash join. - HintNoHashJoin = "no_hash_join" - // HintHJ is hint enforce hash join. - HintHJ = "hash_join" - // HintHashJoinBuild is hint enforce hash join's build side - HintHashJoinBuild = "hash_join_build" - // HintHashJoinProbe is hint enforce hash join's probe side - HintHashJoinProbe = "hash_join_probe" - // HintHashAgg is hint enforce hash aggregation. - HintHashAgg = "hash_agg" - // HintStreamAgg is hint enforce stream aggregation. - HintStreamAgg = "stream_agg" - // HintMPP1PhaseAgg enforces the optimizer to use the mpp-1phase aggregation. - HintMPP1PhaseAgg = "mpp_1phase_agg" - // HintMPP2PhaseAgg enforces the optimizer to use the mpp-2phase aggregation. - HintMPP2PhaseAgg = "mpp_2phase_agg" - // HintUseIndex is hint enforce using some indexes. - HintUseIndex = "use_index" - // HintIgnoreIndex is hint enforce ignoring some indexes. - HintIgnoreIndex = "ignore_index" - // HintForceIndex make optimizer to use this index even if it thinks a table scan is more efficient. - HintForceIndex = "force_index" - // HintOrderIndex is hint enforce using some indexes and keep the index's order. - HintOrderIndex = "order_index" - // HintNoOrderIndex is hint enforce using some indexes and not keep the index's order. - HintNoOrderIndex = "no_order_index" - // HintAggToCop is hint enforce pushing aggregation to coprocessor. - HintAggToCop = "agg_to_cop" - // HintReadFromStorage is hint enforce some tables read from specific type of storage. - HintReadFromStorage = "read_from_storage" - // HintTiFlash is a label represents the tiflash storage type. - HintTiFlash = "tiflash" - // HintTiKV is a label represents the tikv storage type. - HintTiKV = "tikv" - // HintIndexMerge is a hint to enforce using some indexes at the same time. - HintIndexMerge = "use_index_merge" - // HintTimeRange is a hint to specify the time range for metrics summary tables - HintTimeRange = "time_range" - // HintIgnorePlanCache is a hint to enforce ignoring plan cache - HintIgnorePlanCache = "ignore_plan_cache" - // HintLimitToCop is a hint enforce pushing limit or topn to coprocessor. - HintLimitToCop = "limit_to_cop" - // HintMerge is a hint which can switch turning inline for the CTE. - HintMerge = "merge" - // HintSemiJoinRewrite is a hint to force we rewrite the semi join operator as much as possible. - HintSemiJoinRewrite = "semi_join_rewrite" - // HintNoDecorrelate indicates a LogicalApply not to be decorrelated. - HintNoDecorrelate = "no_decorrelate" - - // HintMemoryQuota sets the memory limit for a query - HintMemoryQuota = "memory_quota" - // HintUseToja is a hint to optimize `in (select ...)` subquery into `join` - HintUseToja = "use_toja" - // HintNoIndexMerge is a hint to disable index merge - HintNoIndexMerge = "no_index_merge" - // HintMaxExecutionTime specifies the max allowed execution time in milliseconds - HintMaxExecutionTime = "max_execution_time" -) - const ( // ErrExprInSelect is in select fields for the error of ErrFieldNotInGroupBy ErrExprInSelect = "SELECT list" diff --git a/pkg/planner/core/planbuilder.go b/pkg/planner/core/planbuilder.go index f0f644a4b1..62f3c75d20 100644 --- a/pkg/planner/core/planbuilder.go +++ b/pkg/planner/core/planbuilder.go @@ -15,7 +15,6 @@ package core import ( - "bytes" "context" "encoding/binary" "fmt" @@ -82,99 +81,6 @@ type visitInfo struct { dynamicWithGrant bool } -type indexNestedLoopJoinTables struct { - inljTables []hintTableInfo - inlhjTables []hintTableInfo - inlmjTables []hintTableInfo -} - -type tableHintInfo struct { - indexNestedLoopJoinTables - noIndexJoinTables indexNestedLoopJoinTables - sortMergeJoinTables []hintTableInfo - broadcastJoinTables []hintTableInfo - shuffleJoinTables []hintTableInfo - hashJoinTables []hintTableInfo - noHashJoinTables []hintTableInfo - noMergeJoinTables []hintTableInfo - indexHintList []indexHintInfo - tiflashTables []hintTableInfo - tikvTables []hintTableInfo - aggHints aggHintInfo - indexMergeHintList []indexHintInfo - timeRangeHint ast.HintTimeRange - limitHints limitHintInfo - MergeHints MergeHintInfo - leadingJoinOrder []hintTableInfo - hjBuildTables []hintTableInfo - hjProbeTables []hintTableInfo -} - -type limitHintInfo struct { - preferLimitToCop bool -} - -// MergeHintInfo ...one bool flag for cte -type MergeHintInfo struct { - preferMerge bool -} - -type hintTableInfo struct { - dbName model.CIStr - tblName model.CIStr - partitions []model.CIStr - selectOffset int - matched bool -} - -type indexHintInfo struct { - dbName model.CIStr - tblName model.CIStr - partitions []model.CIStr - indexHint *ast.IndexHint - // Matched indicates whether this index hint - // has been successfully applied to a DataSource. - // If an indexHintInfo is not matched after building - // a Select statement, we will generate a warning for it. - matched bool -} - -func (hint *indexHintInfo) match(dbName, tblName model.CIStr) bool { - return hint.tblName.L == tblName.L && - (hint.dbName.L == dbName.L || - hint.dbName.L == "*") // for universal bindings, e.g. *.t -} - -func (hint *indexHintInfo) hintTypeString() string { - switch hint.indexHint.HintType { - case ast.HintUse: - return "use_index" - case ast.HintIgnore: - return "ignore_index" - case ast.HintForce: - return "force_index" - } - return "" -} - -// indexString formats the indexHint as dbName.tableName[, indexNames]. -func (hint *indexHintInfo) indexString() string { - var indexListString string - indexList := make([]string, len(hint.indexHint.IndexNames)) - for i := range hint.indexHint.IndexNames { - indexList[i] = hint.indexHint.IndexNames[i].L - } - if len(indexList) > 0 { - indexListString = fmt.Sprintf(", %s", strings.Join(indexList, ", ")) - } - return fmt.Sprintf("%s.%s%s", hint.dbName, hint.tblName, indexListString) -} - -type aggHintInfo struct { - preferAggType uint - preferAggToCop bool -} - // QueryTimeRange represents a time range specified by TIME_RANGE hint type QueryTimeRange struct { From time.Time @@ -197,234 +103,6 @@ func (tr *QueryTimeRange) MemoryUsage() (sum int64) { return emptyQueryTimeRangeSize } -func tableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTables []ast.HintTable, p *hint.BlockHintProcessor, currentOffset int) []hintTableInfo { - if len(hintTables) == 0 { - return nil - } - hintTableInfos := make([]hintTableInfo, 0, len(hintTables)) - defaultDBName := model.NewCIStr(ctx.GetSessionVars().CurrentDB) - isInapplicable := false - for _, hintTable := range hintTables { - tableInfo := hintTableInfo{ - dbName: hintTable.DBName, - tblName: hintTable.TableName, - partitions: hintTable.PartitionList, - selectOffset: p.GetHintOffset(hintTable.QBName, currentOffset), - } - if tableInfo.dbName.L == "" { - tableInfo.dbName = defaultDBName - } - switch hintName { - case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ, HintLeading: - if len(tableInfo.partitions) > 0 { - isInapplicable = true - } - } - hintTableInfos = append(hintTableInfos, tableInfo) - } - if isInapplicable { - ctx.GetSessionVars().StmtCtx.AppendWarning( - fmt.Errorf("Optimizer Hint %s is inapplicable on specified partitions", - restore2JoinHint(hintName, hintTableInfos))) - return nil - } - return hintTableInfos -} - -func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.sortMergeJoinTables) -} - -func (info *tableHintInfo) ifPreferBroadcastJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.broadcastJoinTables) -} - -func (info *tableHintInfo) ifPreferShuffleJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.shuffleJoinTables) -} - -func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.hashJoinTables) -} - -func (info *tableHintInfo) ifPreferNoHashJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.noHashJoinTables) -} - -func (info *tableHintInfo) ifPreferNoMergeJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.noMergeJoinTables) -} - -func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.hjBuildTables) -} - -func (info *tableHintInfo) ifPreferHJProbe(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.hjProbeTables) -} - -func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables) -} - -func (info *tableHintInfo) ifPreferINLHJ(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlhjTables) -} - -func (info *tableHintInfo) ifPreferINLMJ(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlmjTables) -} - -func (info *tableHintInfo) ifPreferNoIndexJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.noIndexJoinTables.inljTables) -} - -func (info *tableHintInfo) ifPreferNoIndexHashJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.noIndexJoinTables.inlhjTables) -} - -func (info *tableHintInfo) ifPreferNoIndexMergeJoin(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.noIndexJoinTables.inlmjTables) -} - -func (info *tableHintInfo) ifPreferTiFlash(tableName *hintTableInfo) *hintTableInfo { - if tableName == nil { - return nil - } - for i, tbl := range info.tiflashTables { - if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset { - info.tiflashTables[i].matched = true - return &tbl - } - } - return nil -} - -func (info *tableHintInfo) ifPreferTiKV(tableName *hintTableInfo) *hintTableInfo { - if tableName == nil { - return nil - } - for i, tbl := range info.tikvTables { - if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset { - info.tikvTables[i].matched = true - return &tbl - } - } - return nil -} - -// matchTableName checks whether the hint hit the need. -// Only need either side matches one on the list. -// Even though you can put 2 tables on the list, -// it doesn't mean optimizer will reorder to make them -// join directly. -// Which it joins on with depend on sequence of traverse -// and without reorder, user might adjust themselves. -// This is similar to MySQL hints. -func (*tableHintInfo) matchTableName(tables []*hintTableInfo, hintTables []hintTableInfo) bool { - hintMatched := false - for _, table := range tables { - for i, curEntry := range hintTables { - if table == nil { - continue - } - if (curEntry.dbName.L == table.dbName.L || curEntry.dbName.L == "*") && - curEntry.tblName.L == table.tblName.L && - table.selectOffset == curEntry.selectOffset { - hintTables[i].matched = true - hintMatched = true - break - } - } - } - return hintMatched -} - -func restore2TableHint(hintTables ...hintTableInfo) string { - buffer := bytes.NewBufferString("") - for i, table := range hintTables { - buffer.WriteString(table.tblName.L) - if len(table.partitions) > 0 { - buffer.WriteString(" PARTITION(") - for j, partition := range table.partitions { - if j > 0 { - buffer.WriteString(", ") - } - buffer.WriteString(partition.L) - } - buffer.WriteString(")") - } - if i < len(hintTables)-1 { - buffer.WriteString(", ") - } - } - return buffer.String() -} - -func restore2JoinHint(hintType string, hintTables []hintTableInfo) string { - if len(hintTables) == 0 { - return strings.ToUpper(hintType) - } - buffer := bytes.NewBufferString("/*+ ") - buffer.WriteString(strings.ToUpper(hintType)) - buffer.WriteString("(") - buffer.WriteString(restore2TableHint(hintTables...)) - buffer.WriteString(") */") - return buffer.String() -} - -func restore2IndexHint(hintType string, hintIndex indexHintInfo) string { - buffer := bytes.NewBufferString("/*+ ") - buffer.WriteString(strings.ToUpper(hintType)) - buffer.WriteString("(") - buffer.WriteString(restore2TableHint(hintTableInfo{ - dbName: hintIndex.dbName, - tblName: hintIndex.tblName, - partitions: hintIndex.partitions, - })) - if hintIndex.indexHint != nil && len(hintIndex.indexHint.IndexNames) > 0 { - for i, indexName := range hintIndex.indexHint.IndexNames { - if i > 0 { - buffer.WriteString(",") - } - buffer.WriteString(" " + indexName.L) - } - } - buffer.WriteString(") */") - return buffer.String() -} - -func restore2StorageHint(tiflashTables, tikvTables []hintTableInfo) string { - buffer := bytes.NewBufferString("/*+ ") - buffer.WriteString(strings.ToUpper(HintReadFromStorage)) - buffer.WriteString("(") - if len(tiflashTables) > 0 { - buffer.WriteString("tiflash[") - buffer.WriteString(restore2TableHint(tiflashTables...)) - buffer.WriteString("]") - if len(tikvTables) > 0 { - buffer.WriteString(", ") - } - } - if len(tikvTables) > 0 { - buffer.WriteString("tikv[") - buffer.WriteString(restore2TableHint(tikvTables...)) - buffer.WriteString("]") - } - buffer.WriteString(") */") - return buffer.String() -} - -func extractUnmatchedTables(hintTables []hintTableInfo) []string { - var tableNames []string - for _, table := range hintTables { - if !table.matched { - tableNames = append(tableNames, table.tblName.O) - } - } - return tableNames -} - // clauseCode indicates in which clause the column is currently. type clauseCode int @@ -517,14 +195,6 @@ const ( handlingScalarSubquery ) -// Hint flags listed here are used by PlanBuilder.subQueryHintFlags. -const ( - // HintFlagSemiJoinRewrite corresponds to HintSemiJoinRewrite. - HintFlagSemiJoinRewrite uint64 = 1 << iota - // HintFlagNoDecorrelate corresponds to HintNoDecorrelate. - HintFlagNoDecorrelate -) - // PlanBuilder builds Plan from an ast.Node. // It just builds the ast node straightforwardly. type PlanBuilder struct {