Skip to content

Commit 661f5e2

Browse files
committed
fix sorting on indexed field
1 parent 5fdc4e9 commit 661f5e2

File tree

13 files changed

+559
-117
lines changed

13 files changed

+559
-117
lines changed

packages/nitrite/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Updated some dependencies.
44
- Issue fix for restricting multiple indexes on same field(s) in an ObjectRepository.
5+
- Issue fix for sorting on indexed field.
56

67
## 1.0.2
78

packages/nitrite/lib/src/filters/filter.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,13 @@ abstract class ComparableFilter extends FieldBasedFilter {
305305
Stream<dynamic> applyOnIndex(IndexMap indexMap);
306306
}
307307

308+
abstract class SortingAwareFilter extends ComparableFilter {
309+
SortingAwareFilter(super.field, super.value);
310+
311+
/// Indicates if the filter should scan the index in reverse order.
312+
bool isReverseScan = false;
313+
}
314+
308315
/// An abstract class representing a filter for string values.
309316
abstract class StringFilter extends ComparableFilter {
310317
StringFilter(super.field, super.value);

packages/nitrite/lib/src/filters/filter_impl.dart

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ class _Bound<T> {
300300
{this.upperInclusive = true, this.lowerInclusive = true});
301301
}
302302

303-
class _GreaterEqualFilter extends ComparableFilter {
303+
class _GreaterEqualFilter extends SortingAwareFilter {
304304
_GreaterEqualFilter(super.field, super.value);
305305

306306
@override
@@ -320,21 +320,33 @@ class _GreaterEqualFilter extends ComparableFilter {
320320

321321
@override
322322
Stream<dynamic> applyOnIndex(IndexMap indexMap) async* {
323-
var ceilingKey = await indexMap.ceilingKey(comparable);
324-
while (ceilingKey != null) {
325-
// get the starting value, it can be a navigable-map (compound index)
326-
// or list (single field index)
327-
var val = await indexMap.get(ceilingKey);
328-
yield* yieldValues(val);
329-
ceilingKey = await indexMap.higherKey(ceilingKey);
323+
if (isReverseScan) {
324+
// if reverse scan is required, then start from the last key
325+
var lastKey = await indexMap.lastKey();
326+
while (lastKey != null && compare(lastKey, comparable) >= 0) {
327+
// get the starting value, it can be a navigable-map (compound index)
328+
// or list (single field index)
329+
var val = await indexMap.get(lastKey);
330+
yield* yieldValues(val);
331+
lastKey = await indexMap.lowerKey(lastKey);
332+
}
333+
} else {
334+
var ceilingKey = await indexMap.ceilingKey(comparable);
335+
while (ceilingKey != null) {
336+
// get the starting value, it can be a navigable-map (compound index)
337+
// or list (single field index)
338+
var val = await indexMap.get(ceilingKey);
339+
yield* yieldValues(val);
340+
ceilingKey = await indexMap.higherKey(ceilingKey);
341+
}
330342
}
331343
}
332344

333345
@override
334346
toString() => "($field >= $value)";
335347
}
336348

337-
class _GreaterThanFilter extends ComparableFilter {
349+
class _GreaterThanFilter extends SortingAwareFilter {
338350
_GreaterThanFilter(super.field, super.value);
339351

340352
@override
@@ -354,21 +366,32 @@ class _GreaterThanFilter extends ComparableFilter {
354366

355367
@override
356368
Stream<dynamic> applyOnIndex(IndexMap indexMap) async* {
357-
var higherKey = await indexMap.higherKey(comparable);
358-
while (higherKey != null) {
359-
// get the starting value, it can be a navigable-map (compound index)
360-
// or list (single field index)
361-
var val = await indexMap.get(higherKey);
362-
yield* yieldValues(val);
363-
higherKey = await indexMap.higherKey(higherKey);
369+
if (isReverseScan) {
370+
var lastKey = await indexMap.lastKey();
371+
while (lastKey != null && compare(lastKey, comparable) > 0) {
372+
// get the starting value, it can be a navigable-map (compound index)
373+
// or list (single field index)
374+
var val = await indexMap.get(lastKey);
375+
yield* yieldValues(val);
376+
lastKey = await indexMap.lowerKey(lastKey);
377+
}
378+
} else {
379+
var higherKey = await indexMap.higherKey(comparable);
380+
while (higherKey != null) {
381+
// get the starting value, it can be a navigable-map (compound index)
382+
// or list (single field index)
383+
var val = await indexMap.get(higherKey);
384+
yield* yieldValues(val);
385+
higherKey = await indexMap.higherKey(higherKey);
386+
}
364387
}
365388
}
366389

367390
@override
368391
toString() => "($field > $value)";
369392
}
370393

371-
class _LesserEqualFilter extends ComparableFilter {
394+
class _LesserEqualFilter extends SortingAwareFilter {
372395
_LesserEqualFilter(super.field, super.value);
373396

374397
@override
@@ -388,21 +411,32 @@ class _LesserEqualFilter extends ComparableFilter {
388411

389412
@override
390413
Stream<dynamic> applyOnIndex(IndexMap indexMap) async* {
391-
var floorKey = await indexMap.floorKey(comparable);
392-
while (floorKey != null) {
393-
// get the starting value, it can be a navigable-map (compound index)
394-
// or list (single field index)
395-
var val = await indexMap.get(floorKey);
396-
yield* yieldValues(val);
397-
floorKey = await indexMap.lowerKey(floorKey);
414+
if (isReverseScan) {
415+
var floorKey = await indexMap.floorKey(comparable);
416+
while (floorKey != null) {
417+
// get the starting value, it can be a navigable-map (compound index)
418+
// or list (single field index)
419+
var val = await indexMap.get(floorKey);
420+
yield* yieldValues(val);
421+
floorKey = await indexMap.lowerKey(floorKey);
422+
}
423+
} else {
424+
var firstKey = await indexMap.firstKey();
425+
while (firstKey != null && compare(firstKey, comparable) <= 0) {
426+
// get the starting value, it can be a navigable-map (compound index)
427+
// or list (single field index)
428+
var val = await indexMap.get(firstKey);
429+
yield* yieldValues(val);
430+
firstKey = await indexMap.higherKey(firstKey);
431+
}
398432
}
399433
}
400434

401435
@override
402436
toString() => "($field <= $value)";
403437
}
404438

405-
class _LesserThanFilter extends ComparableFilter {
439+
class _LesserThanFilter extends SortingAwareFilter {
406440
_LesserThanFilter(super.field, super.value);
407441

408442
@override
@@ -422,13 +456,24 @@ class _LesserThanFilter extends ComparableFilter {
422456

423457
@override
424458
Stream<dynamic> applyOnIndex(IndexMap indexMap) async* {
425-
var lowerKey = await indexMap.lowerKey(comparable);
426-
while (lowerKey != null) {
427-
// get the starting value, it can be a navigable-map (compound index)
428-
// or list (single field index)
429-
var val = await indexMap.get(lowerKey);
430-
yield* yieldValues(val);
431-
lowerKey = await indexMap.lowerKey(lowerKey);
459+
if (isReverseScan) {
460+
var lowerKey = await indexMap.lowerKey(comparable);
461+
while (lowerKey != null) {
462+
// get the starting value, it can be a navigable-map (compound index)
463+
// or list (single field index)
464+
var val = await indexMap.get(lowerKey);
465+
yield* yieldValues(val);
466+
lowerKey = await indexMap.lowerKey(lowerKey);
467+
}
468+
} else {
469+
var firstKey = await indexMap.firstKey();
470+
while (firstKey != null && compare(firstKey, comparable) < 0) {
471+
// get the starting value, it can be a navigable-map (compound index)
472+
// or list (single field index)
473+
var val = await indexMap.get(firstKey);
474+
yield* yieldValues(val);
475+
firstKey = await indexMap.higherKey(firstKey);
476+
}
432477
}
433478
}
434479

packages/nitrite/lib/src/index/index_map.dart

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,41 @@ class IndexMap {
3333
return null;
3434
}
3535

36+
Future<dynamic> firstKey() async {
37+
DBValue? dbKey;
38+
if (_nitriteMap != null) {
39+
dbKey = await _nitriteMap.firstKey();
40+
} else if (_navigableMap != null) {
41+
dbKey = _navigableMap.firstKey();
42+
} else {
43+
return null;
44+
}
45+
46+
return dbKey == null || dbKey == DBNull.instance ? null : dbKey.value;
47+
}
48+
49+
Future<dynamic> lastKey() async {
50+
DBValue? dbKey;
51+
if (_nitriteMap != null) {
52+
dbKey = await _nitriteMap.lastKey();
53+
} else if (_navigableMap != null) {
54+
dbKey = _navigableMap.lastKey();
55+
} else {
56+
return null;
57+
}
58+
59+
return dbKey == null || dbKey == DBNull.instance ? null : dbKey.value;
60+
}
61+
3662
Future<dynamic> ceilingKey(Comparable? comparable) async {
3763
DBValue? dbKey = comparable is DBNull
3864
? comparable
3965
: (comparable == null ? DBNull.instance : DBValue(comparable));
4066

41-
if (!_reverseScan) {
42-
if (_nitriteMap != null) {
43-
dbKey = await _nitriteMap.ceilingKey(dbKey);
44-
} else if (_navigableMap != null) {
45-
dbKey = _navigableMap.ceilingKey(dbKey);
46-
}
47-
} else {
48-
if (_nitriteMap != null) {
49-
dbKey = await _nitriteMap.floorKey(dbKey);
50-
} else if (_navigableMap != null) {
51-
dbKey = _navigableMap.floorKey(dbKey);
52-
}
67+
if (_nitriteMap != null) {
68+
dbKey = await _nitriteMap.ceilingKey(dbKey);
69+
} else if (_navigableMap != null) {
70+
dbKey = _navigableMap.ceilingKey(dbKey);
5371
}
5472

5573
return dbKey == null || dbKey == DBNull.instance ? null : dbKey.value;
@@ -60,18 +78,10 @@ class IndexMap {
6078
? comparable
6179
: (comparable == null ? DBNull.instance : DBValue(comparable));
6280

63-
if (!_reverseScan) {
64-
if (_nitriteMap != null) {
65-
dbKey = await _nitriteMap.higherKey(dbKey);
66-
} else if (_navigableMap != null) {
67-
dbKey = _navigableMap.higherKey(dbKey);
68-
}
69-
} else {
70-
if (_nitriteMap != null) {
71-
dbKey = await _nitriteMap.lowerKey(dbKey);
72-
} else if (_navigableMap != null) {
73-
dbKey = _navigableMap.lowerKey(dbKey);
74-
}
81+
if (_nitriteMap != null) {
82+
dbKey = await _nitriteMap.higherKey(dbKey);
83+
} else if (_navigableMap != null) {
84+
dbKey = _navigableMap.higherKey(dbKey);
7585
}
7686

7787
return dbKey == null || dbKey == DBNull.instance ? null : dbKey.value;
@@ -82,18 +92,10 @@ class IndexMap {
8292
? comparable
8393
: (comparable == null ? DBNull.instance : DBValue(comparable));
8494

85-
if (!_reverseScan) {
86-
if (_nitriteMap != null) {
87-
dbKey = await _nitriteMap.floorKey(dbKey);
88-
} else if (_navigableMap != null) {
89-
dbKey = _navigableMap.floorKey(dbKey);
90-
}
91-
} else {
92-
if (_nitriteMap != null) {
93-
dbKey = await _nitriteMap.ceilingKey(dbKey);
94-
} else if (_navigableMap != null) {
95-
dbKey = _navigableMap.ceilingKey(dbKey);
96-
}
95+
if (_nitriteMap != null) {
96+
dbKey = await _nitriteMap.floorKey(dbKey);
97+
} else if (_navigableMap != null) {
98+
dbKey = _navigableMap.floorKey(dbKey);
9799
}
98100

99101
return dbKey == null || dbKey == DBNull.instance ? null : dbKey.value;
@@ -104,18 +106,10 @@ class IndexMap {
104106
? comparable
105107
: (comparable == null ? DBNull.instance : DBValue(comparable));
106108

107-
if (!_reverseScan) {
108-
if (_nitriteMap != null) {
109-
dbKey = await _nitriteMap.lowerKey(dbKey);
110-
} else if (_navigableMap != null) {
111-
dbKey = _navigableMap.lowerKey(dbKey);
112-
}
113-
} else {
114-
if (_nitriteMap != null) {
115-
dbKey = await _nitriteMap.higherKey(dbKey);
116-
} else if (_navigableMap != null) {
117-
dbKey = _navigableMap.higherKey(dbKey);
118-
}
109+
if (_nitriteMap != null) {
110+
dbKey = await _nitriteMap.lowerKey(dbKey);
111+
} else if (_navigableMap != null) {
112+
dbKey = _navigableMap.lowerKey(dbKey);
119113
}
120114

121115
return dbKey == null || dbKey is DBNull ? null : dbKey.value;

packages/nitrite/lib/src/index/index_scanner.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ class IndexScanner {
2222
: false;
2323
_indexMap.reverseScan = reverseScan!;
2424

25+
if (comparableFilter is SortingAwareFilter) {
26+
// if the filter is sorting aware, then set the scan order
27+
comparableFilter.isReverseScan = reverseScan;
28+
}
29+
2530
// apply the filter on the index map
2631
// result can be list of nitrite ids or list of navigable maps
2732
var scanResult =

packages/nitrite/lib/src/store/memory/in_memory_map.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ class InMemoryMap<Key, Value> extends NitriteMap<Key, Value> {
9595
_backingMap.reversedEntries.map((e) => (e.key, e.value)));
9696
}
9797

98+
@override
99+
Future<Key?> firstKey() async {
100+
_checkOpened();
101+
return _backingMap.isEmpty ? null : _backingMap.firstKey();
102+
}
103+
104+
@override
105+
Future<Key?> lastKey() async {
106+
_checkOpened();
107+
return _backingMap.isEmpty ? null : _backingMap.lastKey();
108+
}
109+
98110
@override
99111
Future<Key?> higherKey(Key key) async {
100112
_checkOpened();
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
/// DO NOT EDIT THIS FILE EXCEPT TO ENTER INITIAL VERSION AND OTHER META INFO
2-
/// THIS FILE IS AUTOMATICALLY OVER WRITTEN BY MetaUpdate
1+
/// DO NOT EDIT THIS FILE EXCEPT TO ENTER INITIAL VERSION AND OTHER META INFO
2+
/// THIS FILE IS AUTOMATICALLY OVER WRITTEN BY MetaUpdate
33
Map<String, String> meta = <String, String>{
4-
"version": "1.0.3",
4+
"version":"1.0.3",
55
};
6+

packages/nitrite/lib/src/store/nitrite_map.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ abstract class NitriteMap<Key, Value> extends AttributesAware
4444
/// Add a key-value pair if it does not yet exist.
4545
Future<Value?> putIfAbsent(Key key, Value value);
4646

47+
/// Get the first key in the map, or null if the map is empty.
48+
Future<Key?> firstKey();
49+
50+
/// Get the last key in the map, or null if the map is empty.
51+
Future<Key?> lastKey();
52+
4753
/// Get the lest key that is greater than the given key, or null if no
4854
/// such key exists.
4955
Future<Key?> higherKey(Key key);

0 commit comments

Comments
 (0)