diff --git a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java index ffa990763..dbddffe1f 100644 --- a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java +++ b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java @@ -242,10 +242,10 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex("field1", "field2"); cursor = collection.find(and(where("field1").eq(0.03), @@ -257,10 +257,10 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java index 28a09d17d..85c7604d3 100644 --- a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java +++ b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java @@ -244,12 +244,12 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java index a59e7e459..32b193738 100644 --- a/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java +++ b/nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java @@ -207,12 +207,12 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java index ffa990763..dbddffe1f 100644 --- a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java +++ b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java @@ -242,10 +242,10 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex("field1", "field2"); cursor = collection.find(and(where("field1").eq(0.03), @@ -257,10 +257,10 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java index 28a09d17d..85c7604d3 100644 --- a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java +++ b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java @@ -244,12 +244,12 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java index a59e7e459..32b193738 100644 --- a/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java +++ b/nitrite-rocksdb-adapter/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java @@ -207,12 +207,12 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite/src/main/java/org/dizitart/no2/common/DBValue.java b/nitrite/src/main/java/org/dizitart/no2/common/DBValue.java index 6ee3c5014..2294c7373 100644 --- a/nitrite/src/main/java/org/dizitart/no2/common/DBValue.java +++ b/nitrite/src/main/java/org/dizitart/no2/common/DBValue.java @@ -38,8 +38,11 @@ public class DBValue implements Comparable, Serializable { @Setter(AccessLevel.PRIVATE) private Comparable value; + private DBValue() { + } + public DBValue(Comparable value) { - this.value = value; + this.value = normalizeNumber(value); } @Override @@ -55,6 +58,15 @@ public int compareTo(DBValue o) { return Comparables.compare(value, o.value); } + private static Comparable normalizeNumber(Comparable value) { + // Normalize all numeric types to Double for consistent serialization + // This ensures Integer(5) and Double(5.0) are treated the same in indexes + if (value instanceof Number && !(value instanceof Double)) { + return ((Number) value).doubleValue(); + } + return value; + } + private void writeObject(ObjectOutputStream stream) throws IOException { stream.writeObject(value); } diff --git a/nitrite/src/main/java/org/dizitart/no2/common/util/Comparables.java b/nitrite/src/main/java/org/dizitart/no2/common/util/Comparables.java index 8b98f4612..cdaefdb3e 100644 --- a/nitrite/src/main/java/org/dizitart/no2/common/util/Comparables.java +++ b/nitrite/src/main/java/org/dizitart/no2/common/util/Comparables.java @@ -14,13 +14,7 @@ public static int compare(Comparable first, Comparable second) { if (first instanceof Number && second instanceof Number) { Number number1 = (Number) first; Number number2 = (Number) second; - int result = Numbers.compare(number1, number2); - if (!first.getClass().equals(second.getClass())) { - if (result == 0) { - return first.toString().compareTo(second.toString()) < 0 ? -1 : 1; - } - } - return result; + return Numbers.compare(number1, number2); } return first.compareTo(second); diff --git a/nitrite/src/main/java/org/dizitart/no2/common/util/ObjectUtils.java b/nitrite/src/main/java/org/dizitart/no2/common/util/ObjectUtils.java index 361a7082b..2910c5cae 100644 --- a/nitrite/src/main/java/org/dizitart/no2/common/util/ObjectUtils.java +++ b/nitrite/src/main/java/org/dizitart/no2/common/util/ObjectUtils.java @@ -127,9 +127,6 @@ public static boolean deepEquals(Object o1, Object o2) { } if (o1 instanceof Number && o2 instanceof Number) { - if (o1.getClass() != o2.getClass()) { - return false; - } // cast to Number and take care of boxing and compare return Numbers.compare((Number) o1, (Number) o2) == 0; } else if (o1 instanceof Iterable && o2 instanceof Iterable) { diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/EqualsFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/EqualsFilter.java index 20ad2a85a..0a65327b4 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/EqualsFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/EqualsFilter.java @@ -18,6 +18,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.index.IndexMap; @@ -43,7 +45,9 @@ public boolean apply(Pair element) { @Override public List applyOnIndex(IndexMap indexMap) { - Object value = indexMap.get((Comparable) getValue()); + Object fieldValue = getValue(); + DBValue dbValue = fieldValue == null ? DBNull.getInstance() : new DBValue((Comparable) fieldValue); + Object value = indexMap.get(dbValue); if (value == null) { return new ArrayList<>(); } diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/FieldBasedFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/FieldBasedFilter.java index c459bfe7f..c2fe34e3e 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/FieldBasedFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/FieldBasedFilter.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.mapper.NitriteMapper; import org.dizitart.no2.exceptions.ValidationException; @@ -102,7 +103,7 @@ protected void validateSearchTerm(NitriteMapper nitriteMapper, String field, Obj */ @SuppressWarnings("unchecked") protected void processIndexValue(Object value, - List, Object>> subMap, + List> subMap, List nitriteIds) { if (value instanceof List) { // if it is list then add it directly to nitrite ids @@ -111,7 +112,7 @@ protected void processIndexValue(Object value, } if (value instanceof NavigableMap) { - subMap.add((NavigableMap, Object>) value); + subMap.add((NavigableMap) value); } } } diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/GreaterEqualFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/GreaterEqualFilter.java index 3691cea75..b5adf480a 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/GreaterEqualFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/GreaterEqualFilter.java @@ -18,6 +18,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.common.util.Comparables; import org.dizitart.no2.exceptions.FilterException; @@ -58,18 +60,19 @@ public boolean apply(Pair element) { } @Override - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"rawtypes"}) public List applyOnIndex(IndexMap indexMap) { Comparable comparable = getComparable(); - List, Object>> subMaps = new ArrayList<>(); + DBValue dbValue = comparable == null ? DBNull.getInstance() : new DBValue(comparable); + List> subMaps = new ArrayList<>(); // maintain the find sorting order List nitriteIds = new ArrayList<>(); if (isReverseScan()) { // if reverse scan is required, then start from the last key - Comparable lastKey = indexMap.lastKey(); - while(lastKey != null && Comparables.compare(lastKey, comparable) >= 0) { + DBValue lastKey = indexMap.lastKey(); + while(lastKey != DBNull.getInstance() && Comparables.compare(lastKey, dbValue) >= 0) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(lastKey); @@ -77,8 +80,8 @@ public List applyOnIndex(IndexMap indexMap) { lastKey = indexMap.lowerKey(lastKey); } } else { - Comparable ceilingKey = indexMap.ceilingKey(comparable); - while (ceilingKey != null) { + DBValue ceilingKey = indexMap.ceilingKey(dbValue); + while (ceilingKey != DBNull.getInstance()) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(ceilingKey); diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/GreaterThanFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/GreaterThanFilter.java index 356d34581..6a8446e4b 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/GreaterThanFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/GreaterThanFilter.java @@ -18,6 +18,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.common.util.Comparables; import org.dizitart.no2.exceptions.FilterException; @@ -59,15 +61,16 @@ public boolean apply(Pair element) { } @Override - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"rawtypes"}) public List applyOnIndex(IndexMap indexMap) { Comparable comparable = getComparable(); - List, Object>> subMaps = new ArrayList<>(); + DBValue dbValue = comparable == null ? DBNull.getInstance() : new DBValue(comparable); + List> subMaps = new ArrayList<>(); List nitriteIds = new ArrayList<>(); if (isReverseScan()) { - Comparable lastKey = indexMap.lastKey(); - while (lastKey != null && Comparables.compare(lastKey, comparable) > 0) { + DBValue lastKey = indexMap.lastKey(); + while (lastKey != DBNull.getInstance() && Comparables.compare(lastKey, dbValue) > 0) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(lastKey); @@ -75,8 +78,8 @@ public List applyOnIndex(IndexMap indexMap) { lastKey = indexMap.lowerKey(lastKey); } } else { - Comparable higherKey = indexMap.higherKey(comparable); - while (higherKey != null) { + DBValue higherKey = indexMap.higherKey(dbValue); + while (higherKey != DBNull.getInstance()) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(higherKey); diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/InFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/InFilter.java index 13d1b2fdf..acfbef5cf 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/InFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/InFilter.java @@ -19,10 +19,13 @@ import lombok.Getter; import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.index.IndexMap; import java.util.*; +import java.util.stream.Collectors; /** * @author Anindya Chatterjee @@ -50,11 +53,15 @@ public boolean apply(Pair element) { } public List applyOnIndex(IndexMap indexMap) { - List, Object>> subMap = new ArrayList<>(); + // convert comparable set to DBValue set + Set dbValueSet = comparableSet.stream().map(value -> value == null ? DBNull.getInstance() + : new DBValue(value)).collect(Collectors.toSet()); + + List> subMap = new ArrayList<>(); List nitriteIds = new ArrayList<>(); - for (Pair, ?> entry : indexMap.entries()) { - if (comparableSet.contains(entry.getFirst())) { + for (Pair entry : indexMap.entries()) { + if (dbValueSet.contains(entry.getFirst())) { processIndexValue(entry.getSecond(), subMap, nitriteIds); } } diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/LesserEqualFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/LesserEqualFilter.java index 40758feea..3d5c585ce 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/LesserEqualFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/LesserEqualFilter.java @@ -18,6 +18,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.common.util.Comparables; import org.dizitart.no2.exceptions.FilterException; @@ -58,15 +60,16 @@ public boolean apply(Pair element) { } @Override - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"rawtypes"}) public List applyOnIndex(IndexMap indexMap) { Comparable comparable = getComparable(); - List, Object>> subMap = new ArrayList<>(); + DBValue dbValue = comparable == null ? DBNull.getInstance() : new DBValue(comparable); + List> subMap = new ArrayList<>(); List nitriteIds = new ArrayList<>(); if (isReverseScan()) { - Comparable floorKey = indexMap.floorKey(comparable); - while (floorKey != null) { + DBValue floorKey = indexMap.floorKey(dbValue); + while (floorKey != DBNull.getInstance()) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(floorKey); @@ -74,8 +77,8 @@ public List applyOnIndex(IndexMap indexMap) { floorKey = indexMap.lowerKey(floorKey); } } else { - Comparable firstKey = indexMap.firstKey(); - while (firstKey != null && Comparables.compare(firstKey, comparable) <= 0) { + DBValue firstKey = indexMap.firstKey(); + while (firstKey != DBNull.getInstance() && Comparables.compare(firstKey, dbValue) <= 0) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(firstKey); diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/LesserThanFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/LesserThanFilter.java index 66f8dc66a..2224ee1c0 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/LesserThanFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/LesserThanFilter.java @@ -18,6 +18,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.common.util.Comparables; import org.dizitart.no2.exceptions.FilterException; @@ -58,15 +60,16 @@ public boolean apply(Pair element) { } @Override - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"rawtypes"}) public List applyOnIndex(IndexMap indexMap) { Comparable comparable = getComparable(); - List, Object>> subMap = new ArrayList<>(); + DBValue dbValue = comparable == null ? DBNull.getInstance() : new DBValue(comparable); + List> subMap = new ArrayList<>(); List nitriteIds = new ArrayList<>(); if (isReverseScan()) { - Comparable lowerKey = indexMap.lowerKey(comparable); - while (lowerKey != null) { + DBValue lowerKey = indexMap.lowerKey(dbValue); + while (lowerKey != DBNull.getInstance()) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(lowerKey); @@ -74,8 +77,8 @@ public List applyOnIndex(IndexMap indexMap) { lowerKey = indexMap.lowerKey(lowerKey); } } else { - Comparable firstKey = indexMap.firstKey(); - while (firstKey != null && Comparables.compare(firstKey, comparable) < 0) { + DBValue firstKey = indexMap.firstKey(); + while (firstKey != DBNull.getInstance() && Comparables.compare(firstKey, dbValue) < 0) { // get the starting value, it can be a navigable-map (compound index) // or list (single field index) Object value = indexMap.get(firstKey); diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/NotEqualsFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/NotEqualsFilter.java index daa95d9f8..488e13823 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/NotEqualsFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/NotEqualsFilter.java @@ -2,6 +2,8 @@ import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBNull; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.index.IndexMap; @@ -27,11 +29,13 @@ public boolean apply(Pair element) { } public List applyOnIndex(IndexMap indexMap) { - List, Object>> subMap = new ArrayList<>(); + Object fieldValue = getValue(); + DBValue dbValue = fieldValue == null ? DBNull.getInstance() : new DBValue((Comparable) fieldValue); + List> subMap = new ArrayList<>(); List nitriteIds = new ArrayList<>(); - for (Pair, ?> entry : indexMap.entries()) { - if (!deepEquals(getValue(), entry.getFirst())) { + for (Pair entry : indexMap.entries()) { + if (!deepEquals(dbValue, entry.getFirst())) { processIndexValue(entry.getSecond(), subMap, nitriteIds); } } diff --git a/nitrite/src/main/java/org/dizitart/no2/filters/NotInFilter.java b/nitrite/src/main/java/org/dizitart/no2/filters/NotInFilter.java index 6375fe617..ba3d952e5 100644 --- a/nitrite/src/main/java/org/dizitart/no2/filters/NotInFilter.java +++ b/nitrite/src/main/java/org/dizitart/no2/filters/NotInFilter.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.dizitart.no2.collection.Document; import org.dizitart.no2.collection.NitriteId; +import org.dizitart.no2.common.DBValue; import org.dizitart.no2.common.tuples.Pair; import org.dizitart.no2.index.IndexMap; @@ -50,10 +51,10 @@ public boolean apply(Pair element) { } public List applyOnIndex(IndexMap indexMap) { - List, Object>> subMap = new ArrayList<>(); + List> subMap = new ArrayList<>(); List nitriteIds = new ArrayList<>(); - for (Pair, ?> entry : indexMap.entries()) { + for (Pair entry : indexMap.entries()) { if (!comparableSet.contains(entry.getFirst())) { processIndexValue(entry.getSecond(), subMap, nitriteIds); } diff --git a/nitrite/src/main/java/org/dizitart/no2/index/IndexMap.java b/nitrite/src/main/java/org/dizitart/no2/index/IndexMap.java index acb6b0dfc..cb944cd96 100644 --- a/nitrite/src/main/java/org/dizitart/no2/index/IndexMap.java +++ b/nitrite/src/main/java/org/dizitart/no2/index/IndexMap.java @@ -59,118 +59,115 @@ public IndexMap(NavigableMap navigableMap) { this.navigableMap = navigableMap; } - public > T firstKey() { + public DBValue firstKey() { DBValue dbKey; if (nitriteMap != null) { dbKey = nitriteMap.firstKey(); } else if (navigableMap != null) { dbKey = navigableMap.firstKey(); } else { - return null; + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } - public > T lastKey() { + public DBValue lastKey() { DBValue dbKey; if (nitriteMap != null) { dbKey = nitriteMap.lastKey(); } else if (navigableMap != null) { dbKey = navigableMap.lastKey(); } else { - return null; + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } /** * Get the largest key that is smaller than the given key, or null if no * such key exists. * - * @param the type parameter * @param key the key * @return the t */ - public > T lowerKey(T key) { - DBValue dbKey = key == null ? DBNull.getInstance() : new DBValue(key); + public DBValue lowerKey(DBValue key) { + DBValue dbKey = key == null ? DBNull.getInstance() : key; if (nitriteMap != null) { dbKey = nitriteMap.lowerKey(dbKey); } else if (navigableMap != null) { dbKey = navigableMap.lowerKey(dbKey); + } else { + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } /** * Get the smallest key that is larger than the given key, or null if no * such key exists. * - * @param the type parameter * @param key the key * @return the t */ - public > T higherKey(T key) { - DBValue dbKey = key == null ? DBNull.getInstance() : new DBValue(key); + public DBValue higherKey(DBValue key) { + DBValue dbKey = key == null ? DBNull.getInstance() : key; if (nitriteMap != null) { dbKey = nitriteMap.higherKey(dbKey); } else if (navigableMap != null) { dbKey = navigableMap.higherKey(dbKey); + } else { + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } /** * Get the smallest key that is larger or equal to this key. * - * @param the type parameter * @param key the key * @return the t */ - public > T ceilingKey(T key) { - DBValue dbKey = key == null ? DBNull.getInstance() : new DBValue(key); + public DBValue ceilingKey(DBValue key) { + DBValue dbKey = key == null ? DBNull.getInstance() : key; if (nitriteMap != null) { dbKey = nitriteMap.ceilingKey(dbKey); } else if (navigableMap != null) { dbKey = navigableMap.ceilingKey(dbKey); + } else { + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } /** * Get the largest key that is smaller or equal to this key. * - * @param the type parameter * @param key the key * @return the t */ - public > T floorKey(T key) { - DBValue dbKey = key == null ? DBNull.getInstance() : new DBValue(key); + public DBValue floorKey(DBValue key) { + DBValue dbKey = key == null ? DBNull.getInstance() : key; if (nitriteMap != null) { dbKey = nitriteMap.floorKey(dbKey); } else if (navigableMap != null) { dbKey = navigableMap.floorKey(dbKey); + } else { + dbKey = null; } - - return dbKey == null || dbKey instanceof DBNull ? null : (T) dbKey.getValue(); + return dbKey == null ? DBNull.getInstance() : dbKey; } /** * Gets the value mapped with the specified key or null otherwise. * - * @param comparable the comparable + * @param dbValue the db value * @return the object */ - public Object get(Comparable comparable) { - DBValue dbKey = comparable == null ? DBNull.getInstance() : new DBValue(comparable); + public Object get(DBValue dbValue) { if (nitriteMap != null) { - return nitriteMap.get(dbKey); + return nitriteMap.get(dbValue); } else if (navigableMap != null) { - return navigableMap.get(dbKey); + return navigableMap.get(dbValue); } return null; } @@ -180,7 +177,7 @@ public Object get(Comparable comparable) { * * @return the iterable */ - public Iterable, ?>> entries() { + public Iterable> entries() { if (nitriteMap != null) { Iterator> entryIterator; if (!reverseScan) { @@ -189,20 +186,20 @@ public Object get(Comparable comparable) { entryIterator = nitriteMap.reversedEntries().iterator(); } - return (Iterable, ?>>) () -> new Iterator<>() { + return (Iterable>) () -> new Iterator<>() { @Override public boolean hasNext() { return entryIterator.hasNext(); } @Override - public Pair, ?> next() { + public Pair next() { Pair next = entryIterator.next(); DBValue dbKey = next.getFirst(); if (dbKey instanceof DBNull) { return new Pair<>(null, next.getSecond()); } else { - return new Pair<>(dbKey.getValue(), next.getSecond()); + return new Pair<>(dbKey, next.getSecond()); } } }; @@ -214,20 +211,20 @@ public boolean hasNext() { entryIterator = navigableMap.entrySet().iterator(); } - return (Iterable, ?>>) () -> new Iterator, ?>>() { + return (Iterable>) () -> new Iterator<>() { @Override public boolean hasNext() { return entryIterator.hasNext(); } @Override - public Pair, ?> next() { + public Pair next() { Map.Entry next = entryIterator.next(); DBValue dbKey = next.getKey(); if (dbKey instanceof DBNull) { return new Pair<>(null, next.getValue()); } else { - return new Pair<>(dbKey.getValue(), next.getValue()); + return new Pair<>(dbKey, next.getValue()); } } }; @@ -244,7 +241,7 @@ public List getTerminalNitriteIds() { List terminalResult = new CopyOnWriteArrayList<>(); // scan each entry of the navigable map and collect all terminal nitrite-ids - for (Pair, ?> entry : entries()) { + for (Pair entry : entries()) { // if the value is terminal, collect all nitrite-ids if (entry.getSecond() instanceof List) { List nitriteIds = (List) entry.getSecond(); diff --git a/nitrite/src/test/java/org/dizitart/no2/common/util/ComparablesTest.java b/nitrite/src/test/java/org/dizitart/no2/common/util/ComparablesTest.java index 77ce794c4..7284d1a54 100644 --- a/nitrite/src/test/java/org/dizitart/no2/common/util/ComparablesTest.java +++ b/nitrite/src/test/java/org/dizitart/no2/common/util/ComparablesTest.java @@ -29,7 +29,7 @@ public void testCompare3() { @Test public void testCompare4() { MutableByte first = new MutableByte(); - assertEquals(-1, Comparables.compare(first, new MutableDouble())); + assertEquals(0, Comparables.compare(first, new MutableDouble())); } } diff --git a/nitrite/src/test/java/org/dizitart/no2/common/util/ObjectUtilsTest.java b/nitrite/src/test/java/org/dizitart/no2/common/util/ObjectUtilsTest.java index ae4741c61..4e2b58dec 100644 --- a/nitrite/src/test/java/org/dizitart/no2/common/util/ObjectUtilsTest.java +++ b/nitrite/src/test/java/org/dizitart/no2/common/util/ObjectUtilsTest.java @@ -96,7 +96,8 @@ public void testDeepEquals() { @Test public void testDeepEquals3() { MutableByte o1 = new MutableByte(); - assertFalse(ObjectUtils.deepEquals(o1, new MutableDouble())); + // Numbers with equal values but different types should be considered equal + assertTrue(ObjectUtils.deepEquals(o1, new MutableDouble())); } @Test diff --git a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java index 71c848c3a..66822d8da 100644 --- a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java +++ b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionCompoundIndexTest.java @@ -251,10 +251,12 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + // With cross-type numeric equality, both Integer(5) and Double(5.0) match + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + // With cross-type numeric equality, both Integer(5) and Double(5.0) match + assertEquals(cursor.size(), 2); collection.createIndex("field1", "field2"); cursor = collection.find(and(where("field1").eq(0.03), @@ -266,10 +268,12 @@ public void testIssue178() { assertEquals(cursor.size(), 1); cursor = collection.find(where("field1").eq(5)); - assertEquals(cursor.size(), 1); + // With cross-type numeric equality, both Integer(5) and Double(5.0) match + assertEquals(cursor.size(), 2); cursor = collection.find(where("field1").eq(5.0)); - assertEquals(cursor.size(), 1); + // With cross-type numeric equality, both Integer(5) and Double(5.0) match + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java index bb4ea47e7..81f7b4644 100644 --- a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java +++ b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionIndexTest.java @@ -181,13 +181,15 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); + // With cross-type numeric equality, both 5 (Integer) and 5.0 (Double) match DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); + // Same behavior with index cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java index a59e7e459..3e64c933f 100644 --- a/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java +++ b/nitrite/src/test/java/org/dizitart/no2/integration/collection/CollectionSingleFieldIndexTest.java @@ -206,13 +206,15 @@ public void testIssue178() { collection.insert(doc1, doc2, doc3, doc4, doc5); + // With cross-type numeric equality, both 5 (Integer) and 5.0 (Double) match DocumentCursor cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "field"); + // Same behavior with index cursor = collection.find(where("field").eq(5)); - assertEquals(cursor.size(), 1); + assertEquals(cursor.size(), 2); } @Test diff --git a/nitrite/src/test/java/org/dizitart/no2/integration/collection/IssueTest.java b/nitrite/src/test/java/org/dizitart/no2/integration/collection/IssueTest.java new file mode 100644 index 000000000..4b61ac834 --- /dev/null +++ b/nitrite/src/test/java/org/dizitart/no2/integration/collection/IssueTest.java @@ -0,0 +1,50 @@ +package org.dizitart.no2.integration.collection; + +import org.dizitart.no2.Nitrite; +import org.dizitart.no2.collection.Document; +import org.dizitart.no2.collection.NitriteCollection; +import org.dizitart.no2.filters.FluentFilter; +import org.dizitart.no2.index.IndexOptions; +import org.dizitart.no2.index.IndexType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IssueTest { + private Nitrite db; + private NitriteCollection collection; + + @Before + public void setUp() { + db = Nitrite.builder().openOrCreate(); + collection = db.getCollection("myCollection"); + } + + @After + public void tearDown() { + if (collection != null) { + collection.close(); + } + if (db != null) { + db.close(); + } + } + + @Test + public void testOriginalIssue() { + Document doc = Document.createDocument("value", 42); + collection.insert(doc); + + assertEquals(1, collection.find(FluentFilter.where("value").eq(42L)).size()); + assertEquals(1, collection.find(FluentFilter.where("value").lte(42L)).size()); + assertEquals(1, collection.find(FluentFilter.where("value").gte(42L)).size()); + + collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value"); + + assertEquals(1, collection.find(FluentFilter.where("value").eq(42L)).size()); + assertEquals(1, collection.find(FluentFilter.where("value").lte(42L)).size()); + assertEquals(1, collection.find(FluentFilter.where("value").gte(42L)).size()); + } +} diff --git a/nitrite/src/test/java/org/dizitart/no2/integration/collection/NumericFilterTest.java b/nitrite/src/test/java/org/dizitart/no2/integration/collection/NumericFilterTest.java new file mode 100644 index 000000000..a936e1718 --- /dev/null +++ b/nitrite/src/test/java/org/dizitart/no2/integration/collection/NumericFilterTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2017-2021 Nitrite author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.dizitart.no2.integration.collection; + +import org.dizitart.no2.Nitrite; +import org.dizitart.no2.collection.Document; +import org.dizitart.no2.collection.NitriteCollection; +import org.dizitart.no2.index.IndexOptions; +import org.dizitart.no2.index.IndexType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.dizitart.no2.filters.FluentFilter.where; +import static org.junit.Assert.assertEquals; + +/** + * Test case for numeric filter consistency across different numeric types. + * + * @author Nitrite Team + */ +public class NumericFilterTest { + private Nitrite db; + private NitriteCollection collection; + + @Before + public void setUp() { + db = Nitrite.builder().openOrCreate(); + collection = db.getCollection("numericTest"); + } + + @After + public void tearDown() { + if (collection != null) { + collection.close(); + } + if (db != null) { + db.close(); + } + } + + @Test + public void testIntLongComparisonWithoutIndex() { + Document doc = Document.createDocument("value", 42); + collection.insert(doc); + + // All filters should work consistently without index + assertEquals(1, collection.find(where("value").eq(42L)).size()); + assertEquals(1, collection.find(where("value").lte(42L)).size()); + assertEquals(1, collection.find(where("value").gte(42L)).size()); + assertEquals(1, collection.find(where("value").lt(43L)).size()); + assertEquals(1, collection.find(where("value").gt(41L)).size()); + } + + @Test + public void testIntLongComparisonWithIndex() { + Document doc = Document.createDocument("value", 42); + collection.insert(doc); + + collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value"); + + // All filters should work consistently with index + assertEquals(1, collection.find(where("value").eq(42L)).size()); + assertEquals(1, collection.find(where("value").lte(42L)).size()); + assertEquals(1, collection.find(where("value").gte(42L)).size()); + assertEquals(1, collection.find(where("value").lt(43L)).size()); + assertEquals(1, collection.find(where("value").gt(41L)).size()); + } + + @Test + public void testIntDoubleComparisonWithoutIndex() { + Document doc = Document.createDocument("value", 42); + collection.insert(doc); + + // All filters should work consistently without index + assertEquals(1, collection.find(where("value").eq(42.0)).size()); + assertEquals(1, collection.find(where("value").lte(42.0)).size()); + assertEquals(1, collection.find(where("value").gte(42.0)).size()); + assertEquals(1, collection.find(where("value").lt(43.0)).size()); + assertEquals(1, collection.find(where("value").gt(41.0)).size()); + } + + @Test + public void testIntDoubleComparisonWithIndex() { + Document doc = Document.createDocument("value", 42); + collection.insert(doc); + + collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value"); + + // All filters should work consistently with index + assertEquals(1, collection.find(where("value").eq(42.0)).size()); + assertEquals(1, collection.find(where("value").lte(42.0)).size()); + assertEquals(1, collection.find(where("value").gte(42.0)).size()); + assertEquals(1, collection.find(where("value").lt(43.0)).size()); + assertEquals(1, collection.find(where("value").gt(41.0)).size()); + } + + @Test + public void testLongIntComparisonWithoutIndex() { + Document doc = Document.createDocument("value", 42L); + collection.insert(doc); + + // All filters should work consistently without index + assertEquals(1, collection.find(where("value").eq(42)).size()); + assertEquals(1, collection.find(where("value").lte(42)).size()); + assertEquals(1, collection.find(where("value").gte(42)).size()); + assertEquals(1, collection.find(where("value").lt(43)).size()); + assertEquals(1, collection.find(where("value").gt(41)).size()); + } + + @Test + public void testLongIntComparisonWithIndex() { + Document doc = Document.createDocument("value", 42L); + collection.insert(doc); + + collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value"); + + // All filters should work consistently with index + assertEquals(1, collection.find(where("value").eq(42)).size()); + assertEquals(1, collection.find(where("value").lte(42)).size()); + assertEquals(1, collection.find(where("value").gte(42)).size()); + assertEquals(1, collection.find(where("value").lt(43)).size()); + assertEquals(1, collection.find(where("value").gt(41)).size()); + } + + @Test + public void testMultipleNumericTypesWithoutIndex() { + collection.insert(Document.createDocument("value", 10)); + collection.insert(Document.createDocument("value", 20L)); + collection.insert(Document.createDocument("value", 30.0)); + + // Query with different numeric types + assertEquals(1, collection.find(where("value").eq(10L)).size()); + assertEquals(1, collection.find(where("value").eq(20)).size()); + assertEquals(1, collection.find(where("value").eq(30)).size()); + + assertEquals(3, collection.find(where("value").gte(10)).size()); + assertEquals(2, collection.find(where("value").gt(10L)).size()); + assertEquals(2, collection.find(where("value").lte(20.0)).size()); + } + + @Test + public void testMultipleNumericTypesWithIndex() { + collection.insert(Document.createDocument("value", 10)); + collection.insert(Document.createDocument("value", 20L)); + collection.insert(Document.createDocument("value", 30.0)); + + collection.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "value"); + + // Query with different numeric types + assertEquals(1, collection.find(where("value").eq(10L)).size()); + assertEquals(1, collection.find(where("value").eq(20)).size()); + assertEquals(1, collection.find(where("value").eq(30)).size()); + + assertEquals(3, collection.find(where("value").gte(10)).size()); + assertEquals(2, collection.find(where("value").gt(10L)).size()); + assertEquals(2, collection.find(where("value").lte(20.0)).size()); + } +}