1414import org .apache .lucene .document .Field ;
1515import org .apache .lucene .document .FieldType ;
1616import org .apache .lucene .document .StoredField ;
17+ import org .apache .lucene .index .BinaryDocValues ;
18+ import org .apache .lucene .index .DocValues ;
1719import org .apache .lucene .index .IndexOptions ;
1820import org .apache .lucene .index .LeafReaderContext ;
1921import org .apache .lucene .index .Term ;
3032import org .apache .lucene .util .BytesRef ;
3133import org .apache .lucene .util .IOFunction ;
3234import org .elasticsearch .common .CheckedIntFunction ;
35+ import org .elasticsearch .common .io .stream .ByteArrayStreamInput ;
3336import org .elasticsearch .common .lucene .Lucene ;
3437import org .elasticsearch .common .text .UTF8DecodingReader ;
3538import org .elasticsearch .common .unit .Fuzziness ;
3942import org .elasticsearch .index .analysis .NamedAnalyzer ;
4043import org .elasticsearch .index .fielddata .FieldDataContext ;
4144import org .elasticsearch .index .fielddata .IndexFieldData ;
45+ import org .elasticsearch .index .fielddata .SortedBinaryDocValues ;
4246import org .elasticsearch .index .fielddata .SourceValueFetcherSortedBinaryIndexFieldData ;
4347import org .elasticsearch .index .fielddata .StoredFieldSortedBinaryIndexFieldData ;
4448import org .elasticsearch .index .fieldvisitor .StoredFieldLoader ;
@@ -297,12 +301,17 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
297301
298302 if (parent instanceof KeywordFieldMapper .KeywordFieldType keywordParent
299303 && keywordParent .ignoreAbove ().valuesPotentiallyIgnored ()) {
300- final String parentFallbackFieldName = keywordParent .syntheticSourceFallbackFieldName ();
301304 if (parent .isStored ()) {
302- return storedFieldFetcher (parentFieldName , parentFallbackFieldName );
305+ return combineFieldFetchers (
306+ storedFieldFetcher (parentFieldName ),
307+ ignoredValuesDocValuesFieldFetcher (keywordParent .syntheticSourceFallbackFieldName ())
308+ );
303309 } else if (parent .hasDocValues ()) {
304310 var ifd = searchExecutionContext .getForField (parent , MappedFieldType .FielddataOperation .SEARCH );
305- return combineFieldFetchers (docValuesFieldFetcher (ifd ), storedFieldFetcher (parentFallbackFieldName ));
311+ return combineFieldFetchers (
312+ docValuesFieldFetcher (ifd ),
313+ ignoredValuesDocValuesFieldFetcher (keywordParent .syntheticSourceFallbackFieldName ())
314+ );
306315 }
307316 }
308317
@@ -325,22 +334,16 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
325334 final KeywordFieldMapper .KeywordFieldType keywordDelegate
326335 ) {
327336 if (keywordDelegate .ignoreAbove ().valuesPotentiallyIgnored ()) {
328- // because we don't know whether the delegate field will be ignored during parsing, we must also check the current field
329- String fieldName = name ();
330- String fallbackName = syntheticSourceFallbackFieldName ();
331-
332- // delegate field names
333337 String delegateFieldName = keywordDelegate .name ();
334- String delegateFieldFallbackName = keywordDelegate .syntheticSourceFallbackFieldName ();
338+ // bc we don't know whether the delegate will ignore a value, we must also check the fallback field created by this
339+ // match_only_text field
340+ String fallbackName = syntheticSourceFallbackFieldName ();
335341
336342 if (keywordDelegate .isStored ()) {
337- return storedFieldFetcher (delegateFieldName , delegateFieldFallbackName , fieldName , fallbackName );
343+ return storedFieldFetcher (delegateFieldName , fallbackName );
338344 } else if (keywordDelegate .hasDocValues ()) {
339345 var ifd = searchExecutionContext .getForField (keywordDelegate , MappedFieldType .FielddataOperation .SEARCH );
340- return combineFieldFetchers (
341- docValuesFieldFetcher (ifd ),
342- storedFieldFetcher (delegateFieldFallbackName , fieldName , fallbackName )
343- );
346+ return combineFieldFetchers (docValuesFieldFetcher (ifd ), storedFieldFetcher (fallbackName ));
344347 }
345348 }
346349
@@ -355,25 +358,34 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
355358 }
356359 }
357360
358- private static IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> docValuesFieldFetcher (
359- IndexFieldData <?> ifd
361+ private IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> docValuesFieldFetcher (IndexFieldData <?> ifd ) {
362+ return context -> {
363+ SortedBinaryDocValues indexedValuesDocValues = ifd .load (context ).getBytesValues ();
364+ return docId -> getValuesFromDocValues (indexedValuesDocValues , docId );
365+ };
366+ }
367+
368+ private IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> ignoredValuesDocValuesFieldFetcher (
369+ String fieldName
360370 ) {
361371 return context -> {
362- var sortedBinaryDocValues = ifd .load (context ).getBytesValues ();
363- return docId -> {
364- if (sortedBinaryDocValues .advanceExact (docId )) {
365- var values = new ArrayList <>(sortedBinaryDocValues .docValueCount ());
366- for (int i = 0 ; i < sortedBinaryDocValues .docValueCount (); i ++) {
367- values .add (sortedBinaryDocValues .nextValue ().utf8ToString ());
368- }
369- return values ;
370- } else {
371- return List .of ();
372- }
373- };
372+ CustomBinaryDocValues ignoredValuesDocValues = new CustomBinaryDocValues (DocValues .getBinary (context .reader (), fieldName ));
373+ return docId -> getValuesFromDocValues (ignoredValuesDocValues , docId );
374374 };
375375 }
376376
377+ private List <Object > getValuesFromDocValues (SortedBinaryDocValues docValues , int docId ) throws IOException {
378+ if (docValues .advanceExact (docId )) {
379+ var values = new ArrayList <>(docValues .docValueCount ());
380+ for (int i = 0 ; i < docValues .docValueCount (); i ++) {
381+ values .add (docValues .nextValue ().utf8ToString ());
382+ }
383+ return values ;
384+ } else {
385+ return List .of ();
386+ }
387+ }
388+
377389 private static IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> storedFieldFetcher (String ... names ) {
378390 var loader = StoredFieldLoader .create (false , Set .of (names ));
379391 return context -> {
@@ -779,4 +791,46 @@ protected void writeValue(Object value, XContentBuilder b) throws IOException {
779791
780792 return fieldLoader ;
781793 }
794+
795+ /**
796+ * A wrapper around {@link BinaryDocValues} that exposes some quality of life functions. Note, these values are not sorted.
797+ */
798+ private static class CustomBinaryDocValues extends SortedBinaryDocValues {
799+
800+ private final BinaryDocValues binaryDocValues ;
801+ private final ByteArrayStreamInput stream ;
802+
803+ private int docValueCount = 0 ;
804+
805+ CustomBinaryDocValues (BinaryDocValues binaryDocValues ) {
806+ this .binaryDocValues = binaryDocValues ;
807+ this .stream = new ByteArrayStreamInput ();
808+ }
809+
810+ @ Override
811+ public BytesRef nextValue () throws IOException {
812+ // this function already knows how to decode the underlying bytes array, so no need to explicitly call VInt()
813+ return stream .readBytesRef ();
814+ }
815+
816+ @ Override
817+ public boolean advanceExact (int docId ) throws IOException {
818+ // if document has a value, read underlying bytes
819+ if (binaryDocValues .advanceExact (docId )) {
820+ BytesRef docValuesBytes = binaryDocValues .binaryValue ();
821+ stream .reset (docValuesBytes .bytes , docValuesBytes .offset , docValuesBytes .length );
822+ docValueCount = stream .readVInt ();
823+ return true ;
824+ }
825+
826+ // otherwise there is nothing to do
827+ docValueCount = 0 ;
828+ return false ;
829+ }
830+
831+ @ Override
832+ public int docValueCount () {
833+ return docValueCount ;
834+ }
835+ }
782836}
0 commit comments