diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index cc796c16e8b89..6ab2ba83a48a3 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -637,7 +637,7 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ // Construct warning message prefix. errMsg := "Optimizer Hint TIDB_INLJ is inapplicable" if p.hintInfo != nil { - errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", p.hintInfo.restore2IndexJoinHint()) + errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(TiDBIndexNestedLoopJoin, p.hintInfo.indexNestedLoopJoinTables)) } // Append inapplicable reason. diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 4dec9cce3a246..194d8b9f5be18 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1841,15 +1841,15 @@ func (b *PlanBuilder) unfoldWildStar(p LogicalPlan, selectFields []*ast.SelectFi } func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool { - var sortMergeTables, INLJTables, hashJoinTables []model.CIStr + var sortMergeTables, INLJTables, hashJoinTables []hintTableInfo for _, hint := range hints { switch hint.HintName.L { case TiDBMergeJoin: - sortMergeTables = append(sortMergeTables, hint.Tables...) + sortMergeTables = tableNames2HintTableInfo(hint.Tables) case TiDBIndexNestedLoopJoin: - INLJTables = append(INLJTables, hint.Tables...) + INLJTables = tableNames2HintTableInfo(hint.Tables) case TiDBHashJoin: - hashJoinTables = append(hashJoinTables, hint.Tables...) + hashJoinTables = tableNames2HintTableInfo(hint.Tables) default: // ignore hints that not implemented } @@ -1866,9 +1866,23 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool { } func (b *PlanBuilder) popTableHints() { + hintInfo := b.tableHintInfo[len(b.tableHintInfo)-1] + b.appendUnmatchedJoinHintWarning(TiDBIndexNestedLoopJoin, hintInfo.indexNestedLoopJoinTables) + b.appendUnmatchedJoinHintWarning(TiDBMergeJoin, hintInfo.sortMergeJoinTables) + b.appendUnmatchedJoinHintWarning(TiDBHashJoin, hintInfo.hashJoinTables) b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1] } +func (b *PlanBuilder) appendUnmatchedJoinHintWarning(joinType string, hintTables []hintTableInfo) { + unMatchedTables := extractUnmatchedTables(hintTables) + if len(unMatchedTables) == 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(unMatchedTables, ", "), restore2JoinHint(joinType, hintTables)) + b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg)) +} + // TableHints returns the *tableHintInfo of PlanBuilder. func (b *PlanBuilder) TableHints() *tableHintInfo { if len(b.tableHintInfo) == 0 { diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index 2b8fc1230f63a..8a5b34fd20882 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -26,6 +26,7 @@ import ( "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/util/testleak" ) @@ -1434,3 +1435,57 @@ func (s *testPlanSuite) TestSemiJoinToInner(c *C) { c.Assert(err, IsNil) c.Assert(core.ToString(p), Equals, "Apply{TableReader(Table(t))->IndexJoin{IndexReader(Index(t.c_d_e)[[NULL,+inf]]->HashAgg)->HashAgg->IndexReader(Index(t.g)[[NULL,+inf]])}(t3.d,t2.g)}->StreamAgg") } + +func (s *testPlanSuite) TestUnmatchedTableInHint(c *C) { + defer testleak.AfterTest(c)() + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer func() { + dom.Close() + store.Close() + }() + se, err := session.CreateSession4Test(store) + c.Assert(err, IsNil) + _, err = se.Execute(context.Background(), "use test") + c.Assert(err, IsNil) + tests := []struct { + sql string + warning string + }{ + { + sql: "SELECT /*+ TIDB_SMJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a", + warning: "[planner:1815]There are no matching table names for (t3, t4) in optimizer hint /*+ TIDB_SMJ(t3, t4) */. Maybe you can use the table alias name", + }, + { + sql: "SELECT /*+ TIDB_HJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a", + warning: "[planner:1815]There are no matching table names for (t3, t4) in optimizer hint /*+ TIDB_HJ(t3, t4) */. Maybe you can use the table alias name", + }, + { + sql: "SELECT /*+ TIDB_INLJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a", + warning: "[planner:1815]There are no matching table names for (t3, t4) in optimizer hint /*+ TIDB_INLJ(t3, t4) */. Maybe you can use the table alias name", + }, + { + sql: "SELECT /*+ TIDB_SMJ(t1, t2) */ * from t t1, t t2 where t1.a = t2.a", + warning: "", + }, + { + sql: "SELECT /*+ TIDB_SMJ(t3, t4) */ * from t t1, t t2, t t3 where t1.a = t2.a and t2.a = t3.a", + warning: "[planner:1815]There are no matching table names for (t4) in optimizer hint /*+ TIDB_SMJ(t3, t4) */. Maybe you can use the table alias name", + }, + } + for _, test := range tests { + se.GetSessionVars().StmtCtx.SetWarnings(nil) + stmt, err := s.ParseOneStmt(test.sql, "", "") + c.Assert(err, IsNil) + _, err = planner.Optimize(se, stmt, s.is) + c.Assert(err, IsNil) + warnings := se.GetSessionVars().StmtCtx.GetWarnings() + if test.warning == "" { + c.Assert(len(warnings), Equals, 0) + } else { + c.Assert(len(warnings), Equals, 1) + c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning) + c.Assert(warnings[0].Err.Error(), Equals, test.warning) + } + } +} diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 6b33cbed6a8a5..02d095276c923 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -46,9 +46,25 @@ type visitInfo struct { } type tableHintInfo struct { - indexNestedLoopJoinTables []model.CIStr - sortMergeJoinTables []model.CIStr - hashJoinTables []model.CIStr + indexNestedLoopJoinTables []hintTableInfo + sortMergeJoinTables []hintTableInfo + hashJoinTables []hintTableInfo +} + +type hintTableInfo struct { + name model.CIStr + matched bool +} + +func tableNames2HintTableInfo(tableNames []model.CIStr) []hintTableInfo { + if len(tableNames) == 0 { + return nil + } + hintTables := make([]hintTableInfo, 0, len(tableNames)) + for _, tableName := range tableNames { + hintTables = append(hintTables, hintTableInfo{name: tableName}) + } + return hintTables } func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*model.CIStr) bool { @@ -71,25 +87,30 @@ func (info *tableHintInfo) ifPreferINLJ(tableNames ...*model.CIStr) bool { // Which it joins on with depend on sequence of traverse // and without reorder, user might adjust themselves. // This is similar to MySQL hints. -func (info *tableHintInfo) matchTableName(tables []*model.CIStr, tablesInHints []model.CIStr) bool { +func (info *tableHintInfo) matchTableName(tables []*model.CIStr, hintTables []hintTableInfo) bool { + hintMatched := false for _, tableName := range tables { if tableName == nil { continue } - for _, curEntry := range tablesInHints { - if curEntry.L == tableName.L { - return true + for i, curEntry := range hintTables { + if curEntry.name.L == tableName.L { + hintTables[i].matched = true + hintMatched = true + break } } } - return false + return hintMatched } -func (info *tableHintInfo) restore2IndexJoinHint() string { - buffer := bytes.NewBufferString("/*+ TIDB_INLJ(") - for i, tableName := range info.indexNestedLoopJoinTables { - buffer.WriteString(tableName.O) - if i < len(info.indexNestedLoopJoinTables)-1 { +func restore2JoinHint(hintType string, hintTables []hintTableInfo) string { + buffer := bytes.NewBufferString("/*+ ") + buffer.WriteString(strings.ToUpper(hintType)) + buffer.WriteString("(") + for i, table := range hintTables { + buffer.WriteString(table.name.L) + if i < len(hintTables)-1 { buffer.WriteString(", ") } } @@ -97,6 +118,16 @@ func (info *tableHintInfo) restore2IndexJoinHint() string { return buffer.String() } +func extractUnmatchedTables(hintTables []hintTableInfo) []string { + var tableNames []string + for _, table := range hintTables { + if !table.matched { + tableNames = append(tableNames, table.name.O) + } + } + return tableNames +} + // clauseCode indicates in which clause the column is currently. type clauseCode int