Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public void testFindByOrFilter() throws ParseException {

FindPlan findPlan = cursor.getFindPlan();
assertEquals(3, findPlan.getSubPlans().size());
assertEquals(5, cursor.size());
assertEquals(3, cursor.size());

// distinct
cursor = collection.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public void testFindByOrFilter() throws ParseException {

FindPlan findPlan = cursor.getFindPlan();
assertEquals(3, findPlan.getSubPlans().size());
assertEquals(5, cursor.size());
assertEquals(3, cursor.size());

// distinct
cursor = collection.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,9 @@ private RecordStream<Pair<NitriteId, Document>> findSuitableStream(FindPlan find
// concat all suitable stream of all sub plans
rawStream = new ConcatStream(subStreams);

if (findPlan.isDistinct()) {
// return only distinct items
rawStream = new DistinctStream(rawStream);
}
// Always apply distinct stream for OR filters to avoid duplicates
// when the same document matches multiple sub-plans (different indexes)
rawStream = new DistinctStream(rawStream);
} else {
// and or single filter
if (findPlan.getByIdFilter() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ public void testFindByOrFilterAndFilter() {
)
);

assertEquals(3, cursor.size());
// With the fix, OR filters no longer return duplicates
// doc2 = {firstName: "fn2", lastName: "ln2"}
// doc3 = {firstName: "fn3", lastName: "ln2"}
// First AND: lastName=ln2 AND firstName!=fn1 → matches doc2 and doc3
// Second AND: firstName=fn3 AND lastName=ln2 → matches doc3
// Union without duplicates: doc2 and doc3 (2 total)
assertEquals(2, cursor.size());

FindPlan findPlan = cursor.getFindPlan();
assertNull(findPlan.getIndexScanFilter());
Expand All @@ -101,11 +107,11 @@ public void testFindByOrFilterAndFilter() {
d.get("firstName", String.class).equals("fn2")
&& d.get("lastName", String.class).equals("ln2")).count());

assertEquals(2, cursor.toList().stream().filter(d ->
assertEquals(1, cursor.toList().stream().filter(d ->
d.get("firstName", String.class).equals("fn3")
&& d.get("lastName", String.class).equals("ln2")).count());
Comment on lines +110 to 112
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling cursor.toList() twice (lines 106 and 110) iterates through the cursor multiple times and creates multiple list copies. Since cursors are typically forward-only, cache the list in a variable and reuse it for both filter operations to improve performance.

Copilot uses AI. Check for mistakes.

// distinct test
// distinct test - should still return the same results since we're already deduplicating
cursor = collection.find(
or(
and(
Expand Down Expand Up @@ -231,11 +237,21 @@ public void testFindByOrFilter() throws ParseException {
)
);

// With the fix, OR filters no longer return duplicates
// doc1 = {firstName: "fn1", lastName: "ln1", birthDay: "2012-07-01"}
// doc2 = {firstName: "fn2", lastName: "ln2", birthDay: "2010-06-12"}
// doc3 = {firstName: "fn3", lastName: "ln2", birthDay: "2014-04-17"}
// Flattened OR conditions:
// 1. lastName=ln2 → doc2, doc3
// 2. firstName!=fn1 → doc2, doc3
// 3. birthDay=2012-07-01 → doc1
// 4. firstName!=fn1 → doc2, doc3 (duplicate)
// Union without duplicates: doc1, doc2, doc3 (3 total)
FindPlan findPlan = cursor.getFindPlan();
assertEquals(3, findPlan.getSubPlans().size());
assertEquals(5, cursor.size());
assertEquals(3, cursor.size());

// distinct
// distinct test - should still return the same results since we're already deduplicating
cursor = collection.find(
or(
or(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

import org.dizitart.no2.Nitrite;
import org.dizitart.no2.collection.Document;
import org.dizitart.no2.collection.DocumentCursor;
import org.dizitart.no2.collection.NitriteCollection;
import org.dizitart.no2.filters.Filter;
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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static org.junit.Assert.assertEquals;

public class IssueTest {
Expand Down Expand Up @@ -47,4 +53,32 @@ public void testOriginalIssue() {
assertEquals(1, collection.find(FluentFilter.where("value").lte(42L)).size());
assertEquals(1, collection.find(FluentFilter.where("value").gte(42L)).size());
}

@Test
public void testMultipleIndexesOrFilterDuplicates() {
NitriteCollection items = db.getCollection("items");
items.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "field_a");
items.createIndex(IndexOptions.indexOptions(IndexType.NON_UNIQUE), "field_b");

Document doc = Document.createDocument();
doc.put("field_a", "A");
doc.put("field_b", "B");
items.insert(doc);

Filter aFilter = FluentFilter.where("field_a").eq("A");
Filter bFilter = FluentFilter.where("field_b").eq("B");

Filter orFilter = Filter.or(aFilter, bFilter);

DocumentCursor cursor = items.find(orFilter);
Iterator<Document> docIter = cursor.iterator();

List<Long> matches = new ArrayList<>();
while (docIter.hasNext()) {
Document match = docIter.next();
long id = match.getId().getIdValue();
matches.add(id);
}
assertEquals("Single document must yield single match", 1, matches.size());
}
}