planner/core: raise warning for unmatched join hint (#9914)
This commit is contained in:
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user