Skip to content

Commit ea9fd6b

Browse files
committed
Fix some issues
Signed-off-by: Binlong Gao <[email protected]>
1 parent 994644d commit ea9fd6b

File tree

3 files changed

+57
-34
lines changed

3 files changed

+57
-34
lines changed

server/src/main/java/org/apache/lucene/search/grouping/CollapsingTopDocsCollector.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ public final class CollapsingTopDocsCollector<T> extends FirstPassGroupingCollec
8585
this.after = after;
8686

8787
if (after != null) {
88-
// we have only one sort field which is the collapse field
88+
// we should have only one sort field which is the collapse field
89+
if (sort.getSort().length != 1 || !sort.getSort()[0].getField().equals(collapseField)) {
90+
throw new IllegalArgumentException("The after parameter can only be used when the sort is based on the collapse field");
91+
}
8992
SortField field = sort.getSort()[0];
9093
afterComparator = field.getComparator(1, Pruning.NONE);
9194

server/src/main/java/org/opensearch/search/collapse/CollapseContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public CollapsingTopDocsCollector<?> createTopDocs(Sort sort, int topN, FieldDoc
109109
} else if (fieldType != null && fieldType.unwrap() instanceof NumberFieldMapper.NumberFieldType) {
110110
return CollapsingTopDocsCollector.createNumeric(fieldName, fieldType, sort, topN, searchAfter);
111111
} else {
112-
throw new IllegalStateException("unknown type for collapse field " + fieldName + ", only keywords and numbers are accepted");
112+
throw new IllegalStateException("unsupported type for collapse field " + fieldName + ", only keywords and numbers are accepted");
113113
}
114114
}
115115
}

server/src/test/java/org/opensearch/lucene/grouping/CollapsingTopDocsCollectorTests.java

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,47 @@ public void testInconsistentShardIndicesException() {
494494
assertEquals("Inconsistent order of shard indices", exception.getMessage());
495495
}
496496

497+
public void testSearchAfterValidation() {
498+
MappedFieldType fieldType = new MockFieldMapper.FakeFieldType("category");
499+
500+
// Test multiple sort fields - should fail
501+
Sort multiSort = new Sort(new SortField("category", SortField.Type.INT), new SortField("score", SortField.Type.FLOAT));
502+
FieldDoc multiAfter = new FieldDoc(0, Float.NaN, new Object[] { 1, 1.0f });
503+
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> {
504+
CollapsingTopDocsCollector.createNumeric("category", fieldType, multiSort, 10, multiAfter);
505+
});
506+
assertEquals("The after parameter can only be used when the sort is based on the collapse field", exception.getMessage());
507+
508+
// Test wrong sort field - should fail
509+
Sort wrongSort = new Sort(new SortField("different_field", SortField.Type.INT));
510+
FieldDoc wrongAfter = new FieldDoc(0, Float.NaN, new Object[] { 1 });
511+
exception = expectThrows(IllegalArgumentException.class, () -> {
512+
CollapsingTopDocsCollector.createNumeric("category", fieldType, wrongSort, 10, wrongAfter);
513+
});
514+
assertEquals("The after parameter can only be used when the sort is based on the collapse field", exception.getMessage());
515+
516+
// Test correct sort field - should succeed
517+
Sort correctSort = new Sort(new SortField("category", SortField.Type.INT));
518+
FieldDoc correctAfter = new FieldDoc(0, Float.NaN, new Object[] { 1 });
519+
CollapsingTopDocsCollector<?> collector = CollapsingTopDocsCollector.createNumeric(
520+
"category",
521+
fieldType,
522+
correctSort,
523+
10,
524+
correctAfter
525+
);
526+
assertNotNull(collector);
527+
528+
// Test keyword field with multiple sorts - should fail
529+
MappedFieldType keywordFieldType = new MockFieldMapper.FakeFieldType("tag");
530+
Sort keywordMultiSort = new Sort(new SortField("tag", SortField.Type.STRING), new SortField("score", SortField.Type.FLOAT));
531+
FieldDoc keywordAfter = new FieldDoc(0, Float.NaN, new Object[] { "A", 1.0f });
532+
exception = expectThrows(IllegalArgumentException.class, () -> {
533+
CollapsingTopDocsCollector.createKeyword("tag", keywordFieldType, keywordMultiSort, 10, keywordAfter);
534+
});
535+
assertEquals("The after parameter can only be used when the sort is based on the collapse field", exception.getMessage());
536+
}
537+
497538
public void testSearchAfterWithNumericCollapse() throws IOException {
498539
testSearchAfterCollapse(new NumericDVProducer(), true);
499540
}
@@ -509,20 +550,19 @@ private <T extends Comparable<T>> void testSearchAfterCollapse(CollapsingDocValu
509550
final Directory dir = newDirectory();
510551
final RandomIndexWriter w = new RandomIndexWriter(random(), dir);
511552

512-
// Create test documents
513553
for (int i = 0; i < numDocs; i++) {
514554
Document doc = new Document();
515555
T groupValue = dvProducer.randomGroup(maxGroup);
516556
dvProducer.add(doc, groupValue, false);
517-
doc.add(new NumericDocValuesField("sort1", i)); // Ascending sort values
518557
w.addDocument(doc);
519558
}
520559

521560
final IndexReader reader = w.getReader();
522561
final IndexSearcher searcher = newSearcher(reader);
523562

524563
SortField collapseField = dvProducer.sortField(false);
525-
Sort sort = new Sort(new SortField("sort1", SortField.Type.INT), collapseField);
564+
// Use collapse field as sort field to satisfy validation
565+
Sort sort = new Sort(collapseField);
526566
MappedFieldType fieldType = new MockFieldMapper.FakeFieldType(collapseField.getField());
527567

528568
// First search without search_after
@@ -546,13 +586,6 @@ private <T extends Comparable<T>> void testSearchAfterCollapse(CollapsingDocValu
546586
searcher.search(new MatchAllDocsQuery(), collector2);
547587
CollapseTopFieldDocs results2 = collector2.getTopDocs();
548588

549-
// Verify search_after works correctly
550-
if (results2.scoreDocs.length > 0) {
551-
FieldDoc firstAfterDoc = (FieldDoc) results2.scoreDocs[0];
552-
// First doc in second page should be after the last doc in first page
553-
assertTrue("Search after should skip previous results", compareFieldDocs(after, firstAfterDoc, sort) < 0);
554-
}
555-
556589
// Verify no overlap between pages
557590
Set<Integer> firstPageDocs = new HashSet<>();
558591
for (ScoreDoc doc : results1.scoreDocs) {
@@ -572,16 +605,18 @@ public void testSearchAfterWithEmptyResults() throws IOException {
572605
final Directory dir = newDirectory();
573606
final RandomIndexWriter w = new RandomIndexWriter(random(), dir);
574607

575-
// Add a single document
576-
Document doc = new Document();
577-
doc.add(new NumericDocValuesField("group", 1));
578-
doc.add(new NumericDocValuesField("sort1", 100));
579-
w.addDocument(doc);
608+
final int numDocs = 100;
609+
for (int i = 0; i < numDocs; i++) {
610+
Document doc = new Document();
611+
doc.add(new NumericDocValuesField("group", i));
612+
w.addDocument(doc);
613+
}
580614

581615
final IndexReader reader = w.getReader();
582616
final IndexSearcher searcher = newSearcher(reader);
583617

584-
Sort sort = new Sort(new SortField("sort1", SortField.Type.INT));
618+
// Use collapse field as sort field to satisfy validation
619+
Sort sort = new Sort(new SortField("group", SortField.Type.INT));
585620
MappedFieldType fieldType = new MockFieldMapper.FakeFieldType("group");
586621

587622
// Create search_after that's beyond all documents
@@ -593,28 +628,13 @@ public void testSearchAfterWithEmptyResults() throws IOException {
593628
CollapseTopFieldDocs results = collector.getTopDocs();
594629

595630
assertEquals("Should have no results after last document", 0, results.scoreDocs.length);
596-
assertEquals("Total hits should reflect all documents processed with search_after", 1, results.totalHits.value());
631+
assertEquals("Total hits should reflect all documents processed with search_after", numDocs, results.totalHits.value());
597632

598633
w.close();
599634
reader.close();
600635
dir.close();
601636
}
602637

603-
private int compareFieldDocs(FieldDoc doc1, FieldDoc doc2, Sort sort) {
604-
SortField[] fields = sort.getSort();
605-
for (int i = 0; i < fields.length && i < doc1.fields.length && i < doc2.fields.length; i++) {
606-
@SuppressWarnings("unchecked")
607-
Comparable<Object> val1 = (Comparable<Object>) doc1.fields[i];
608-
Object val2 = doc2.fields[i];
609-
610-
int cmp = val1.compareTo(val2);
611-
if (cmp != 0) {
612-
return fields[i].getReverse() ? -cmp : cmp;
613-
}
614-
}
615-
return Integer.compare(doc1.doc, doc2.doc);
616-
}
617-
618638
// Helper classes for test data
619639
private static class NumericDVProducer implements CollapsingDocValuesProducer<Long> {
620640
@Override

0 commit comments

Comments
 (0)