Skip to content

Commit 8f7bd00

Browse files
authored
Merge pull request #3057 from dolthub/fulghum/join-planning
Changing how `NO_MERGE_JOIN` hint is applied to fix a panic
2 parents a836475 + 6195dab commit 8f7bd00

File tree

4 files changed

+39
-50
lines changed

4 files changed

+39
-50
lines changed

sql/analyzer/indexed_joins.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
158158

159159
qFlags.Set(sql.QFlagInnerJoin)
160160

161+
hints := m.SessionHints()
162+
hints = append(hints, memo.ExtractJoinHint(n)...)
163+
161164
err = addIndexScans(ctx, m)
162165
if err != nil {
163166
return nil, err
@@ -180,9 +183,11 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
180183
return nil, err
181184
}
182185

183-
err = addMergeJoins(ctx, m)
184-
if err != nil {
185-
return nil, err
186+
if !mergeJoinsDisabled(hints) {
187+
err = addMergeJoins(ctx, m)
188+
if err != nil {
189+
return nil, err
190+
}
186191
}
187192

188193
memo.CardMemoGroups(ctx, m.Root())
@@ -200,11 +205,9 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
200205
return nil, err
201206
}
202207

203-
m.SetDefaultHints()
204-
hints := memo.ExtractJoinHint(n)
208+
// Once we've enumerated all expression groups, we can apply hints. This must be done after expression
209+
// groups have been identified, so that the applied hints use the correct metadata.
205210
for _, h := range hints {
206-
// this should probably happen earlier, but the root is not
207-
// populated before reordering
208211
m.ApplyHint(h)
209212
}
210213

@@ -223,6 +226,16 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
223226
return m.BestRootPlan(ctx)
224227
}
225228

229+
// mergeJoinsDisabled returns true if merge joins have been disabled in the specified |hints|.
230+
func mergeJoinsDisabled(hints []memo.Hint) bool {
231+
for _, hint := range hints {
232+
if hint.Typ == memo.HintTypeNoMergeJoin {
233+
return true
234+
}
235+
}
236+
return false
237+
}
238+
226239
// addLookupJoins prefixes memo join group expressions with indexed join
227240
// alternatives to join plans added by joinOrderBuilder. We can assume that a
228241
// join with a non-nil join filter is not degenerate, and we can apply indexed

sql/memo/join_order_builder.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ var ErrUnsupportedReorderNode = errors.New("unsupported join reorder node")
154154

155155
// useFastReorder determines whether to skip the current brute force join planning and use an alternate
156156
// planning algorithm that analyzes the join tree to find a sequence that can be implemented purely as lookup joins.
157-
// Currently we only use it for large joins (20+ tables) with no join hints.
157+
// Currently, we only use it for large joins (15+ tables) with no join hints.
158158
func (j *joinOrderBuilder) useFastReorder() bool {
159159
if j.forceFastDFSLookupForTest {
160160
return true
@@ -180,7 +180,7 @@ func (j *joinOrderBuilder) ReorderJoin(n sql.Node) {
180180
// from ensureClosure in buildSingleLookupPlan, but the equivalence sets could create multiple possible join orders
181181
// for the single-lookup plan, which would complicate things.
182182
j.ensureClosure(j.m.root)
183-
j.dbSube()
183+
j.dpEnumerateSubsets()
184184
return
185185
}
186186

@@ -627,10 +627,10 @@ func (j *joinOrderBuilder) checkSize() {
627627
}
628628
}
629629

630-
// dpSube iterates all disjoint combinations of table sets,
630+
// dpEnumerateSubsets iterates all disjoint combinations of table sets,
631631
// adding plans to the tree when we find two sets that can
632632
// be joined
633-
func (j *joinOrderBuilder) dbSube() {
633+
func (j *joinOrderBuilder) dpEnumerateSubsets() {
634634
all := j.allVertices()
635635
for subset := vertexSet(1); subset <= all; subset++ {
636636
if subset.isSingleton() {

sql/memo/memo.go

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ func (m *Memo) StatsProvider() sql.StatsProvider {
8282
return m.statsProv
8383
}
8484

85-
func (m *Memo) SetDefaultHints() {
85+
// SessionHints returns any hints that have been enabled in the session for join planning,
86+
// such as the @@disable_merge_join SQL system variable.
87+
func (m *Memo) SessionHints() (hints []Hint) {
8688
if val, _ := m.Ctx.GetSessionVariable(m.Ctx, sql.DisableMergeJoin); val.(int8) != 0 {
87-
m.ApplyHint(Hint{Typ: HintTypeNoMergeJoin})
89+
hints = append(hints, Hint{Typ: HintTypeNoMergeJoin})
8890
}
91+
return hints
8992
}
9093

9194
// newExprGroup creates a new logical expression group to encapsulate the
@@ -465,11 +468,6 @@ func (m *Memo) optimizeMemoGroup(grp *ExprGroup) error {
465468
// rather than a local property.
466469
func (m *Memo) updateBest(grp *ExprGroup, n RelExpr, cost float64) {
467470
if !m.hints.isEmpty() {
468-
for _, block := range m.hints.block {
469-
if !block.isOk(n) {
470-
return
471-
}
472-
}
473471
if m.hints.satisfiedBy(n) {
474472
if !grp.HintOk {
475473
grp.Best = n
@@ -522,31 +520,20 @@ func getProjectColset(p *Project) sql.ColSet {
522520
return colset
523521
}
524522

523+
// ApplyHint applies |hint| to this memo, converting the parsed hint into an internal representation and updating
524+
// the internal data to match the memo metadata. Note that this function MUST be called only after memo groups have
525+
// been fully built out, otherwise the group information set in the internal join hint structures will be incomplete.
525526
func (m *Memo) ApplyHint(hint Hint) {
526527
switch hint.Typ {
527528
case HintTypeJoinOrder:
528529
m.SetJoinOrder(hint.Args)
529530
case HintTypeJoinFixedOrder:
530531
case HintTypeNoMergeJoin:
531-
m.SetBlockOp(func(n RelExpr) bool {
532-
switch n := n.(type) {
533-
case JoinRel:
534-
jp := n.JoinPrivate()
535-
if !jp.Left.Best.Group().HintOk || !jp.Right.Best.Group().HintOk {
536-
// equiv closures can generate child plans that bypass hints
537-
return false
538-
}
539-
if jp.Op.IsMerge() {
540-
return false
541-
}
542-
}
543-
return true
544-
})
532+
m.hints.disableMergeJoin = true
545533
case HintTypeInnerJoin, HintTypeMergeJoin, HintTypeLookupJoin, HintTypeHashJoin, HintTypeSemiJoin, HintTypeAntiJoin, HintTypeLeftOuterLookupJoin:
546534
m.SetJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
547535
case HintTypeLeftDeep:
548536
m.hints.leftDeep = true
549-
default:
550537
}
551538
}
552539

@@ -568,10 +555,6 @@ func (m *Memo) SetJoinOrder(tables []string) {
568555
}
569556
}
570557

571-
func (m *Memo) SetBlockOp(cb func(n RelExpr) bool) {
572-
m.hints.block = append(m.hints.block, joinBlockHint{cb: cb})
573-
}
574-
575558
func (m *Memo) SetJoinOp(op HintType, left, right string) {
576559
var lTab, rTab sql.TableId
577560
for _, n := range m.root.RelProps.TableIdNodes() {

sql/memo/select_hints.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,25 +372,18 @@ func (o joinOpHint) typeMatches(n RelExpr) bool {
372372
return true
373373
}
374374

375-
type joinBlockHint struct {
376-
cb func(n RelExpr) bool
377-
}
378-
379-
func (o joinBlockHint) isOk(n RelExpr) bool {
380-
return o.cb(n)
381-
}
382-
383375
// joinHints wraps a collection of join hints. The memo
384376
// interfaces with this object during costing.
385377
type joinHints struct {
386-
ops []joinOpHint
387-
order *joinOrderHint
388-
block []joinBlockHint
389-
leftDeep bool
378+
ops []joinOpHint
379+
order *joinOrderHint
380+
leftDeep bool
381+
disableMergeJoin bool
390382
}
391383

384+
// isEmpty returns true if no hints that affect join planning have been set.
392385
func (h joinHints) isEmpty() bool {
393-
return len(h.ops) == 0 && h.order == nil && !h.leftDeep && len(h.block) == 0
386+
return len(h.ops) == 0 && h.order == nil && !h.leftDeep && !h.disableMergeJoin
394387
}
395388

396389
// satisfiedBy returns whether a RelExpr satisfies every join hint. This

0 commit comments

Comments
 (0)