diff --git a/pkg/planner/core/casetest/hint/BUILD.bazel b/pkg/planner/core/casetest/hint/BUILD.bazel index 9db40cf4873e4..cf2c5aff0e10b 100644 --- a/pkg/planner/core/casetest/hint/BUILD.bazel +++ b/pkg/planner/core/casetest/hint/BUILD.bazel @@ -9,7 +9,7 @@ go_test( ], data = glob(["testdata/**"]), flaky = True, - shard_count = 6, + shard_count = 7, deps = [ "//pkg/config", "//pkg/domain", diff --git a/pkg/planner/core/casetest/hint/hint_test.go b/pkg/planner/core/casetest/hint/hint_test.go index d62bd7d59777f..98916fa3383d6 100644 --- a/pkg/planner/core/casetest/hint/hint_test.go +++ b/pkg/planner/core/casetest/hint/hint_test.go @@ -340,3 +340,29 @@ func TestOptimizeHintOnPartitionTable(t *testing.T) { tk.MustQuery("SELECT /*+ MAX_EXECUTION_TIME(10), dtc(name=tt) unknow(t1,t2) */ SLEEP(5)").Check(testkit.Rows("0")) require.Len(t, tk.Session().GetSessionVars().StmtCtx.GetWarnings(), 2) } + +func TestHints(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t1 (a int);") + tk.MustExec("create table t2 (a int);") + tk.MustExec("create table t3 (a int);") + var input []string + var output []struct { + SQL string + Plan []string + Warn []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery("explain format = 'brief' " + tt).Rows()) + output[i].Warn = testdata.ConvertRowsToStrings(tk.MustQuery("show warnings").Rows()) + }) + tk.MustQuery("explain format = 'brief' " + tt).Check(testkit.Rows(output[i].Plan...)) + tk.MustQuery("show warnings").Check(testkit.Rows(output[i].Warn...)) + } +} diff --git a/pkg/planner/core/casetest/hint/testdata/integration_suite_in.json b/pkg/planner/core/casetest/hint/testdata/integration_suite_in.json index 9959c3a7ca666..9edca6deacc28 100644 --- a/pkg/planner/core/casetest/hint/testdata/integration_suite_in.json +++ b/pkg/planner/core/casetest/hint/testdata/integration_suite_in.json @@ -180,5 +180,11 @@ "explain format = 'brief' select /*+ use_index(t, idx)*/ * from t", "explain format = 'brief' select /*+ use_index(t)*/ * from t" ] + }, + { + "name": "TestHints", + "cases": [ + "select * from t1, t2, t3 union all select /*+ leading(t3, t2) */ * from t1, t2, t3 union all select * from t1, t2, t3" + ] } ] diff --git a/pkg/planner/core/casetest/hint/testdata/integration_suite_out.json b/pkg/planner/core/casetest/hint/testdata/integration_suite_out.json index 369189e4531db..471b39747cf4a 100644 --- a/pkg/planner/core/casetest/hint/testdata/integration_suite_out.json +++ b/pkg/planner/core/casetest/hint/testdata/integration_suite_out.json @@ -325,7 +325,7 @@ " └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo" ], "Warn": [ - "[planner:1815]We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid" + "[planner:1815]leading hint is inapplicable, check if the leading hint table is valid" ] }, { @@ -347,7 +347,7 @@ " └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo" ], "Warn": [ - "[planner:1815]We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid" + "[planner:1815]leading hint is inapplicable, check if the leading hint table is valid" ] }, { @@ -1862,5 +1862,44 @@ "Warn": null } ] + }, + { + "Name": "TestHints", + "Cases": [ + { + "SQL": "select * from t1, t2, t3 union all select /*+ leading(t3, t2) */ * from t1, t2, t3 union all select * from t1, t2, t3", + "Plan": [ + "Union 3000000000000.00 root ", + "├─HashJoin 1000000000000.00 root CARTESIAN inner join", + "│ ├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ │ └─TableFullScan 10000.00 cop[tikv] table:t3 keep order:false, stats:pseudo", + "│ └─HashJoin(Probe) 100000000.00 root CARTESIAN inner join", + "│ ├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "│ └─TableReader(Probe) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "├─Projection 1000000000000.00 root test.t1.a->Column#19, test.t2.a->Column#20, test.t3.a->Column#21", + "│ └─HashJoin 1000000000000.00 root CARTESIAN inner join", + "│ ├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "│ └─HashJoin(Probe) 100000000.00 root CARTESIAN inner join", + "│ ├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "│ └─TableReader(Probe) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t3 keep order:false, stats:pseudo", + "└─HashJoin 1000000000000.00 root CARTESIAN inner join", + " ├─TableReader(Build) 10000.00 root data:TableFullScan", + " │ └─TableFullScan 10000.00 cop[tikv] table:t3 keep order:false, stats:pseudo", + " └─HashJoin(Probe) 100000000.00 root CARTESIAN inner join", + " ├─TableReader(Build) 10000.00 root data:TableFullScan", + " │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + " └─TableReader(Probe) 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warn": [ + "Warning 1815 leading hint is inapplicable, check if the leading hint table has join conditions with other tables" + ] + } + ] } ] diff --git a/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index c76dc15bf893d..c4c07335b7d7e 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -4199,6 +4199,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("We can only use the straight_join hint, when we use the leading hint and straight_join hint at the same time, all leading hints will be invalid")) } } +<<<<<<< HEAD b.tableHintInfo = append(b.tableHintInfo, tableHintInfo{ sortMergeJoinTables: sortMergeTables, broadcastJoinTables: bcTables, @@ -4215,6 +4216,24 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev indexMergeHintList: indexMergeHintList, timeRangeHint: timeRangeHint, limitHints: limitHints, +======= + b.tableHintInfo = append(b.tableHintInfo, &h.TableHintInfo{ + SortMergeJoinTables: sortMergeTables, + BroadcastJoinTables: bcTables, + ShuffleJoinTables: shuffleJoinTables, + IndexNestedLoopJoinTables: h.IndexNestedLoopJoinTables{INLJTables: inljTables, INLHJTables: inlhjTables, INLMJTables: inlmjTables}, + NoIndexJoinTables: h.IndexNestedLoopJoinTables{INLJTables: noIndexJoinTables, INLHJTables: noIndexHashJoinTables, INLMJTables: noIndexMergeJoinTables}, + HashJoinTables: hashJoinTables, + NoHashJoinTables: noHashJoinTables, + NoMergeJoinTables: noMergeJoinTables, + IndexHintList: indexHintList, + TiFlashTables: tiflashTables, + TiKVTables: tikvTables, + AggHints: aggHints, + IndexMergeHintList: indexMergeHintList, + TimeRangeHint: timeRangeHint, + LimitHints: limitHints, +>>>>>>> 0236944eab4 (planner: fix leading hint cannot take effect in UNION ALL statements (#50277)) MergeHints: MergeHints, leadingJoinOrder: leadingJoinOrder, hjBuildTables: hjBuildTables, @@ -4298,7 +4317,7 @@ func (b *PlanBuilder) TableHints() *tableHintInfo { if len(b.tableHintInfo) == 0 { return nil } - return &(b.tableHintInfo[len(b.tableHintInfo)-1]) + return b.tableHintInfo[len(b.tableHintInfo)-1] } func (b *PlanBuilder) buildSelect(ctx context.Context, sel *ast.SelectStmt) (p LogicalPlan, err error) { diff --git a/pkg/planner/core/planbuilder.go b/pkg/planner/core/planbuilder.go index c5b0a464d3896..db1be537a65b2 100644 --- a/pkg/planner/core/planbuilder.go +++ b/pkg/planner/core/planbuilder.go @@ -533,7 +533,11 @@ type PlanBuilder struct { colMapper map[*ast.ColumnNameExpr]int // visitInfo is used for privilege check. visitInfo []visitInfo +<<<<<<< HEAD tableHintInfo []tableHintInfo +======= + tableHintInfo []*hint.TableHintInfo +>>>>>>> 0236944eab4 (planner: fix leading hint cannot take effect in UNION ALL statements (#50277)) // optFlag indicates the flags of the optimizer rules. optFlag uint64 // capFlag indicates the capability flags. diff --git a/pkg/planner/core/rule_join_reorder_greedy.go b/pkg/planner/core/rule_join_reorder_greedy.go index 2cc01d48dd55e..f601591904b7c 100644 --- a/pkg/planner/core/rule_join_reorder_greedy.go +++ b/pkg/planner/core/rule_join_reorder_greedy.go @@ -79,7 +79,14 @@ func (s *joinReorderGreedySolver) solve(joinNodePlans []LogicalPlan, tracer *joi // Getting here means that there is no join condition between the table used in the leading hint and other tables // For example: select /*+ leading(t3) */ * from t1 join t2 on t1.a=t2.a cross join t3 // We can not let table t3 join first. +<<<<<<< HEAD s.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("leading hint is inapplicable, check if the leading hint table has join conditions with other tables")) +======= + // TODO(hawkingrei): we find the problem in the TestHint. + // `select * from t1, t2, t3 union all select /*+ leading(t3, t2) */ * from t1, t2, t3 union all select * from t1, t2, t3` + // this sql should not return the warning. but It will not affect the result. so we will fix it as soon as possible. + s.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.FastGen("leading hint is inapplicable, check if the leading hint table has join conditions with other tables")) +>>>>>>> 0236944eab4 (planner: fix leading hint cannot take effect in UNION ALL statements (#50277)) } cartesianGroup = append(cartesianGroup, newNode.p) } diff --git a/pkg/util/hint/hint.go b/pkg/util/hint/hint.go new file mode 100644 index 0000000000000..bd6baf2bd2063 --- /dev/null +++ b/pkg/util/hint/hint.go @@ -0,0 +1,634 @@ +// 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 hint + +import ( + "bytes" + "fmt" + "strings" + + mysql "github.com/pingcap/tidb/pkg/errno" + "github.com/pingcap/tidb/pkg/parser/ast" + "github.com/pingcap/tidb/pkg/parser/model" + "github.com/pingcap/tidb/pkg/sessionctx" + "github.com/pingcap/tidb/pkg/util/dbterror" +) + +// 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 +) + +const ( + // PreferINLJ indicates that the optimizer prefers to use index nested loop join. + PreferINLJ uint = 1 << iota + // PreferINLHJ indicates that the optimizer prefers to use index nested loop hash join. + PreferINLHJ + // PreferINLMJ indicates that the optimizer prefers to use index nested loop merge join. + PreferINLMJ + // PreferHJBuild indicates that the optimizer prefers to use hash join. + PreferHJBuild + // PreferHJProbe indicates that the optimizer prefers to use hash join. + PreferHJProbe + // PreferHashJoin indicates that the optimizer prefers to use hash join. + PreferHashJoin + // PreferNoHashJoin indicates that the optimizer prefers not to use hash join. + PreferNoHashJoin + // PreferMergeJoin indicates that the optimizer prefers to use merge join. + PreferMergeJoin + // PreferNoMergeJoin indicates that the optimizer prefers not to use merge join. + PreferNoMergeJoin + // PreferNoIndexJoin indicates that the optimizer prefers not to use index join. + PreferNoIndexJoin + // PreferNoIndexHashJoin indicates that the optimizer prefers not to use index hash join. + PreferNoIndexHashJoin + // PreferNoIndexMergeJoin indicates that the optimizer prefers not to use index merge join. + PreferNoIndexMergeJoin + // PreferBCJoin indicates that the optimizer prefers to use broadcast join. + PreferBCJoin + // PreferShuffleJoin indicates that the optimizer prefers to use shuffle join. + PreferShuffleJoin + // PreferRewriteSemiJoin indicates that the optimizer prefers to rewrite semi join. + PreferRewriteSemiJoin + + // PreferLeftAsINLJInner indicates that the optimizer prefers to use left child as inner child of index nested loop join. + PreferLeftAsINLJInner + // PreferRightAsINLJInner indicates that the optimizer prefers to use right child as inner child of index nested loop join. + PreferRightAsINLJInner + // PreferLeftAsINLHJInner indicates that the optimizer prefers to use left child as inner child of index nested loop hash join. + PreferLeftAsINLHJInner + // PreferRightAsINLHJInner indicates that the optimizer prefers to use right child as inner child of index nested loop hash join. + PreferRightAsINLHJInner + // PreferLeftAsINLMJInner indicates that the optimizer prefers to use left child as inner child of index nested loop merge join. + PreferLeftAsINLMJInner + // PreferRightAsINLMJInner indicates that the optimizer prefers to use right child as inner child of index nested loop merge join. + PreferRightAsINLMJInner + // PreferLeftAsHJBuild indicates that the optimizer prefers to use left child as build child of hash join. + PreferLeftAsHJBuild + // PreferRightAsHJBuild indicates that the optimizer prefers to use right child as build child of hash join. + PreferRightAsHJBuild + // PreferLeftAsHJProbe indicates that the optimizer prefers to use left child as probe child of hash join. + PreferLeftAsHJProbe + // PreferRightAsHJProbe indicates that the optimizer prefers to use right child as probe child of hash join. + PreferRightAsHJProbe + + // PreferHashAgg indicates that the optimizer prefers to use hash aggregation. + PreferHashAgg + // PreferStreamAgg indicates that the optimizer prefers to use stream aggregation. + PreferStreamAgg + // PreferMPP1PhaseAgg indicates that the optimizer prefers to use 1-phase aggregation. + PreferMPP1PhaseAgg + // PreferMPP2PhaseAgg indicates that the optimizer prefers to use 2-phase aggregation. + PreferMPP2PhaseAgg +) + +const ( + // PreferTiKV indicates that the optimizer prefers to use TiKV layer. + PreferTiKV = 1 << iota + // PreferTiFlash indicates that the optimizer prefers to use TiFlash layer. + PreferTiFlash +) + +// IndexNestedLoopJoinTables stores hint information about index nested loop join. +type IndexNestedLoopJoinTables struct { + INLJTables []TableInfo + INLHJTables []TableInfo + INLMJTables []TableInfo +} + +// TableHintInfo stores all table-level hint information. +// TableHintInfo is just another representation of ast.TableOptimizerHint, which is easier for the planner to use. +type TableHintInfo struct { + IndexNestedLoopJoinTables + NoIndexJoinTables IndexNestedLoopJoinTables + SortMergeJoinTables []TableInfo + BroadcastJoinTables []TableInfo + ShuffleJoinTables []TableInfo + HashJoinTables []TableInfo + NoHashJoinTables []TableInfo + NoMergeJoinTables []TableInfo + IndexHintList []IndexHintInfo + TiFlashTables []TableInfo + TiKVTables []TableInfo + AggHints AggHintInfo + IndexMergeHintList []IndexHintInfo + TimeRangeHint ast.HintTimeRange + LimitHints LimitHintInfo + MergeHints MergeHintInfo + LeadingJoinOrder []TableInfo + HJBuildTables []TableInfo + HJProbeTables []TableInfo +} + +// LimitHintInfo stores limit hint information. +type LimitHintInfo struct { + PreferLimitToCop bool +} + +// MergeHintInfo ...one bool flag for cte +type MergeHintInfo struct { + PreferMerge bool +} + +// TableInfo indicates which table this hint should take effect on. +type TableInfo struct { + DBName model.CIStr // the database name + TblName model.CIStr // the table name + Partitions []model.CIStr // partition information + SelectOffset int // the select block offset of this hint + Matched bool // whether this hint is applied successfully +} + +// IndexHintInfo indicates which index this hint should take effect on. +type IndexHintInfo struct { + DBName model.CIStr // the database name + TblName model.CIStr // the table name + Partitions []model.CIStr // partition information + IndexHint *ast.IndexHint // the original parser index hint structure + // 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 +} + +// Match checks whether the hint is matched with the given dbName and tblName. +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 +} + +// HintTypeString returns the string representation of the hint type. +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) +} + +// AggHintInfo stores Agg hint information. +type AggHintInfo struct { + PreferAggType uint + PreferAggToCop bool +} + +// IfPreferMergeJoin checks whether the join hint is merge join. +func (info *TableHintInfo) IfPreferMergeJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.SortMergeJoinTables) +} + +// IfPreferBroadcastJoin checks whether the join hint is broadcast join. +func (info *TableHintInfo) IfPreferBroadcastJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.BroadcastJoinTables) +} + +// IfPreferShuffleJoin checks whether the join hint is shuffle join. +func (info *TableHintInfo) IfPreferShuffleJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.ShuffleJoinTables) +} + +// IfPreferHashJoin checks whether the join hint is hash join. +func (info *TableHintInfo) IfPreferHashJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.HashJoinTables) +} + +// IfPreferNoHashJoin checks whether the join hint is no hash join. +func (info *TableHintInfo) IfPreferNoHashJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.NoHashJoinTables) +} + +// IfPreferNoMergeJoin checks whether the join hint is no merge join. +func (info *TableHintInfo) IfPreferNoMergeJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.NoMergeJoinTables) +} + +// IfPreferHJBuild checks whether the join hint is hash join build side. +func (info *TableHintInfo) IfPreferHJBuild(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.HJBuildTables) +} + +// IfPreferHJProbe checks whether the join hint is hash join probe side. +func (info *TableHintInfo) IfPreferHJProbe(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.HJProbeTables) +} + +// IfPreferINLJ checks whether the join hint is index nested loop join. +func (info *TableHintInfo) IfPreferINLJ(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.IndexNestedLoopJoinTables.INLJTables) +} + +// IfPreferINLHJ checks whether the join hint is index nested loop hash join. +func (info *TableHintInfo) IfPreferINLHJ(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.IndexNestedLoopJoinTables.INLHJTables) +} + +// IfPreferINLMJ checks whether the join hint is index nested loop merge join. +func (info *TableHintInfo) IfPreferINLMJ(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.IndexNestedLoopJoinTables.INLMJTables) +} + +// IfPreferNoIndexJoin checks whether the join hint is no index join. +func (info *TableHintInfo) IfPreferNoIndexJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.NoIndexJoinTables.INLJTables) +} + +// IfPreferNoIndexHashJoin checks whether the join hint is no index hash join. +func (info *TableHintInfo) IfPreferNoIndexHashJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.NoIndexJoinTables.INLHJTables) +} + +// IfPreferNoIndexMergeJoin checks whether the join hint is no index merge join. +func (info *TableHintInfo) IfPreferNoIndexMergeJoin(tableNames ...*TableInfo) bool { + return info.MatchTableName(tableNames, info.NoIndexJoinTables.INLMJTables) +} + +// IfPreferTiFlash checks whether the hint hit the need of TiFlash. +func (info *TableHintInfo) IfPreferTiFlash(tableName *TableInfo) *TableInfo { + return info.matchTiKVOrTiFlash(tableName, info.TiFlashTables) +} + +// IfPreferTiKV checks whether the hint hit the need of TiKV. +func (info *TableHintInfo) IfPreferTiKV(tableName *TableInfo) *TableInfo { + return info.matchTiKVOrTiFlash(tableName, info.TiKVTables) +} + +func (*TableHintInfo) matchTiKVOrTiFlash(tableName *TableInfo, hintTables []TableInfo) *TableInfo { + if tableName == nil { + return nil + } + for i, tbl := range hintTables { + if tableName.DBName.L == tbl.DBName.L && tableName.TblName.L == tbl.TblName.L && tbl.SelectOffset == tableName.SelectOffset { + hintTables[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 []*TableInfo, hintTables []TableInfo) 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 +} + +// RemoveDuplicatedHints removes duplicated hints in this hit list. +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 := RestoreTableOptimizerHint(hint) + if _, ok := m[key]; ok { + continue + } + m[key] = struct{}{} + res = append(res, hint) + } + return res +} + +// TableNames2HintTableInfo converts table names to TableInfo. +func TableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTables []ast.HintTable, p *QBHintHandler, currentOffset int) []TableInfo { + if len(hintTables) == 0 { + return nil + } + hintTableInfos := make([]TableInfo, 0, len(hintTables)) + defaultDBName := model.NewCIStr(ctx.GetSessionVars().CurrentDB) + isInapplicable := false + for _, hintTable := range hintTables { + tableInfo := TableInfo{ + 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 ...TableInfo) 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() +} + +// Restore2JoinHint restores join hint to string. +func Restore2JoinHint(hintType string, hintTables []TableInfo) 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() +} + +// Restore2IndexHint restores index hint to string. +func Restore2IndexHint(hintType string, hintIndex IndexHintInfo) string { + buffer := bytes.NewBufferString("/*+ ") + buffer.WriteString(strings.ToUpper(hintType)) + buffer.WriteString("(") + buffer.WriteString(restore2TableHint(TableInfo{ + 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() +} + +// Restore2StorageHint restores storage hint to string. +func Restore2StorageHint(tiflashTables, tikvTables []TableInfo) 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() +} + +// ExtractUnmatchedTables extracts unmatched tables from hintTables. +func ExtractUnmatchedTables(hintTables []TableInfo) []string { + var tableNames []string + for _, table := range hintTables { + if !table.Matched { + tableNames = append(tableNames, table.TblName.O) + } + } + return tableNames +} + +var ( + errInternal = dbterror.ClassOptimizer.NewStd(mysql.ErrInternal) +) + +// CollectUnmatchedHintWarnings collects warnings for unmatched hints from this TableHintInfo. +func CollectUnmatchedHintWarnings(hintInfo *TableHintInfo) (warnings []error) { + warnings = append(warnings, collectUnmatchedIndexHintWarning(hintInfo.IndexHintList, false)...) + warnings = append(warnings, collectUnmatchedIndexHintWarning(hintInfo.IndexMergeHintList, true)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintINLJ, TiDBIndexNestedLoopJoin, hintInfo.IndexNestedLoopJoinTables.INLJTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintINLHJ, "", hintInfo.IndexNestedLoopJoinTables.INLHJTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintINLMJ, "", hintInfo.IndexNestedLoopJoinTables.INLMJTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintSMJ, TiDBMergeJoin, hintInfo.SortMergeJoinTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintBCJ, TiDBBroadCastJoin, hintInfo.BroadcastJoinTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintShuffleJoin, HintShuffleJoin, hintInfo.ShuffleJoinTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintHJ, TiDBHashJoin, hintInfo.HashJoinTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintHashJoinBuild, "", hintInfo.HJBuildTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintHashJoinProbe, "", hintInfo.HJProbeTables)...) + warnings = append(warnings, collectUnmatchedJoinHintWarning(HintLeading, "", hintInfo.LeadingJoinOrder)...) + warnings = append(warnings, collectUnmatchedStorageHintWarning(hintInfo.TiFlashTables, hintInfo.TiKVTables)...) + return warnings +} + +func collectUnmatchedIndexHintWarning(indexHints []IndexHintInfo, usedForIndexMerge bool) (warnings []error) { + for _, hint := range indexHints { + if !hint.Matched { + var hintTypeString string + if usedForIndexMerge { + hintTypeString = "use_index_merge" + } else { + hintTypeString = hint.HintTypeString() + } + errMsg := fmt.Sprintf("%s(%s) is inapplicable, check whether the table(%s.%s) exists", + hintTypeString, + hint.IndexString(), + hint.DBName, + hint.TblName, + ) + warnings = append(warnings, errInternal.FastGen(errMsg)) + } + } + return warnings +} + +func collectUnmatchedJoinHintWarning(joinType string, joinTypeAlias string, hintTables []TableInfo) (warnings []error) { + unMatchedTables := ExtractUnmatchedTables(hintTables) + if len(unMatchedTables) == 0 { + return + } + if len(joinTypeAlias) != 0 { + joinTypeAlias = fmt.Sprintf(" or %s", Restore2JoinHint(joinTypeAlias, hintTables)) + } + + errMsg := fmt.Sprintf("There are no matching table names for (%s) in optimizer hint %s%s. Maybe you can use the table alias name", + strings.Join(unMatchedTables, ", "), Restore2JoinHint(joinType, hintTables), joinTypeAlias) + warnings = append(warnings, errInternal.GenWithStack(errMsg)) + return warnings +} + +func collectUnmatchedStorageHintWarning(tiflashTables, tikvTables []TableInfo) (warnings []error) { + unMatchedTiFlashTables := ExtractUnmatchedTables(tiflashTables) + unMatchedTiKVTables := ExtractUnmatchedTables(tikvTables) + if len(unMatchedTiFlashTables)+len(unMatchedTiKVTables) == 0 { + return + } + errMsg := fmt.Sprintf("There are no matching table names for (%s) in optimizer hint %s. Maybe you can use the table alias name", + strings.Join(append(unMatchedTiFlashTables, unMatchedTiKVTables...), ", "), + Restore2StorageHint(tiflashTables, tikvTables)) + warnings = append(warnings, errInternal.GenWithStack(errMsg)) + return warnings +} diff --git a/tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result b/tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result index 5cf778d646009..a9135cc00da33 100644 --- a/tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result +++ b/tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result @@ -5468,24 +5468,19 @@ IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core_ └─TableRowIDScan(Probe) 4.69 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select /*+ leading(t2, t3@sel_2) */ * from t1 join t2 on t1.a=t2.a where t1.a in (select t3.a from t3); id estRows task access object operator info -IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t1.a, inner key:planner__core__casetest__rule__rule_join_reorder.t2.a -├─IndexMergeJoin(Build) 3.75 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t3.a, inner key:planner__core__casetest__rule__rule_join_reorder.t1.a -│ ├─StreamAgg(Build) 3.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a -│ │ └─IndexReader 3.00 root index:StreamAgg -│ │ └─StreamAgg 3.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, -│ │ └─IndexFullScan 3.00 cop[tikv] table:t3, index:a(a) keep order:true -│ └─Projection(Probe) 3.75 root planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t1.b -│ └─IndexLookUp 3.75 root -│ ├─Selection(Build) 3.75 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) -│ │ └─IndexRangeScan 3.75 cop[tikv] table:t1, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t3.a)], keep order:true, stats:pseudo -│ └─TableRowIDScan(Probe) 3.75 cop[tikv] table:t1 keep order:false, stats:pseudo -└─Projection(Probe) 4.69 root planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t2.b - └─IndexLookUp 4.69 root - ├─Selection(Build) 4.69 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) - │ └─IndexRangeScan 4.69 cop[tikv] table:t2, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a)], keep order:true, stats:pseudo - └─TableRowIDScan(Probe) 4.69 cop[tikv] table:t2 keep order:false, stats:pseudo -Level Code Message -Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid +Projection 37462.50 root planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t1.b, planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t2.b +└─HashJoin 37462.50 root inner join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a) eq(planner__core__casetest__rule__rule_join_reorder.t3.a, planner__core__casetest__rule__rule_join_reorder.t1.a)] + ├─TableReader(Build) 9990.00 root data:Selection + │ └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) + │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo + └─HashJoin(Probe) 29970.00 root CARTESIAN inner join + ├─StreamAgg(Build) 3.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a + │ └─IndexReader 3.00 root index:StreamAgg + │ └─StreamAgg 3.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, + │ └─IndexFullScan 3.00 cop[tikv] table:t3, index:a(a) keep order:true + └─TableReader(Probe) 9990.00 root data:Selection + └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) + └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select /*+ leading(t1, t3@sel_2) */ * from t1 join t2 on t1.a=t2.a where t1.a in (select t3.a from t3); id estRows task access object operator info IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t1.a, inner key:planner__core__casetest__rule__rule_join_reorder.t2.a @@ -5504,28 +5499,21 @@ IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core_ ├─Selection(Build) 4.69 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) │ └─IndexRangeScan 4.69 cop[tikv] table:t2, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a)], keep order:true, stats:pseudo └─TableRowIDScan(Probe) 4.69 cop[tikv] table:t2 keep order:false, stats:pseudo -Level Code Message -Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid explain format = 'brief' select /*+ leading(t3@sel_2, t2) */ * from t1 join t2 on t1.a=t2.a where t1.a in (select t3.a from t3); id estRows task access object operator info -IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t1.a, inner key:planner__core__casetest__rule__rule_join_reorder.t2.a -├─IndexMergeJoin(Build) 3.75 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t3.a, inner key:planner__core__casetest__rule__rule_join_reorder.t1.a -│ ├─StreamAgg(Build) 3.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a -│ │ └─IndexReader 3.00 root index:StreamAgg -│ │ └─StreamAgg 3.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, -│ │ └─IndexFullScan 3.00 cop[tikv] table:t3, index:a(a) keep order:true -│ └─Projection(Probe) 3.75 root planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t1.b -│ └─IndexLookUp 3.75 root -│ ├─Selection(Build) 3.75 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) -│ │ └─IndexRangeScan 3.75 cop[tikv] table:t1, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t3.a)], keep order:true, stats:pseudo -│ └─TableRowIDScan(Probe) 3.75 cop[tikv] table:t1 keep order:false, stats:pseudo -└─Projection(Probe) 4.69 root planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t2.b - └─IndexLookUp 4.69 root - ├─Selection(Build) 4.69 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) - │ └─IndexRangeScan 4.69 cop[tikv] table:t2, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a)], keep order:true, stats:pseudo - └─TableRowIDScan(Probe) 4.69 cop[tikv] table:t2 keep order:false, stats:pseudo -Level Code Message -Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid +Projection 37462.50 root planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t1.b, planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t2.b +└─HashJoin 37462.50 root inner join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a) eq(planner__core__casetest__rule__rule_join_reorder.t3.a, planner__core__casetest__rule__rule_join_reorder.t1.a)] + ├─TableReader(Build) 9990.00 root data:Selection + │ └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) + │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo + └─HashJoin(Probe) 29970.00 root CARTESIAN inner join + ├─StreamAgg(Build) 3.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a + │ └─IndexReader 3.00 root index:StreamAgg + │ └─StreamAgg 3.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, + │ └─IndexFullScan 3.00 cop[tikv] table:t3, index:a(a) keep order:true + └─TableReader(Probe) 9990.00 root data:Selection + └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) + └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select /*+ leading(t3@sel_2, t1) */ * from t1 join t2 on t1.a=t2.a where t1.a in (select t3.a from t3); id estRows task access object operator info IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core__casetest__rule__rule_join_reorder.t1.a, inner key:planner__core__casetest__rule__rule_join_reorder.t2.a @@ -5544,8 +5532,6 @@ IndexMergeJoin 4.69 root inner join, inner:Projection, outer key:planner__core_ ├─Selection(Build) 4.69 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) │ └─IndexRangeScan 4.69 cop[tikv] table:t2, index:a(a) range: decided by [eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a)], keep order:true, stats:pseudo └─TableRowIDScan(Probe) 4.69 cop[tikv] table:t2 keep order:false, stats:pseudo -Level Code Message -Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid explain format = 'brief' select /*+ leading(t4) */ * from t1 join t2 on t1.a=t2.a join t4 on t1.b = t4.b where t1.a not in (select t3.a from t3); id estRows task access object operator info HashJoin 12475.01 root Null-aware anti semi join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t3.a)] @@ -7157,20 +7143,19 @@ HashJoin 12487.50 root left outer join, equal:[eq(planner__core__casetest__rule └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select /*+ leading(t2, t3@sel_2) */ * from t1 join t2 on t1.a=t2.a where t1.a in (select t3.a from t3); id estRows task access object operator info -HashJoin 12487.50 root inner join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t3.a)] -├─StreamAgg(Build) 7992.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a -│ └─IndexReader 7992.00 root index:StreamAgg -│ └─StreamAgg 7992.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, -│ └─IndexFullScan 9990.00 cop[tikv] table:t3, index:a(a) keep order:true, stats:pseudo -└─HashJoin(Probe) 12487.50 root inner join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t2.a)] +Projection 99800100.00 root planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t1.b, planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t2.b +└─HashJoin 99800100.00 root inner join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t2.a, planner__core__casetest__rule__rule_join_reorder.t1.a) eq(planner__core__casetest__rule__rule_join_reorder.t3.a, planner__core__casetest__rule__rule_join_reorder.t1.a)] ├─TableReader(Build) 9990.00 root data:Selection - │ └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) - │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo - └─TableReader(Probe) 9990.00 root data:Selection - └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) - └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo -Level Code Message -Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid + │ └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t1.a)) + │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo + └─HashJoin(Probe) 79840080.00 root CARTESIAN inner join + ├─StreamAgg(Build) 7992.00 root group by:planner__core__casetest__rule__rule_join_reorder.t3.a, funcs:firstrow(planner__core__casetest__rule__rule_join_reorder.t3.a)->planner__core__casetest__rule__rule_join_reorder.t3.a + │ └─IndexReader 7992.00 root index:StreamAgg + │ └─StreamAgg 7992.00 cop[tikv] group by:planner__core__casetest__rule__rule_join_reorder.t3.a, + │ └─IndexFullScan 9990.00 cop[tikv] table:t3, index:a(a) keep order:true, stats:pseudo + └─TableReader(Probe) 9990.00 root data:Selection + └─Selection 9990.00 cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t2.a)) + └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select /*+ leading(t4) */ * from t1 left join t2 on t1.a=t2.a right join t4 on t1.b = t4.b where t1.a not in (select t3.a from t3); id estRows task access object operator info HashJoin 12487.50 root Null-aware anti semi join, equal:[eq(planner__core__casetest__rule__rule_join_reorder.t1.a, planner__core__casetest__rule__rule_join_reorder.t3.a)]