diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index e4e8d2e605..09c4b5e5dc 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -8755,6 +8755,17 @@ from typestable`, {"12.0000"}, }, }, + { + Query: "select * from one_pk_two_idx where v1 < 4 and v2 < 2 or v2 > 3 order by v1", + Expected: []sql.Row{ + {0, 0, 0}, + {1, 1, 1}, + {4, 4, 4}, + {5, 5, 5}, + {6, 6, 6}, + {7, 7, 7}, + }, + }, } var KeylessQueries = []QueryTest{ diff --git a/enginetest/queries/query_plans.go b/enginetest/queries/query_plans.go index e85f88017a..086361bc99 100644 --- a/enginetest/queries/query_plans.go +++ b/enginetest/queries/query_plans.go @@ -12189,4 +12189,17 @@ order by x, y; " └─ tableId: 0\n" + "", }, + { + Query: "select * from one_pk_two_idx where v1 < 4 and v2 < 2 or v2 > 3 order by v1", + ExpectedPlan: "Sort(one_pk_two_idx.v1:1 ASC nullsFirst)\n" + + " └─ IndexedTableAccess(one_pk_two_idx)\n" + + " ├─ index: [one_pk_two_idx.v1,one_pk_two_idx.v2]\n" + + " ├─ static: [{[NULL, ∞), (3, ∞)}, {(NULL, 4), (NULL, 2)}]\n" + + " ├─ colSet: (1-3)\n" + + " ├─ tableId: 1\n" + + " └─ Table\n" + + " ├─ name: one_pk_two_idx\n" + + " └─ columns: [pk v1 v2]\n" + + "", + }, } diff --git a/sql/analyzer/replace_sort.go b/sql/analyzer/replace_sort.go index 8aa5c01d28..9226432817 100644 --- a/sql/analyzer/replace_sort.go +++ b/sql/analyzer/replace_sort.go @@ -44,6 +44,12 @@ func replaceIdxSortHelper(ctx *sql.Context, scope *plan.Scope, node sql.Node, so return n, transform.SameTree, nil } + // if the resulting ranges are overlapping, we cannot drop the sort node + // it is possible we end up with blocks rows that intersect + if hasOverlapping(sfExprs, lookup.Ranges) { + return n, transform.SameTree, nil + } + // if the lookup does not need any reversing, do nothing if sortNode.SortFields[0].Order != sql.Descending { return n, transform.NewTree, nil @@ -291,6 +297,21 @@ func isValidSortFieldOrder(sfs sql.SortFields) bool { return true } +// hasOverlapping checks if the ranges in a RangeCollection that are part of the sortfield exprs are overlapping +// This function assumes that the sort field exprs are a valid prefix of the index columns +func hasOverlapping(sfExprs []sql.Expression, ranges sql.RangeCollection) bool { + for si := range sfExprs { + for ri := 0; ri < len(ranges)-1; ri++ { + for rj := ri + 1; rj < len(ranges); rj++ { + if _, overlaps, _ := ranges[ri][si].Overlaps(ranges[rj][si]); overlaps { + return true + } + } + } + } + return false +} + // getPKIndex returns the primary key index of an IndexAddressableTable func getPKIndex(ctx *sql.Context, idxTbl sql.IndexAddressableTable) (sql.Index, error) { idxs, err := idxTbl.GetIndexes(ctx)