Skip to content

Commit c9f859b

Browse files
author
James Cor
committed
reapply reverse optimization
1 parent 4d54c48 commit c9f859b

File tree

4 files changed

+77
-52
lines changed

4 files changed

+77
-52
lines changed

enginetest/queries/script_queries.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8507,7 +8507,8 @@ where
85078507
},
85088508
},
85098509
{
8510-
Name: "merge join optimization",
8510+
// https://github.com/dolthub/dolt/issues/8728
8511+
Name: "test merge join optimization (removing sort node over indexed tables) does not break ordering",
85118512
Dialect: "mysql",
85128513
SetUpScript: []string{
85138514
"create table t1 (i int primary key);",
@@ -8536,21 +8537,26 @@ where
85368537
},
85378538
},
85388539
{
8539-
// The Sort node can be optimized out of this query, but currently is not
85408540
Query: "select /*+ MERGE_JOIN(t1, t2) */ * from t1 join t2 on t1.i = t2.j order by t1.i desc;",
85418541
Expected: []sql.Row{
85428542
{3, 3},
85438543
{2, 2},
85448544
},
85458545
},
85468546
{
8547-
// The Sort node can be optimized out of this query, but currently is not
85488547
Query: "select /*+ MERGE_JOIN(t1, t2) */ * from t1 join t2 on t1.i = t2.j order by t2.j desc;",
85498548
Expected: []sql.Row{
85508549
{3, 3},
85518550
{2, 2},
85528551
},
85538552
},
8553+
{
8554+
// InSubquery expressions can be optimized into MERGE_JOINs, so this optimization should apply over this as well
8555+
Query: "select /*+ MERGE_JOIN(t1, t2) */ * from t1 where ((i in (select j from t2 where j > 2))) order by i desc;",
8556+
Expected: []sql.Row{
8557+
{3},
8558+
},
8559+
},
85548560

85558561
{
85568562
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i;",
@@ -8560,7 +8566,6 @@ where
85608566
},
85618567
},
85628568
{
8563-
// The Sort node can be optimized out of this query, but currently is not
85648569
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i desc;",
85658570
Expected: []sql.Row{
85668571
{3, 3, 3, 3},
@@ -8575,7 +8580,6 @@ where
85758580
},
85768581
},
85778582
{
8578-
// The Sort node can be optimized out of this query, but currently is not
85798583
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t4.x desc;",
85808584
Expected: []sql.Row{
85818585
{3, 3, 3, 3},
@@ -8590,7 +8594,6 @@ where
85908594
},
85918595
},
85928596
{
8593-
// The Sort node can be optimized out of this query, but currently is not
85948597
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i desc, t3.j desc;",
85958598
Expected: []sql.Row{
85968599
{3, 3, 3, 3},
@@ -8605,7 +8608,6 @@ where
86058608
},
86068609
},
86078610
{
8608-
// The Sort node can be optimized out of this query, but currently is not
86098611
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t4.x desc, t4.y desc;",
86108612
Expected: []sql.Row{
86118613
{3, 3, 3, 3},
@@ -8620,6 +8622,14 @@ where
86208622
{3, 3, 3, 3},
86218623
},
86228624
},
8625+
{
8626+
// The Sort node can be optimized out of this query, but currently is not
8627+
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i, t4.x desc;",
8628+
Expected: []sql.Row{
8629+
{2, 2, 2, 2},
8630+
{3, 3, 3, 3},
8631+
},
8632+
},
86238633
{
86248634
// The Sort node can be optimized out of this query, but currently is not
86258635
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i, t3.j, t4.x;",
@@ -8661,14 +8671,6 @@ where
86618671
{3, 3, 3, 3},
86628672
},
86638673
},
8664-
{
8665-
// Trickier, but Sort node can be optimized out of this query
8666-
Query: "select /*+ MERGE_JOIN(t3, t4) */ * from t3 join t4 on t3.i = t4.x order by t3.i, t4.x desc;",
8667-
Expected: []sql.Row{
8668-
{2, 2, 2, 2},
8669-
{3, 3, 3, 3},
8670-
},
8671-
},
86728674
},
86738675
},
86748676

sql/analyzer/replace_sort.go

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ func replaceIdxSort(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope
2020
func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, sortNode *plan.Sort) (sql.Node, transform.TreeIdentity, error) {
2121
switch n := node.(type) {
2222
case *plan.Sort:
23-
// TODO: are there problems when there are multiple ORDER BYs?
2423
if isValidSortFieldOrder(n.SortFields) {
2524
sortNode = n // lowest parent sort node
2625
}
@@ -59,12 +58,10 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
5958
}
6059

6160
// TODO: need to check for indexOrder = None?
62-
6361
// if the index is not reversible, do nothing
6462
if oi, ok := lookup.Index.(sql.OrderedIndex); ok && !oi.Reversible() {
6563
return n, transform.SameTree, nil
6664
}
67-
6865
lookup = sql.NewIndexLookup(
6966
lookup.Index,
7067
mysqlRanges,
@@ -73,11 +70,11 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
7370
lookup.IsSpatialLookup,
7471
true,
7572
)
76-
nn, err := plan.NewStaticIndexedAccessForTableNode(ctx, n.TableNode, lookup)
73+
newIdxTbl, err := plan.NewStaticIndexedAccessForTableNode(ctx, n.TableNode, lookup)
7774
if err != nil {
7875
return nil, transform.SameTree, err
7976
}
80-
return nn, transform.NewTree, err
77+
return newIdxTbl, transform.NewTree, err
8178
case *plan.ResolvedTable:
8279
if sortNode == nil {
8380
return n, transform.SameTree, nil
@@ -154,9 +151,8 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
154151
var err error
155152
same := transform.SameTree
156153
switch c := child.(type) {
157-
case *plan.Sort, *plan.IndexedTableAccess, *plan.ResolvedTable:
158-
newChildren[i], same, err = replaceIdxSortHelper(ctx, scope, child, sortNode)
159-
case *plan.Project, *plan.Filter, *plan.Limit, *plan.Offset, *plan.Distinct, *plan.TableAlias:
154+
case *plan.Sort, *plan.IndexedTableAccess, *plan.ResolvedTable,
155+
*plan.Project, *plan.Filter, *plan.Limit, *plan.Offset, *plan.Distinct, *plan.TableAlias:
160156
newChildren[i], same, err = replaceIdxSortHelper(ctx, scope, child, sortNode)
161157
case *plan.JoinNode:
162158
// TODO: is this applicable to other types of joins?
@@ -169,10 +165,6 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
169165
if sortNode == nil {
170166
continue
171167
}
172-
// TODO: allow for reversed indexes; for some reason this breaks
173-
if sortNode.SortFields[0].Order == sql.Descending {
174-
continue
175-
}
176168
newLeft, sameLeft, errLeft := replaceIdxSortHelper(ctx, scope, c.Left(), sortNode)
177169
if errLeft != nil {
178170
return nil, transform.SameTree, errLeft
@@ -184,6 +176,25 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
184176
if sameLeft && sameRight {
185177
continue
186178
}
179+
// No need to check all SortField orders because of isValidSortFieldOrder
180+
isReversed := sortNode.SortFields[0].Order == sql.Descending
181+
// either left or right has been reversed
182+
if (sameLeft != sameRight) && isReversed {
183+
// If descending, then both Indexes must be reversed
184+
if sameLeft {
185+
newLeft, same, err = buildReverseIndexedTable(ctx, newLeft)
186+
} else if sameRight {
187+
newRight, same, err = buildReverseIndexedTable(ctx, newRight)
188+
}
189+
if err != nil {
190+
return nil, transform.SameTree, err
191+
}
192+
// If we could not replace the IndexedTableAccess with a reversed one, result is same, so abandon
193+
if same {
194+
continue
195+
}
196+
c.IsReversed = true
197+
}
187198
newChildren[i], err = c.WithChildren(newLeft, newRight)
188199
if err != nil {
189200
return nil, transform.SameTree, err
@@ -213,31 +224,34 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
213224

214225
// buildReverseIndexedTable will attempt to take the lookup from an IndexedTableAccess, and return a new
215226
// IndexedTableAccess with the lookup reversed.
216-
func buildReverseIndexedTable(ctx *sql.Context, node sql.Node) (*plan.IndexedTableAccess, bool, error) {
217-
idxTbl, isIdxTbl := node.(*plan.IndexedTableAccess)
218-
if !isIdxTbl {
219-
return nil, false, nil
220-
}
221-
lookup, err := idxTbl.GetLookup(ctx, nil)
222-
if err != nil {
223-
return nil, false, err
224-
}
225-
if oi, isOrderedIdx := lookup.Index.(sql.OrderedIndex); isOrderedIdx && (!oi.Reversible() || oi.Order() == sql.IndexOrderNone) {
226-
return nil, false, nil
227-
}
228-
lookup = sql.NewIndexLookup(
229-
lookup.Index,
230-
lookup.Ranges.(sql.MySQLRangeCollection),
231-
lookup.IsPointLookup,
232-
lookup.IsEmptyRange,
233-
lookup.IsSpatialLookup,
234-
true,
235-
)
236-
newNode, err := plan.NewStaticIndexedAccessForTableNode(ctx, idxTbl.TableNode, lookup)
237-
if err != nil {
238-
return nil, false, err
239-
}
240-
return newNode, true, nil
227+
func buildReverseIndexedTable(ctx *sql.Context, node sql.Node) (sql.Node, transform.TreeIdentity, error) {
228+
return transform.Node(node, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) {
229+
switch idxTbl := n.(type) {
230+
case *plan.IndexedTableAccess:
231+
lookup, err := idxTbl.GetLookup(ctx, nil)
232+
if err != nil {
233+
return nil, transform.SameTree, err
234+
}
235+
if oi, isOrderedIdx := lookup.Index.(sql.OrderedIndex); isOrderedIdx && (!oi.Reversible() || oi.Order() == sql.IndexOrderNone) {
236+
return n, transform.SameTree, nil
237+
}
238+
lookup = sql.NewIndexLookup(
239+
lookup.Index,
240+
lookup.Ranges.(sql.MySQLRangeCollection),
241+
lookup.IsPointLookup,
242+
lookup.IsEmptyRange,
243+
lookup.IsSpatialLookup,
244+
true,
245+
)
246+
newIdxTbl, err := plan.NewStaticIndexedAccessForTableNode(ctx, idxTbl.TableNode, lookup)
247+
if err != nil {
248+
return nil, transform.SameTree, err
249+
}
250+
return newIdxTbl, transform.NewTree, nil
251+
default:
252+
return n, transform.SameTree, nil
253+
}
254+
})
241255
}
242256

243257
// replaceAgg converts aggregate functions to order by + limit 1 when possible

sql/plan/join.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ type JoinNode struct {
291291
CommentStr string
292292
ScopeLen int
293293
UsingCols []string
294+
295+
IsReversed bool
294296
}
295297

296298
var _ sql.Node = (*JoinNode)(nil)

sql/rowexec/merge_join.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func newMergeJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode,
8282
parentLen: len(row) - j.ScopeLen,
8383
leftRowLen: len(j.Left().Schema()),
8484
rightRowLen: len(j.Right().Schema()),
85+
86+
isReversed: j.IsReversed,
8587
}
8688
return iter, nil
8789
}
@@ -125,6 +127,8 @@ type mergeJoinIter struct {
125127
leftRowLen int
126128
rightRowLen int
127129
parentLen int
130+
131+
isReversed bool
128132
}
129133

130134
var _ sql.RowIter = (*mergeJoinIter)(nil)
@@ -213,6 +217,9 @@ func (i *mergeJoinIter) Next(ctx *sql.Context) (sql.Row, error) {
213217
} else if err != nil {
214218
return nil, err
215219
}
220+
if i.isReversed {
221+
res = -res
222+
}
216223
switch {
217224
case res < 0:
218225
if i.typ.IsLeftOuter() {

0 commit comments

Comments
 (0)