Skip to content

Commit 4d90e92

Browse files
Copilotanidotnet
andcommitted
Fix inconsistent filtering of numeric values across types
- Modified ObjectUtils.deepEquals() to compare numeric values regardless of type - Updated EqualsFilter.applyOnIndex() to iterate and use deepEquals for cross-type numeric equality - Updated range filters (GTE, LTE, GT, LT) to use numeric comparison for single-field indexes - Updated test to reflect new numeric equality behavior - Added comprehensive test suite for numeric filter consistency Co-authored-by: anidotnet <[email protected]>
1 parent 7f06063 commit 4d90e92

File tree

8 files changed

+236
-68
lines changed

8 files changed

+236
-68
lines changed

nitrite/src/main/java/org/dizitart/no2/common/util/ObjectUtils.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ public static boolean deepEquals(Object o1, Object o2) {
127127
}
128128

129129
if (o1 instanceof Number && o2 instanceof Number) {
130-
if (o1.getClass() != o2.getClass()) {
131-
return false;
132-
}
133130
// cast to Number and take care of boxing and compare
134131
return Numbers.compare((Number) o1, (Number) o2) == 0;
135132
} else if (o1 instanceof Iterable && o2 instanceof Iterable) {

nitrite/src/main/java/org/dizitart/no2/filters/EqualsFilter.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,22 @@ public boolean apply(Pair<NitriteId, Document> element) {
4343

4444
@Override
4545
public List<?> applyOnIndex(IndexMap indexMap) {
46-
Object value = indexMap.get((Comparable<?>) getValue());
47-
if (value == null) {
48-
return new ArrayList<>();
46+
List<NitriteId> nitriteIds = new ArrayList<>();
47+
48+
// Iterate through all index entries and use deepEquals for comparison
49+
// This allows numeric types to be compared properly (e.g., int vs long)
50+
for (Pair<Comparable<?>, ?> entry : indexMap.entries()) {
51+
if (deepEquals(getValue(), entry.getFirst())) {
52+
Object value = entry.getSecond();
53+
if (value instanceof List) {
54+
@SuppressWarnings("unchecked")
55+
List<NitriteId> result = (List<NitriteId>) value;
56+
nitriteIds.addAll(result);
57+
}
58+
}
4959
}
5060

51-
if (value instanceof List) {
52-
return ((List<?>) value);
53-
}
54-
55-
List<Object> result = new ArrayList<>();
56-
result.add(value);
57-
return result;
61+
return nitriteIds;
5862
}
5963

6064
@Override

nitrite/src/main/java/org/dizitart/no2/filters/GreaterEqualFilter.java

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,53 @@ public List<?> applyOnIndex(IndexMap indexMap) {
6666
// maintain the find sorting order
6767
List<NitriteId> nitriteIds = new ArrayList<>();
6868

69+
// Check if this is a compound index by looking at the first value
70+
Comparable firstKey = indexMap.firstKey();
71+
boolean isCompoundIndex = firstKey != null && indexMap.get(firstKey) instanceof NavigableMap;
72+
73+
// For compound indexes or non-numeric comparisons, use the efficient range approach
74+
// For single-field numeric indexes, scan all entries to handle cross-type comparisons
75+
boolean useFullScan = !isCompoundIndex && comparable instanceof Number && firstKey instanceof Number;
76+
6977
if (isReverseScan()) {
7078
// if reverse scan is required, then start from the last key
7179
Comparable lastKey = indexMap.lastKey();
72-
while(lastKey != null && Comparables.compare(lastKey, comparable) >= 0) {
73-
// get the starting value, it can be a navigable-map (compound index)
74-
// or list (single field index)
75-
Object value = indexMap.get(lastKey);
76-
processIndexValue(value, subMaps, nitriteIds);
77-
lastKey = indexMap.lowerKey(lastKey);
80+
if (useFullScan) {
81+
// Full scan with numeric comparison for single-field numeric indexes
82+
while(lastKey != null) {
83+
if (compare((Number) lastKey, (Number) comparable) >= 0) {
84+
Object value = indexMap.get(lastKey);
85+
processIndexValue(value, subMaps, nitriteIds);
86+
}
87+
lastKey = indexMap.lowerKey(lastKey);
88+
}
89+
} else {
90+
// Efficient range scan for compound indexes or non-numeric comparisons
91+
while(lastKey != null && Comparables.compare(lastKey, comparable) >= 0) {
92+
Object value = indexMap.get(lastKey);
93+
processIndexValue(value, subMaps, nitriteIds);
94+
lastKey = indexMap.lowerKey(lastKey);
95+
}
7896
}
7997
} else {
80-
Comparable ceilingKey = indexMap.ceilingKey(comparable);
81-
while (ceilingKey != null) {
82-
// get the starting value, it can be a navigable-map (compound index)
83-
// or list (single field index)
84-
Object value = indexMap.get(ceilingKey);
85-
processIndexValue(value, subMaps, nitriteIds);
86-
ceilingKey = indexMap.higherKey(ceilingKey);
98+
if (useFullScan) {
99+
// Full scan with numeric comparison for single-field numeric indexes
100+
Comparable key = indexMap.firstKey();
101+
while (key != null) {
102+
if (compare((Number) key, (Number) comparable) >= 0) {
103+
Object value = indexMap.get(key);
104+
processIndexValue(value, subMaps, nitriteIds);
105+
}
106+
key = indexMap.higherKey(key);
107+
}
108+
} else {
109+
// Efficient range scan for compound indexes or non-numeric comparisons
110+
Comparable ceilingKey = indexMap.ceilingKey(comparable);
111+
while (ceilingKey != null) {
112+
Object value = indexMap.get(ceilingKey);
113+
processIndexValue(value, subMaps, nitriteIds);
114+
ceilingKey = indexMap.higherKey(ceilingKey);
115+
}
87116
}
88117
}
89118

nitrite/src/main/java/org/dizitart/no2/filters/GreaterThanFilter.java

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,52 @@ public List<?> applyOnIndex(IndexMap indexMap) {
6565
List<NavigableMap<Comparable<?>, Object>> subMaps = new ArrayList<>();
6666
List<NitriteId> nitriteIds = new ArrayList<>();
6767

68+
// Check if this is a compound index by looking at the first value
69+
Comparable firstKey = indexMap.firstKey();
70+
boolean isCompoundIndex = firstKey != null && indexMap.get(firstKey) instanceof NavigableMap;
71+
72+
// For compound indexes or non-numeric comparisons, use the efficient range approach
73+
// For single-field numeric indexes, scan all entries to handle cross-type comparisons
74+
boolean useFullScan = !isCompoundIndex && comparable instanceof Number && firstKey instanceof Number;
75+
6876
if (isReverseScan()) {
6977
Comparable lastKey = indexMap.lastKey();
70-
while (lastKey != null && Comparables.compare(lastKey, comparable) > 0) {
71-
// get the starting value, it can be a navigable-map (compound index)
72-
// or list (single field index)
73-
Object value = indexMap.get(lastKey);
74-
processIndexValue(value, subMaps, nitriteIds);
75-
lastKey = indexMap.lowerKey(lastKey);
78+
if (useFullScan) {
79+
// Full scan with numeric comparison for single-field numeric indexes
80+
while (lastKey != null) {
81+
if (compare((Number) lastKey, (Number) comparable) > 0) {
82+
Object value = indexMap.get(lastKey);
83+
processIndexValue(value, subMaps, nitriteIds);
84+
}
85+
lastKey = indexMap.lowerKey(lastKey);
86+
}
87+
} else {
88+
// Efficient range scan for compound indexes or non-numeric comparisons
89+
while (lastKey != null && Comparables.compare(lastKey, comparable) > 0) {
90+
Object value = indexMap.get(lastKey);
91+
processIndexValue(value, subMaps, nitriteIds);
92+
lastKey = indexMap.lowerKey(lastKey);
93+
}
7694
}
7795
} else {
78-
Comparable higherKey = indexMap.higherKey(comparable);
79-
while (higherKey != null) {
80-
// get the starting value, it can be a navigable-map (compound index)
81-
// or list (single field index)
82-
Object value = indexMap.get(higherKey);
83-
processIndexValue(value, subMaps, nitriteIds);
84-
higherKey = indexMap.higherKey(higherKey);
96+
Comparable key = indexMap.firstKey();
97+
if (useFullScan) {
98+
// Full scan with numeric comparison for single-field numeric indexes
99+
while (key != null) {
100+
if (compare((Number) key, (Number) comparable) > 0) {
101+
Object value = indexMap.get(key);
102+
processIndexValue(value, subMaps, nitriteIds);
103+
}
104+
key = indexMap.higherKey(key);
105+
}
106+
} else {
107+
// Efficient range scan for compound indexes or non-numeric comparisons
108+
Comparable higherKey = indexMap.higherKey(comparable);
109+
while (higherKey != null) {
110+
Object value = indexMap.get(higherKey);
111+
processIndexValue(value, subMaps, nitriteIds);
112+
higherKey = indexMap.higherKey(higherKey);
113+
}
85114
}
86115
}
87116

nitrite/src/main/java/org/dizitart/no2/filters/LesserEqualFilter.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,52 @@ public List<?> applyOnIndex(IndexMap indexMap) {
6464
List<NavigableMap<Comparable<?>, Object>> subMap = new ArrayList<>();
6565
List<NitriteId> nitriteIds = new ArrayList<>();
6666

67+
// Check if this is a compound index by looking at the first value
68+
Comparable firstKey = indexMap.firstKey();
69+
boolean isCompoundIndex = firstKey != null && indexMap.get(firstKey) instanceof NavigableMap;
70+
71+
// For compound indexes or non-numeric comparisons, use the efficient range approach
72+
// For single-field numeric indexes, scan all entries to handle cross-type comparisons
73+
boolean useFullScan = !isCompoundIndex && comparable instanceof Number && firstKey instanceof Number;
74+
6775
if (isReverseScan()) {
68-
Comparable floorKey = indexMap.floorKey(comparable);
69-
while (floorKey != null) {
70-
// get the starting value, it can be a navigable-map (compound index)
71-
// or list (single field index)
72-
Object value = indexMap.get(floorKey);
73-
processIndexValue(value, subMap, nitriteIds);
74-
floorKey = indexMap.lowerKey(floorKey);
76+
Comparable lastKey = indexMap.lastKey();
77+
if (useFullScan) {
78+
// Full scan with numeric comparison for single-field numeric indexes
79+
while (lastKey != null) {
80+
if (compare((Number) lastKey, (Number) comparable) <= 0) {
81+
Object value = indexMap.get(lastKey);
82+
processIndexValue(value, subMap, nitriteIds);
83+
}
84+
lastKey = indexMap.lowerKey(lastKey);
85+
}
86+
} else {
87+
// Efficient range scan for compound indexes or non-numeric comparisons
88+
Comparable floorKey = indexMap.floorKey(comparable);
89+
while (floorKey != null) {
90+
Object value = indexMap.get(floorKey);
91+
processIndexValue(value, subMap, nitriteIds);
92+
floorKey = indexMap.lowerKey(floorKey);
93+
}
7594
}
7695
} else {
77-
Comparable firstKey = indexMap.firstKey();
78-
while (firstKey != null && Comparables.compare(firstKey, comparable) <= 0) {
79-
// get the starting value, it can be a navigable-map (compound index)
80-
// or list (single field index)
81-
Object value = indexMap.get(firstKey);
82-
processIndexValue(value, subMap, nitriteIds);
83-
firstKey = indexMap.higherKey(firstKey);
96+
Comparable key = indexMap.firstKey();
97+
if (useFullScan) {
98+
// Full scan with numeric comparison for single-field numeric indexes
99+
while (key != null) {
100+
if (compare((Number) key, (Number) comparable) <= 0) {
101+
Object value = indexMap.get(key);
102+
processIndexValue(value, subMap, nitriteIds);
103+
}
104+
key = indexMap.higherKey(key);
105+
}
106+
} else {
107+
// Efficient range scan for compound indexes or non-numeric comparisons
108+
while (key != null && Comparables.compare(key, comparable) <= 0) {
109+
Object value = indexMap.get(key);
110+
processIndexValue(value, subMap, nitriteIds);
111+
key = indexMap.higherKey(key);
112+
}
84113
}
85114
}
86115

nitrite/src/main/java/org/dizitart/no2/filters/LesserThanFilter.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,52 @@ public List<?> applyOnIndex(IndexMap indexMap) {
6464
List<NavigableMap<Comparable<?>, Object>> subMap = new ArrayList<>();
6565
List<NitriteId> nitriteIds = new ArrayList<>();
6666

67+
// Check if this is a compound index by looking at the first value
68+
Comparable firstKey = indexMap.firstKey();
69+
boolean isCompoundIndex = firstKey != null && indexMap.get(firstKey) instanceof NavigableMap;
70+
71+
// For compound indexes or non-numeric comparisons, use the efficient range approach
72+
// For single-field numeric indexes, scan all entries to handle cross-type comparisons
73+
boolean useFullScan = !isCompoundIndex && comparable instanceof Number && firstKey instanceof Number;
74+
6775
if (isReverseScan()) {
68-
Comparable lowerKey = indexMap.lowerKey(comparable);
69-
while (lowerKey != null) {
70-
// get the starting value, it can be a navigable-map (compound index)
71-
// or list (single field index)
72-
Object value = indexMap.get(lowerKey);
73-
processIndexValue(value, subMap, nitriteIds);
74-
lowerKey = indexMap.lowerKey(lowerKey);
76+
Comparable lastKey = indexMap.lastKey();
77+
if (useFullScan) {
78+
// Full scan with numeric comparison for single-field numeric indexes
79+
while (lastKey != null) {
80+
if (compare((Number) lastKey, (Number) comparable) < 0) {
81+
Object value = indexMap.get(lastKey);
82+
processIndexValue(value, subMap, nitriteIds);
83+
}
84+
lastKey = indexMap.lowerKey(lastKey);
85+
}
86+
} else {
87+
// Efficient range scan for compound indexes or non-numeric comparisons
88+
Comparable lowerKey = indexMap.lowerKey(comparable);
89+
while (lowerKey != null) {
90+
Object value = indexMap.get(lowerKey);
91+
processIndexValue(value, subMap, nitriteIds);
92+
lowerKey = indexMap.lowerKey(lowerKey);
93+
}
7594
}
7695
} else {
77-
Comparable firstKey = indexMap.firstKey();
78-
while (firstKey != null && Comparables.compare(firstKey, comparable) < 0) {
79-
// get the starting value, it can be a navigable-map (compound index)
80-
// or list (single field index)
81-
Object value = indexMap.get(firstKey);
82-
processIndexValue(value, subMap, nitriteIds);
83-
firstKey = indexMap.higherKey(firstKey);
96+
Comparable key = indexMap.firstKey();
97+
if (useFullScan) {
98+
// Full scan with numeric comparison for single-field numeric indexes
99+
while (key != null) {
100+
if (compare((Number) key, (Number) comparable) < 0) {
101+
Object value = indexMap.get(key);
102+
processIndexValue(value, subMap, nitriteIds);
103+
}
104+
key = indexMap.higherKey(key);
105+
}
106+
} else {
107+
// Efficient range scan for compound indexes or non-numeric comparisons
108+
while (key != null && Comparables.compare(key, comparable) < 0) {
109+
Object value = indexMap.get(key);
110+
processIndexValue(value, subMap, nitriteIds);
111+
key = indexMap.higherKey(key);
112+
}
84113
}
85114
}
86115

nitrite/src/test/java/org/dizitart/no2/common/util/ObjectUtilsTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ public void testDeepEquals() {
9696
@Test
9797
public void testDeepEquals3() {
9898
MutableByte o1 = new MutableByte();
99-
assertFalse(ObjectUtils.deepEquals(o1, new MutableDouble()));
99+
// Numbers with equal values but different types should be considered equal
100+
assertTrue(ObjectUtils.deepEquals(o1, new MutableDouble()));
100101
}
101102

102103
@Test
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.dizitart.no2.integration.collection;
2+
3+
import org.dizitart.no2.Nitrite;
4+
import org.dizitart.no2.collection.Document;
5+
import org.dizitart.no2.collection.NitriteCollection;
6+
import org.dizitart.no2.filters.FluentFilter;
7+
import org.dizitart.no2.index.IndexOptions;
8+
import org.dizitart.no2.index.IndexType;
9+
import org.junit.After;
10+
import org.junit.Before;
11+
import org.junit.Test;
12+
13+
import static org.junit.Assert.assertEquals;
14+
15+
public class IssueTest {
16+
private Nitrite db;
17+
private NitriteCollection collection;
18+
19+
@Before
20+
public void setUp() {
21+
db = Nitrite.builder().openOrCreate();
22+
collection = db.getCollection("myCollection");
23+
}
24+
25+
@After
26+
public void tearDown() {
27+
if (collection != null) {
28+
collection.close();
29+
}
30+
if (db != null) {
31+
db.close();
32+
}
33+
}
34+
35+
@Test
36+
public void testOriginalIssue() {
37+
Document doc = Document.createDocument("value", 42);
38+
collection.insert(doc);
39+
40+
assertEquals(1, collection.find(FluentFilter.where("value").eq(42L)).size());
41+
assertEquals(1, collection.find(FluentFilter.where("value").lte(42L)).size());
42+
assertEquals(1, collection.find(FluentFilter.where("value").gte(42L)).size());
43+
44+
collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value");
45+
46+
assertEquals(1, collection.find(FluentFilter.where("value").eq(42L)).size());
47+
assertEquals(1, collection.find(FluentFilter.where("value").lte(42L)).size());
48+
assertEquals(1, collection.find(FluentFilter.where("value").gte(42L)).size());
49+
}
50+
}

0 commit comments

Comments
 (0)