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

This commit is contained in:
Haibin Xie
2019-04-01 11:53:40 +08:00
committed by Zhang Jian
parent 8a7c60d406
commit bab9e90db6
4 changed files with 118 additions and 18 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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