@@ -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