@@ -545,4 +545,153 @@ public void testOnlyIncludeExcludePrefix() throws IOException {
545545 assertEquals (!expectedFilter [i ], longBitSet .get (i ));
546546 }
547547 }
548+
549+ /**
550+ * Test case for prefix filter when the prefix doesn't exist and would be inserted beyond all existing terms.
551+ * This validates the fix for the IndexOutOfBoundsException bug.
552+ */
553+ public void testPrefixFilterWithNonExistentPrefixBeyondRange () throws IOException {
554+ // Create a regex pattern that will trigger prefix optimization
555+ // The prefix "zzz" doesn't exist in our doc values and would be inserted after all existing terms
556+ IncludeExclude includeExclude = new IncludeExclude ("zzz.*" , null );
557+
558+ OrdinalsFilter ordinalsFilter = includeExclude .convertToOrdinalsFilter (DocValueFormat .RAW );
559+
560+ // Create doc values with terms that all come before "zzz" alphabetically
561+ BytesRef [] bytesRefs = toBytesRefArray ("aaa" , "bbb" , "ccc" );
562+
563+ SortedSetDocValues sortedSetDocValues = new AbstractSortedSetDocValues () {
564+ @ Override
565+ public boolean advanceExact (int target ) {
566+ return false ;
567+ }
568+
569+ @ Override
570+ public long nextOrd () {
571+ return 0 ;
572+ }
573+
574+ @ Override
575+ public int docValueCount () {
576+ return 1 ;
577+ }
578+
579+ @ Override
580+ public BytesRef lookupOrd (long ord ) {
581+ if (ord < 0 || ord >= bytesRefs .length ) {
582+ throw new IndexOutOfBoundsException ("ord=" + ord + " is out of bounds [0," + bytesRefs .length + ")" );
583+ }
584+ int ordIndex = Math .toIntExact (ord );
585+ return bytesRefs [ordIndex ];
586+ }
587+
588+ @ Override
589+ public long getValueCount () {
590+ return bytesRefs .length ;
591+ }
592+ };
593+
594+ // This should not throw IndexOutOfBoundsException after the fix
595+ LongBitSet acceptedOrds = ordinalsFilter .acceptedGlobalOrdinals (sortedSetDocValues );
596+
597+ // Since "zzz" doesn't exist in the doc values, no ordinals should be accepted
598+ assertEquals (bytesRefs .length , acceptedOrds .length ());
599+ for (int i = 0 ; i < bytesRefs .length ; i ++) {
600+ assertFalse ("Ordinal " + i + " should not be accepted" , acceptedOrds .get (i ));
601+ }
602+ }
603+
604+ /**
605+ * Test case for prefix filter with exclude pattern when the prefix doesn't exist.
606+ */
607+ public void testPrefixFilterWithNonExistentExcludePrefixBeyondRange () throws IOException {
608+ // Test with an exclude pattern where the prefix doesn't exist
609+ IncludeExclude includeExclude = new IncludeExclude (null , "zzz.*" );
610+
611+ OrdinalsFilter ordinalsFilter = includeExclude .convertToOrdinalsFilter (DocValueFormat .RAW );
612+
613+ BytesRef [] bytesRefs = toBytesRefArray ("aaa" , "bbb" , "ccc" );
614+
615+ SortedSetDocValues sortedSetDocValues = new AbstractSortedSetDocValues () {
616+ @ Override
617+ public boolean advanceExact (int target ) {
618+ return false ;
619+ }
620+
621+ @ Override
622+ public long nextOrd () {
623+ return 0 ;
624+ }
625+
626+ @ Override
627+ public int docValueCount () {
628+ return 1 ;
629+ }
630+
631+ @ Override
632+ public BytesRef lookupOrd (long ord ) {
633+ if (ord < 0 || ord >= bytesRefs .length ) {
634+ throw new IndexOutOfBoundsException ("ord=" + ord + " is out of bounds [0," + bytesRefs .length + ")" );
635+ }
636+ int ordIndex = Math .toIntExact (ord );
637+ return bytesRefs [ordIndex ];
638+ }
639+
640+ @ Override
641+ public long getValueCount () {
642+ return bytesRefs .length ;
643+ }
644+ };
645+
646+ // This should not throw IndexOutOfBoundsException after the fix
647+ LongBitSet acceptedOrds = ordinalsFilter .acceptedGlobalOrdinals (sortedSetDocValues );
648+
649+ // Since "zzz" doesn't exist and we're excluding it, all ordinals should be accepted
650+ assertEquals (bytesRefs .length , acceptedOrds .length ());
651+ for (int i = 0 ; i < bytesRefs .length ; i ++) {
652+ assertTrue ("Ordinal " + i + " should be accepted" , acceptedOrds .get (i ));
653+ }
654+ }
655+
656+ /**
657+ * Test case for prefix filter when the prefix exists before all terms.
658+ */
659+ public void testPrefixFilterWithNonExistentPrefixBeforeRange () throws IOException {
660+ // Test with a prefix that would be inserted before all existing terms
661+ IncludeExclude includeExclude = new IncludeExclude ("aaa.*" , null );
662+
663+ OrdinalsFilter ordinalsFilter = includeExclude .convertToOrdinalsFilter (DocValueFormat .RAW );
664+
665+ // Create doc values where "aaa" would be at the beginning but doesn't exist
666+ BytesRef [] bytesRefs = toBytesRefArray ("bbb" , "ccc" , "ddd" );
667+
668+ LongBitSet acceptedOrds = ordinalsFilter .acceptedGlobalOrdinals (toDocValues (bytesRefs ));
669+
670+ // No ordinals should be accepted since "aaa" doesn't exist
671+ assertEquals (bytesRefs .length , acceptedOrds .length ());
672+ for (int i = 0 ; i < bytesRefs .length ; i ++) {
673+ assertFalse ("Ordinal " + i + " should not be accepted" , acceptedOrds .get (i ));
674+ }
675+ }
676+
677+ /**
678+ * Test case for prefix filter when the prefix matches some terms.
679+ */
680+ public void testPrefixFilterWithMatchingPrefix () throws IOException {
681+ // Test with a prefix that matches some terms
682+ IncludeExclude includeExclude = new IncludeExclude ("aa.*" , null );
683+
684+ OrdinalsFilter ordinalsFilter = includeExclude .convertToOrdinalsFilter (DocValueFormat .RAW );
685+
686+ BytesRef [] bytesRefs = toBytesRefArray ("aaa" , "aab" , "bbb" , "ccc" );
687+
688+ LongBitSet acceptedOrds = ordinalsFilter .acceptedGlobalOrdinals (toDocValues (bytesRefs ));
689+
690+ // Only the first two ordinals should be accepted (matching "aa" prefix)
691+ assertEquals (bytesRefs .length , acceptedOrds .length ());
692+ assertTrue ("Ordinal 0 (aaa) should be accepted" , acceptedOrds .get (0 ));
693+ assertTrue ("Ordinal 1 (aab) should be accepted" , acceptedOrds .get (1 ));
694+ assertFalse ("Ordinal 2 (bbb) should not be accepted" , acceptedOrds .get (2 ));
695+ assertFalse ("Ordinal 3 (ccc) should not be accepted" , acceptedOrds .get (3 ));
696+ }
548697}
0 commit comments