Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner/core: raise warning for unmatched join hint #9914

Merged
merged 5 commits into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.indexNestedLoopJoinHint))
}

// Append inapplicable reason.
Expand Down
18 changes: 15 additions & 3 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1853,19 +1853,31 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool {
}
if len(sortMergeTables)+len(INLJTables)+len(hashJoinTables) > 0 {
b.tableHintInfo = append(b.tableHintInfo, tableHintInfo{
sortMergeJoinTables: sortMergeTables,
indexNestedLoopJoinTables: INLJTables,
hashJoinTables: hashJoinTables,
sortMergeJoinHint: joinHintInfo{tables: sortMergeTables},
indexNestedLoopJoinHint: joinHintInfo{tables: INLJTables},
hashJoinHint: joinHintInfo{tables: hashJoinTables},
})
return true
}
return false
}

func (b *PlanBuilder) popTableHints() {
hintInfo := b.tableHintInfo[len(b.tableHintInfo)-1]
b.appendUnmatchedJoinHintWarning(TiDBIndexNestedLoopJoin, hintInfo.indexNestedLoopJoinHint)
b.appendUnmatchedJoinHintWarning(TiDBMergeJoin, hintInfo.sortMergeJoinHint)
b.appendUnmatchedJoinHintWarning(TiDBHashJoin, hintInfo.hashJoinHint)
b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1]
}

func (b *PlanBuilder) appendUnmatchedJoinHintWarning(joinType string, hintInfo joinHintInfo) {
if len(hintInfo.tables) == 0 || hintInfo.matched {
return
}
errMsg := fmt.Sprintf("Optimizer Hint %s is inapplicable because there are no matching table names. Maybe you can use the table alias name", restore2JoinHint(joinType, hintInfo))
b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg))
}

// TableHints returns the *tableHintInfo of PlanBuilder.
func (b *PlanBuilder) TableHints() *tableHintInfo {
if len(b.tableHintInfo) == 0 {
Expand Down
55 changes: 55 additions & 0 deletions planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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) TestUnmatchedJoinHint(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]Optimizer Hint /*+ TIDB_SMJ(t3, t4) */ is inapplicable because there are no matching table names. 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]Optimizer Hint /*+ TIDB_HJ(t3, t4) */ is inapplicable because there are no matching table names. 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]Optimizer Hint /*+ TIDB_INLJ(t3, t4) */ is inapplicable because there are no matching table names. 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: "",
},
}
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)
}
}
}
33 changes: 21 additions & 12 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,26 @@ type visitInfo struct {
}

type tableHintInfo struct {
indexNestedLoopJoinTables []model.CIStr
sortMergeJoinTables []model.CIStr
hashJoinTables []model.CIStr
indexNestedLoopJoinHint joinHintInfo
sortMergeJoinHint joinHintInfo
hashJoinHint joinHintInfo
}

type joinHintInfo struct {
tables []model.CIStr
matched bool
}

func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.sortMergeJoinTables)
return info.matchTableName(tableNames, &info.sortMergeJoinHint)
}

func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.hashJoinTables)
return info.matchTableName(tableNames, &info.hashJoinHint)
}

func (info *tableHintInfo) ifPreferINLJ(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables)
return info.matchTableName(tableNames, &info.indexNestedLoopJoinHint)
}

// matchTableName checks whether the hint hit the need.
Expand All @@ -72,25 +77,29 @@ 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, joinHint *joinHintInfo) bool {
for _, tableName := range tables {
if tableName == nil {
continue
}
for _, curEntry := range tablesInHints {
for _, curEntry := range joinHint.tables {
if curEntry.L == tableName.L {
joinHint.matched = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to mark hint as unmatched if any specified hint table cannot find a match? /cc @morgo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value is false, is unmatched?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean if the hint contains t1 and t2, while the query only involves t2, should we raise warning?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, maybe it's more reasonable to raise a warning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we define the matched as a string slice?
Thus we can print the accurate wrong table name in the warning message

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning sounds good to me. Note that in some cases, MySQL hints do not even raise warnings because they are considered hints (entirely optional to enforce vs. directives). I think we should just arrive at warnings and never errors.

return true
}
}
}
return false
}

func (info *tableHintInfo) restore2IndexJoinHint() string {
buffer := bytes.NewBufferString("/*+ TIDB_INLJ(")
for i, tableName := range info.indexNestedLoopJoinTables {
func restore2JoinHint(joinType string, joinHint joinHintInfo) string {
alivxxx marked this conversation as resolved.
Show resolved Hide resolved
buffer := bytes.NewBufferString("/*+ ")
buffer.WriteString(strings.ToUpper(joinType))
buffer.WriteString("(")
tables := joinHint.tables
for i, tableName := range tables {
buffer.WriteString(tableName.O)
if i < len(info.indexNestedLoopJoinTables)-1 {
if i < len(tables)-1 {
buffer.WriteString(", ")
}
}
Expand Down