diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index 10d8272de75ea..b81c8847f0bff 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -1976,43 +1976,40 @@ func (p *LogicalJoin) preferAny(joinFlags ...uint) bool { return false } -// satisfyIndexJoinHint returns whether this join plan can satisfy current index join hints. -func (p *LogicalJoin) satisfyIndexJoinHint(join PhysicalPlan) bool { - const left, right = 0, 1 - const indexJoin, indexHashJoin, indexMergeJoin = 0, 1, 2 - var innerSide, innerIdx, joinMethod int +const ( + joinLeft = 0 + joinRight = 1 + indexJoinMethod = 0 + indexHashJoinMethod = 1 + indexMergeJoinMethod = 2 +) + +func (*LogicalJoin) getIndexJoinSideAndMethod(join PhysicalPlan) (innerSide, joinMethod int, ok bool) { + var innerIdx int switch ij := join.(type) { case *PhysicalIndexJoin: innerIdx = ij.getInnerChildIdx() - joinMethod = indexJoin + joinMethod = indexJoinMethod case *PhysicalIndexHashJoin: innerIdx = ij.getInnerChildIdx() - joinMethod = indexHashJoin + joinMethod = indexHashJoinMethod case *PhysicalIndexMergeJoin: innerIdx = ij.getInnerChildIdx() - joinMethod = indexMergeJoin + joinMethod = indexMergeJoinMethod default: - return false + return 0, 0, false } - innerSide = left + ok = true + innerSide = joinLeft if innerIdx == 1 { - innerSide = right - } - - if (p.preferAny(preferLeftAsINLJInner) && innerSide == left && joinMethod == indexJoin) || - (p.preferAny(preferRightAsINLJInner) && innerSide == right && joinMethod == indexJoin) || - (p.preferAny(preferLeftAsINLHJInner) && innerSide == left && joinMethod == indexHashJoin) || - (p.preferAny(preferRightAsINLHJInner) && innerSide == right && joinMethod == indexHashJoin) || - (p.preferAny(preferLeftAsINLMJInner) && innerSide == left && joinMethod == indexMergeJoin) || - (p.preferAny(preferRightAsINLMJInner) && innerSide == right && joinMethod == indexMergeJoin) { - return true + innerSide = joinRight } - return false + return } -// tryToGetIndexJoin will get index join by hints. If we can generate a valid index join by hint, the second return value -// will be true, which means we force to choose this index join. Otherwise we will select a join algorithm with min-cost. +// tryToGetIndexJoin returns all available index join plans, and the second returned value indicates whether this plan is enforced by hints. func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJoins []PhysicalPlan, canForced bool) { +<<<<<<< HEAD forceLeftOuter := p.preferAny(preferRightAsINLJInner, preferRightAsINLHJInner, preferRightAsINLMJInner) // left as outer == right as inner forceRightOuter := p.preferAny(preferLeftAsINLJInner, preferLeftAsINLHJInner, preferLeftAsINLMJInner) // right as outer == left as inner needForced := forceLeftOuter || forceRightOuter @@ -2047,6 +2044,8 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ } }() +======= +>>>>>>> 79f00016016 (planner: refactor `tryToGetIndexJoin` (#45617)) // supportLeftOuter and supportRightOuter indicates whether this type of join // supports the left side or right side to be the outer side. var supportLeftOuter, supportRightOuter bool @@ -2058,9 +2057,9 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ case InnerJoin: supportLeftOuter, supportRightOuter = true, true } - - var allLeftOuterJoins, allRightOuterJoins, forcedLeftOuterJoins, forcedRightOuterJoins []PhysicalPlan + candidates := make([]PhysicalPlan, 0, 2) if supportLeftOuter { +<<<<<<< HEAD allLeftOuterJoins = p.getIndexJoinByOuterIdx(prop, 0) forcedLeftOuterJoins = make([]PhysicalPlan, 0, len(allLeftOuterJoins)) for _, j := range allLeftOuterJoins { @@ -2074,31 +2073,87 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ case len(forcedLeftOuterJoins) != 0 && (!supportRightOuter || (forceLeftOuter && !forceRightOuter)): return forcedLeftOuterJoins, true } +======= + candidates = append(candidates, p.getIndexJoinByOuterIdx(prop, 0)...) +>>>>>>> 79f00016016 (planner: refactor `tryToGetIndexJoin` (#45617)) } - if supportRightOuter { - allRightOuterJoins = p.getIndexJoinByOuterIdx(prop, 1) - forcedRightOuterJoins = make([]PhysicalPlan, 0, len(allRightOuterJoins)) - for _, j := range allRightOuterJoins { - if p.satisfyIndexJoinHint(j) { - forcedRightOuterJoins = append(forcedRightOuterJoins, j) - } + candidates = append(candidates, p.getIndexJoinByOuterIdx(prop, 1)...) + } + + // handle hints and variables about index join. + // the priority is: force hints like TIDB_INLJ > filter hints like NO_INDEX_JOIN > variables. + candidates, canForced = p.handleForceIndexJoinHints(prop, candidates) + if canForced { + return candidates, canForced + } + return filterIndexJoinBySessionVars(p.SCtx(), candidates), false +} + +// handleForceIndexJoinHints handles the force index join hints and returns all plans that can satisfy the hints. +func (p *LogicalJoin) handleForceIndexJoinHints(prop *property.PhysicalProperty, candidates []PhysicalPlan) (indexJoins []PhysicalPlan, canForced bool) { + if !p.preferAny(preferRightAsINLJInner, preferRightAsINLHJInner, preferRightAsINLMJInner, + preferLeftAsINLJInner, preferLeftAsINLHJInner, preferLeftAsINLMJInner) { + return candidates, false // no force index join hints + } + forced := make([]PhysicalPlan, 0, len(candidates)) + for _, candidate := range candidates { + innerSide, joinMethod, ok := p.getIndexJoinSideAndMethod(candidate) + if !ok { + continue } +<<<<<<< HEAD switch { case len(forcedRightOuterJoins) == 0 && !supportLeftOuter: return filterIndexJoinBySessionVars(p.ctx, allRightOuterJoins), false case len(forcedRightOuterJoins) != 0 && (!supportLeftOuter || (forceRightOuter && !forceLeftOuter)): return forcedRightOuterJoins, true +======= + if (p.preferAny(preferLeftAsINLJInner) && innerSide == joinLeft && joinMethod == indexJoinMethod) || + (p.preferAny(preferRightAsINLJInner) && innerSide == joinRight && joinMethod == indexJoinMethod) || + (p.preferAny(preferLeftAsINLHJInner) && innerSide == joinLeft && joinMethod == indexHashJoinMethod) || + (p.preferAny(preferRightAsINLHJInner) && innerSide == joinRight && joinMethod == indexHashJoinMethod) || + (p.preferAny(preferLeftAsINLMJInner) && innerSide == joinLeft && joinMethod == indexMergeJoinMethod) || + (p.preferAny(preferRightAsINLMJInner) && innerSide == joinRight && joinMethod == indexMergeJoinMethod) { + forced = append(forced, candidate) +>>>>>>> 79f00016016 (planner: refactor `tryToGetIndexJoin` (#45617)) } } - canForceLeft := len(forcedLeftOuterJoins) != 0 && forceLeftOuter - canForceRight := len(forcedRightOuterJoins) != 0 && forceRightOuter - canForced = canForceLeft || canForceRight - if canForced { - return append(forcedLeftOuterJoins, forcedRightOuterJoins...), true + if len(forced) > 0 { + return forced, true } +<<<<<<< HEAD return filterIndexJoinBySessionVars(p.ctx, append(allLeftOuterJoins, allRightOuterJoins...)), false +======= + // Cannot find any valid index join plan with these force hints. + // Print warning message if any hints cannot work. + // If the required property is not empty, we will enforce it and try the hint again. + // So we only need to generate warning message when the property is empty. + if prop.IsSortItemEmpty() { + var indexJoinTables, indexHashJoinTables, indexMergeJoinTables []hintTableInfo + if p.hintInfo != nil { + t := p.hintInfo.indexNestedLoopJoinTables + indexJoinTables, indexHashJoinTables, indexMergeJoinTables = t.inljTables, t.inlhjTables, t.inlmjTables + } + var errMsg string + switch { + case p.preferAny(preferLeftAsINLJInner, preferRightAsINLJInner): // prefer index join + errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", restore2JoinHint(HintINLJ, indexJoinTables), restore2JoinHint(TiDBIndexNestedLoopJoin, indexJoinTables)) + case p.preferAny(preferLeftAsINLHJInner, preferRightAsINLHJInner): // prefer index hash join + errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLHJ, indexHashJoinTables)) + case p.preferAny(preferLeftAsINLMJInner, preferRightAsINLMJInner): // prefer index merge join + errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLMJ, indexMergeJoinTables)) + } + // Append inapplicable reason. + if len(p.EqualConditions) == 0 { + errMsg += " without column equal ON condition" + } + // Generate warning message to client. + p.SCtx().GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg)) + } + return candidates, false +>>>>>>> 79f00016016 (planner: refactor `tryToGetIndexJoin` (#45617)) } func checkChildFitBC(p Plan) bool {