Skip to content

Commit 329c484

Browse files
author
James Cor
committed
add optimization and cleanup
1 parent 34f5169 commit 329c484

File tree

1 file changed

+77
-29
lines changed

1 file changed

+77
-29
lines changed

sql/analyzer/replace_sort.go

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ import (
1414

1515
// replaceIdxSort applies an IndexAccess when there is an `OrderBy` over a prefix of any columns with Indexes
1616
func replaceIdxSort(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
17+
// TODO: check if it's possible to rewrite using transform.NodeWithCtx
1718
return replaceIdxSortHelper(ctx, scope, n, nil)
1819
}
1920

2021
func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, sortNode *plan.Sort) (sql.Node, transform.TreeIdentity, error) {
2122
switch n := node.(type) {
2223
case *plan.Sort:
23-
sortNode = n // lowest parent sort node
24+
// TODO: are there problems when there are multiple ORDER BYs?
25+
if isValidSortFieldOrder(n.SortFields) {
26+
sortNode = n // lowest parent sort node
27+
}
2428
case *plan.IndexedTableAccess:
25-
if sortNode == nil || !isValidSortFieldOrder(sortNode.SortFields) {
29+
if sortNode == nil {
2630
return n, transform.SameTree, nil
2731
}
2832
if !n.IsStatic() {
@@ -32,34 +36,31 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
3236
if err != nil {
3337
return nil, transform.SameTree, err
3438
}
35-
3639
tableAliases, err := getTableAliases(sortNode, scope)
3740
if err != nil {
3841
return n, transform.SameTree, nil
3942
}
40-
4143
sfExprs := normalizeExpressions(tableAliases, sortNode.SortFields.ToExpressions()...)
4244
sfAliases := aliasedExpressionsInNode(sortNode)
4345
if !isSortFieldsValidPrefix(sfExprs, sfAliases, lookup.Index.Expressions()) {
4446
return n, transform.SameTree, nil
4547
}
46-
4748
mysqlRanges, ok := lookup.Ranges.(sql.MySQLRangeCollection)
4849
if !ok {
4950
return n, transform.SameTree, nil
5051
}
5152
// if the resulting ranges are overlapping, we cannot drop the sort node
52-
// it is possible we end up with blocks rows that intersect
53+
// it is possible we end up with blocks of rows that intersect
5354
if hasOverlapping(sfExprs, mysqlRanges) {
5455
return n, transform.SameTree, nil
5556
}
56-
57-
// TODO: verify that we don't need to look at other SortFields?
5857
// if the lookup does not need any reversing, do nothing
5958
if sortNode.SortFields[0].Order != sql.Descending {
6059
return n, transform.NewTree, nil
6160
}
6261

62+
// TODO: need to check for indexOrder = None?
63+
6364
// if the index is not reversible, do nothing
6465
if oi, ok := lookup.Index.(sql.OrderedIndex); ok && !oi.Reversible() {
6566
return n, transform.SameTree, nil
@@ -79,10 +80,9 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
7980
}
8081
return nn, transform.NewTree, err
8182
case *plan.ResolvedTable:
82-
if sortNode == nil || !isValidSortFieldOrder(sortNode.SortFields) {
83+
if sortNode == nil {
8384
return n, transform.SameTree, nil
8485
}
85-
8686
table := n.UnderlyingTable()
8787
idxTbl, ok := table.(sql.IndexAddressableTable)
8888
if !ok {
@@ -91,7 +91,6 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
9191
if indexSearchable, ok := table.(sql.IndexSearchableTable); ok && indexSearchable.SkipIndexCosting() {
9292
return n, transform.SameTree, nil
9393
}
94-
9594
tableAliases, err := getTableAliases(sortNode, scope)
9695
if err != nil {
9796
return n, transform.SameTree, nil
@@ -120,14 +119,12 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
120119
if idx == nil {
121120
return n, transform.SameTree, nil
122121
}
123-
124122
// Create lookup based off of index
125123
indexBuilder := sql.NewMySQLIndexBuilder(idx)
126124
lookup, err := indexBuilder.Build(ctx)
127125
if err != nil {
128126
return nil, transform.SameTree, err
129127
}
130-
// TODO: what about the other sort fields?
131128
if sortNode.SortFields[0].Order == sql.Descending {
132129
lookup = sql.NewIndexLookup(
133130
lookup.Index,
@@ -139,7 +136,7 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
139136
)
140137
}
141138
// Some Primary Keys (like doltHistoryTable) are not in order
142-
if oi, ok := idx.(sql.OrderedIndex); ok && ((lookup.IsReverse && !oi.Reversible()) || oi.Order() == sql.IndexOrderNone) {
139+
if oi, isOrderedIdx := idx.(sql.OrderedIndex); isOrderedIdx && ((lookup.IsReverse && !oi.Reversible()) || oi.Order() == sql.IndexOrderNone) {
143140
return n, transform.SameTree, nil
144141
}
145142
if !idx.CanSupport(ctx, lookup.Ranges.(sql.MySQLRangeCollection).ToRanges()...) {
@@ -149,7 +146,6 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
149146
if err != nil {
150147
return nil, transform.SameTree, err
151148
}
152-
153149
return nn, transform.NewTree, err
154150
}
155151

@@ -159,28 +155,54 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
159155
var err error
160156
same := transform.SameTree
161157
switch c := child.(type) {
162-
case *plan.Project, *plan.TableAlias, *plan.ResolvedTable, *plan.Filter, *plan.Limit, *plan.Offset, *plan.Sort, *plan.IndexedTableAccess, *plan.Distinct:
158+
case *plan.Sort, *plan.IndexedTableAccess, *plan.ResolvedTable:
159+
newChildren[i], same, err = replaceIdxSortHelper(ctx, scope, child, sortNode)
160+
case *plan.Project, *plan.Filter, *plan.Limit, *plan.Offset, *plan.Distinct, *plan.TableAlias:
163161
newChildren[i], same, err = replaceIdxSortHelper(ctx, scope, child, sortNode)
164162
case *plan.JoinNode:
163+
// TODO: is this applicable to other types of joins?
164+
// as long as left child is already sorted and SortFields are a prefix, then it's ok?
165165
if !c.JoinType().IsMerge() {
166166
continue
167167
}
168+
// It's (probably) not possible to have Sort as child of Join without Subquery/SubqueryAlias,
169+
// and in the case where there is a Subq/SQA it's taken care of through finalizeSubqueries
168170
if sortNode == nil {
169171
continue
170172
}
171-
// TODO: skipping desc sorting for now
172-
// TODO: check that the sort fields are a prefix of the indexes
173-
hasDesc := false
174-
for _, sf := range sortNode.SortFields {
175-
if sf.Order == sql.Descending {
176-
hasDesc = true
177-
break
178-
}
173+
newLeft, sameLeft, errLeft := replaceIdxSortHelper(ctx, scope, c.Left(), sortNode)
174+
if errLeft != nil {
175+
return nil, transform.SameTree, errLeft
179176
}
180-
if hasDesc {
177+
newRight, sameRight, errRight := replaceIdxSortHelper(ctx, scope, c.Right(), sortNode)
178+
if errRight != nil {
179+
return nil, transform.SameTree, errRight
180+
}
181+
if sameLeft && sameRight {
182+
newChildren[i] = c
181183
continue
182184
}
183-
newChildren[i], same, err = replaceIdxSortHelper(ctx, scope, child, sortNode)
185+
// No need to check all SortField orders because of isValidSortFieldOrder
186+
if sortNode.SortFields[0].Order == sql.Descending {
187+
// If descending, then both Indexes must be reversed
188+
var reversible bool
189+
if sameLeft {
190+
newLeft, reversible, err = buildReverseIndexedTable(ctx, newLeft)
191+
} else {
192+
newRight, reversible, err = buildReverseIndexedTable(ctx, newLeft)
193+
}
194+
if err != nil {
195+
return nil, transform.SameTree, err
196+
}
197+
if !reversible {
198+
return nil, transform.SameTree, nil
199+
}
200+
}
201+
newChildren[i], err = c.WithChildren(newLeft, newRight)
202+
if err != nil {
203+
return nil, transform.SameTree, err
204+
}
205+
allSame = false
184206
default:
185207
newChildren[i] = c
186208
}
@@ -189,23 +211,49 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so
189211
}
190212
allSame = allSame && same
191213
}
192-
193214
if allSame {
194215
return node, transform.SameTree, nil
195216
}
196-
197217
// if sort node was replaced with indexed access, drop sort node
198218
if node == sortNode {
199219
return newChildren[0], transform.NewTree, nil
200220
}
201-
202221
newNode, err := node.WithChildren(newChildren...)
203222
if err != nil {
204223
return nil, transform.SameTree, err
205224
}
206225
return newNode, transform.NewTree, nil
207226
}
208227

228+
// buildReverseIndexedTable will attempt to take the lookup from an IndexedTableAccess, and return a new
229+
// IndexedTableAccess with the lookup reversed.
230+
func buildReverseIndexedTable(ctx *sql.Context, node sql.Node) (*plan.IndexedTableAccess, bool, error) {
231+
idxTbl, isIdxTbl := node.(*plan.IndexedTableAccess)
232+
if !isIdxTbl {
233+
return nil, false, nil
234+
}
235+
lookup, err := idxTbl.GetLookup(ctx, nil)
236+
if err != nil {
237+
return nil, false, err
238+
}
239+
if oi, isOrderedIdx := lookup.Index.(sql.OrderedIndex); isOrderedIdx && (!oi.Reversible() || oi.Order() == sql.IndexOrderNone) {
240+
return nil, false, nil
241+
}
242+
lookup = sql.NewIndexLookup(
243+
lookup.Index,
244+
lookup.Ranges.(sql.MySQLRangeCollection),
245+
lookup.IsPointLookup,
246+
lookup.IsEmptyRange,
247+
lookup.IsSpatialLookup,
248+
true,
249+
)
250+
newNode, err := plan.NewStaticIndexedAccessForTableNode(ctx, idxTbl, lookup)
251+
if err != nil {
252+
return nil, false, err
253+
}
254+
return newNode, true, nil
255+
}
256+
209257
// replaceAgg converts aggregate functions to order by + limit 1 when possible
210258
func replaceAgg(ctx *sql.Context, a *Analyzer, node sql.Node, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
211259
if !FlagIsSet(qFlags, sql.QFlagAggregation) {

0 commit comments

Comments
 (0)