Skip to content

Commit bab9e90

Browse files
alivxxxzz-jason
authored andcommitted
planner/core: raise warning for unmatched join hint (#9914)
1 parent 8a7c60d commit bab9e90

File tree

4 files changed

+118
-18
lines changed

4 files changed

+118
-18
lines changed

planner/core/exhaust_physical_plans.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ
637637
// Construct warning message prefix.
638638
errMsg := "Optimizer Hint TIDB_INLJ is inapplicable"
639639
if p.hintInfo != nil {
640-
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", p.hintInfo.restore2IndexJoinHint())
640+
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(TiDBIndexNestedLoopJoin, p.hintInfo.indexNestedLoopJoinTables))
641641
}
642642

643643
// Append inapplicable reason.

planner/core/logical_plan_builder.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,15 +1841,15 @@ func (b *PlanBuilder) unfoldWildStar(p LogicalPlan, selectFields []*ast.SelectFi
18411841
}
18421842

18431843
func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool {
1844-
var sortMergeTables, INLJTables, hashJoinTables []model.CIStr
1844+
var sortMergeTables, INLJTables, hashJoinTables []hintTableInfo
18451845
for _, hint := range hints {
18461846
switch hint.HintName.L {
18471847
case TiDBMergeJoin:
1848-
sortMergeTables = append(sortMergeTables, hint.Tables...)
1848+
sortMergeTables = tableNames2HintTableInfo(hint.Tables)
18491849
case TiDBIndexNestedLoopJoin:
1850-
INLJTables = append(INLJTables, hint.Tables...)
1850+
INLJTables = tableNames2HintTableInfo(hint.Tables)
18511851
case TiDBHashJoin:
1852-
hashJoinTables = append(hashJoinTables, hint.Tables...)
1852+
hashJoinTables = tableNames2HintTableInfo(hint.Tables)
18531853
default:
18541854
// ignore hints that not implemented
18551855
}
@@ -1866,9 +1866,23 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool {
18661866
}
18671867

18681868
func (b *PlanBuilder) popTableHints() {
1869+
hintInfo := b.tableHintInfo[len(b.tableHintInfo)-1]
1870+
b.appendUnmatchedJoinHintWarning(TiDBIndexNestedLoopJoin, hintInfo.indexNestedLoopJoinTables)
1871+
b.appendUnmatchedJoinHintWarning(TiDBMergeJoin, hintInfo.sortMergeJoinTables)
1872+
b.appendUnmatchedJoinHintWarning(TiDBHashJoin, hintInfo.hashJoinTables)
18691873
b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1]
18701874
}
18711875

1876+
func (b *PlanBuilder) appendUnmatchedJoinHintWarning(joinType string, hintTables []hintTableInfo) {
1877+
unMatchedTables := extractUnmatchedTables(hintTables)
1878+
if len(unMatchedTables) == 0 {
1879+
return
1880+
}
1881+
errMsg := fmt.Sprintf("There are no matching table names for (%s) in optimizer hint %s. Maybe you can use the table alias name",
1882+
strings.Join(unMatchedTables, ", "), restore2JoinHint(joinType, hintTables))
1883+
b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg))
1884+
}
1885+
18721886
// TableHints returns the *tableHintInfo of PlanBuilder.
18731887
func (b *PlanBuilder) TableHints() *tableHintInfo {
18741888
if len(b.tableHintInfo) == 0 {

planner/core/physical_plan_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/pingcap/tidb/planner/core"
2727
"github.com/pingcap/tidb/session"
2828
"github.com/pingcap/tidb/sessionctx"
29+
"github.com/pingcap/tidb/sessionctx/stmtctx"
2930
"github.com/pingcap/tidb/util/testleak"
3031
)
3132

@@ -1434,3 +1435,57 @@ func (s *testPlanSuite) TestSemiJoinToInner(c *C) {
14341435
c.Assert(err, IsNil)
14351436
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")
14361437
}
1438+
1439+
func (s *testPlanSuite) TestUnmatchedTableInHint(c *C) {
1440+
defer testleak.AfterTest(c)()
1441+
store, dom, err := newStoreWithBootstrap()
1442+
c.Assert(err, IsNil)
1443+
defer func() {
1444+
dom.Close()
1445+
store.Close()
1446+
}()
1447+
se, err := session.CreateSession4Test(store)
1448+
c.Assert(err, IsNil)
1449+
_, err = se.Execute(context.Background(), "use test")
1450+
c.Assert(err, IsNil)
1451+
tests := []struct {
1452+
sql string
1453+
warning string
1454+
}{
1455+
{
1456+
sql: "SELECT /*+ TIDB_SMJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a",
1457+
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",
1458+
},
1459+
{
1460+
sql: "SELECT /*+ TIDB_HJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a",
1461+
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",
1462+
},
1463+
{
1464+
sql: "SELECT /*+ TIDB_INLJ(t3, t4) */ * from t t1, t t2 where t1.a = t2.a",
1465+
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",
1466+
},
1467+
{
1468+
sql: "SELECT /*+ TIDB_SMJ(t1, t2) */ * from t t1, t t2 where t1.a = t2.a",
1469+
warning: "",
1470+
},
1471+
{
1472+
sql: "SELECT /*+ TIDB_SMJ(t3, t4) */ * from t t1, t t2, t t3 where t1.a = t2.a and t2.a = t3.a",
1473+
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",
1474+
},
1475+
}
1476+
for _, test := range tests {
1477+
se.GetSessionVars().StmtCtx.SetWarnings(nil)
1478+
stmt, err := s.ParseOneStmt(test.sql, "", "")
1479+
c.Assert(err, IsNil)
1480+
_, err = planner.Optimize(se, stmt, s.is)
1481+
c.Assert(err, IsNil)
1482+
warnings := se.GetSessionVars().StmtCtx.GetWarnings()
1483+
if test.warning == "" {
1484+
c.Assert(len(warnings), Equals, 0)
1485+
} else {
1486+
c.Assert(len(warnings), Equals, 1)
1487+
c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning)
1488+
c.Assert(warnings[0].Err.Error(), Equals, test.warning)
1489+
}
1490+
}
1491+
}

planner/core/planbuilder.go

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,25 @@ type visitInfo struct {
4646
}
4747

4848
type tableHintInfo struct {
49-
indexNestedLoopJoinTables []model.CIStr
50-
sortMergeJoinTables []model.CIStr
51-
hashJoinTables []model.CIStr
49+
indexNestedLoopJoinTables []hintTableInfo
50+
sortMergeJoinTables []hintTableInfo
51+
hashJoinTables []hintTableInfo
52+
}
53+
54+
type hintTableInfo struct {
55+
name model.CIStr
56+
matched bool
57+
}
58+
59+
func tableNames2HintTableInfo(tableNames []model.CIStr) []hintTableInfo {
60+
if len(tableNames) == 0 {
61+
return nil
62+
}
63+
hintTables := make([]hintTableInfo, 0, len(tableNames))
64+
for _, tableName := range tableNames {
65+
hintTables = append(hintTables, hintTableInfo{name: tableName})
66+
}
67+
return hintTables
5268
}
5369

5470
func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*model.CIStr) bool {
@@ -71,32 +87,47 @@ func (info *tableHintInfo) ifPreferINLJ(tableNames ...*model.CIStr) bool {
7187
// Which it joins on with depend on sequence of traverse
7288
// and without reorder, user might adjust themselves.
7389
// This is similar to MySQL hints.
74-
func (info *tableHintInfo) matchTableName(tables []*model.CIStr, tablesInHints []model.CIStr) bool {
90+
func (info *tableHintInfo) matchTableName(tables []*model.CIStr, hintTables []hintTableInfo) bool {
91+
hintMatched := false
7592
for _, tableName := range tables {
7693
if tableName == nil {
7794
continue
7895
}
79-
for _, curEntry := range tablesInHints {
80-
if curEntry.L == tableName.L {
81-
return true
96+
for i, curEntry := range hintTables {
97+
if curEntry.name.L == tableName.L {
98+
hintTables[i].matched = true
99+
hintMatched = true
100+
break
82101
}
83102
}
84103
}
85-
return false
104+
return hintMatched
86105
}
87106

88-
func (info *tableHintInfo) restore2IndexJoinHint() string {
89-
buffer := bytes.NewBufferString("/*+ TIDB_INLJ(")
90-
for i, tableName := range info.indexNestedLoopJoinTables {
91-
buffer.WriteString(tableName.O)
92-
if i < len(info.indexNestedLoopJoinTables)-1 {
107+
func restore2JoinHint(hintType string, hintTables []hintTableInfo) string {
108+
buffer := bytes.NewBufferString("/*+ ")
109+
buffer.WriteString(strings.ToUpper(hintType))
110+
buffer.WriteString("(")
111+
for i, table := range hintTables {
112+
buffer.WriteString(table.name.L)
113+
if i < len(hintTables)-1 {
93114
buffer.WriteString(", ")
94115
}
95116
}
96117
buffer.WriteString(") */")
97118
return buffer.String()
98119
}
99120

121+
func extractUnmatchedTables(hintTables []hintTableInfo) []string {
122+
var tableNames []string
123+
for _, table := range hintTables {
124+
if !table.matched {
125+
tableNames = append(tableNames, table.name.O)
126+
}
127+
}
128+
return tableNames
129+
}
130+
100131
// clauseCode indicates in which clause the column is currently.
101132
type clauseCode int
102133

0 commit comments

Comments
 (0)