@@ -14,15 +14,19 @@ import (
1414
1515// replaceIdxSort applies an IndexAccess when there is an `OrderBy` over a prefix of any columns with Indexes
1616func 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
2021func 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
210258func 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