3636import org .apache .lucene .util .automaton .CharacterRunAutomaton ;
3737import org .elasticsearch .common .bytes .BytesArray ;
3838import org .elasticsearch .common .bytes .BytesReference ;
39+ import org .elasticsearch .common .io .stream .ByteArrayStreamInput ;
40+ import org .elasticsearch .common .io .stream .BytesStreamOutput ;
3941import org .elasticsearch .common .logging .LoggerMessageFormat ;
4042import org .elasticsearch .common .lucene .index .SequentialStoredFieldsLeafReader ;
4143import org .elasticsearch .common .xcontent .XContentHelper ;
4244import org .elasticsearch .core .Tuple ;
4345import org .elasticsearch .index .mapper .FieldNamesFieldMapper ;
4446import org .elasticsearch .index .mapper .IgnoreMalformedStoredValues ;
4547import org .elasticsearch .index .mapper .IgnoredSourceFieldMapper ;
48+ import org .elasticsearch .index .mapper .MultiValuedBinaryDocValuesField ;
4649import org .elasticsearch .index .mapper .SourceFieldMapper ;
4750import org .elasticsearch .index .mapper .TextFamilyFieldType ;
4851import org .elasticsearch .transport .Transports ;
@@ -306,7 +309,19 @@ public NumericDocValues getNumericDocValues(String field) throws IOException {
306309
307310 @ Override
308311 public BinaryDocValues getBinaryDocValues (String field ) throws IOException {
309- return hasField (field ) ? super .getBinaryDocValues (field ) : null ;
312+ if (hasField (field ) == false ) {
313+ return null ;
314+ }
315+ BinaryDocValues dv = super .getBinaryDocValues (field );
316+ if (dv != null
317+ && IgnoredSourceFieldMapper .NAME .equals (field )
318+ && ignoredSourceFormat == IgnoredSourceFieldMapper .IgnoredSourceFormat .DOC_VALUES_IGNORED_SOURCE ) {
319+ NumericDocValues originalCounts = super .getNumericDocValues (
320+ field + MultiValuedBinaryDocValuesField .SeparateCount .COUNT_FIELD_SUFFIX
321+ );
322+ return new FilteredIgnoredSourceDocValues (dv , originalCounts );
323+ }
324+ return dv ;
310325 }
311326
312327 @ Override
@@ -321,74 +336,88 @@ public SortedNumericDocValues getSortedNumericDocValues(String field) throws IOE
321336
322337 @ Override
323338 public SortedSetDocValues getSortedSetDocValues (String field ) throws IOException {
324- if (hasField (field ) == false ) {
325- return null ;
326- }
327- SortedSetDocValues dv = super .getSortedSetDocValues (field );
328- if (dv != null
329- && IgnoredSourceFieldMapper .NAME .equals (field )
330- && ignoredSourceFormat == IgnoredSourceFieldMapper .IgnoredSourceFormat .DOC_VALUES_IGNORED_SOURCE ) {
331- return new FilteredIgnoredSourceDocValues (dv );
332- }
333- return dv ;
339+ return hasField (field ) ? super .getSortedSetDocValues (field ) : null ;
334340 }
335341
336342 /**
337- * Wraps {@link SortedSetDocValues} for the {@code _ignored_source} field to apply field-level security filtering.
338- * Per-document values are decoded, filtered through the DLS field automaton, and re-encoded so that callers
339- * only observe field values the current user is authorised to see.
343+ * Wraps {@link BinaryDocValues} for the {@code _ignored_source} field to apply field-level security filtering.
344+ * Per-document values are decoded from the {@link MultiValuedBinaryDocValuesField.SeparateCount} multi-value
345+ * binary format, filtered through the DLS field automaton, and re-encoded in the
346+ * {@link MultiValuedBinaryDocValuesField.IntegratedCount} format so that callers only observe field values
347+ * the current user is authorised to see. Returning the integrated-count format allows the caller to treat
348+ * the counts numeric field as absent, avoiding stale count mismatches after filtering.
340349 */
341- private class FilteredIgnoredSourceDocValues extends SortedSetDocValues {
342- private final SortedSetDocValues in ;
343- private List < BytesRef > filteredValues ;
344- private int pos ;
350+ private class FilteredIgnoredSourceDocValues extends BinaryDocValues {
351+ private final BinaryDocValues in ;
352+ private final NumericDocValues originalCounts ;
353+ private BytesRef filteredValue ;
345354
346- FilteredIgnoredSourceDocValues (SortedSetDocValues in ) {
355+ FilteredIgnoredSourceDocValues (BinaryDocValues in , NumericDocValues originalCounts ) {
347356 this .in = in ;
357+ this .originalCounts = originalCounts ;
348358 }
349359
350360 @ Override
351361 public boolean advanceExact (int target ) throws IOException {
362+ filteredValue = null ;
352363 if (in .advanceExact (target ) == false ) {
353364 return false ;
354365 }
355- filteredValues = new ArrayList <>();
356- int count = in .docValueCount ();
357- for (int i = 0 ; i < count ; i ++) {
358- long ord = in .nextOrd ();
359- BytesRef encoded = in .lookupOrd (ord );
360- BytesRef filtered = ignoredSourceFormat .filterValue (encoded , v -> filter (v , filter , 0 ));
361- if (filtered != null ) {
362- filteredValues .add (BytesRef .deepCopyOf (filtered ));
366+ BytesRef bytes = in .binaryValue ();
367+ List <BytesRef > filteredValues = new ArrayList <>();
368+ if (originalCounts != null ) {
369+ boolean countsAdvanced = originalCounts .advanceExact (target );
370+ assert countsAdvanced ;
371+ int count = Math .toIntExact (originalCounts .longValue ());
372+ if (count == 1 ) {
373+ BytesRef filtered = ignoredSourceFormat .filterValue (bytes , v -> filter (v , filter , 0 ));
374+ if (filtered != null ) {
375+ filteredValues .add (BytesRef .deepCopyOf (filtered ));
376+ }
377+ } else {
378+ ByteArrayStreamInput input = new ByteArrayStreamInput ();
379+ input .reset (bytes .bytes , bytes .offset , bytes .length );
380+ for (int i = 0 ; i < count ; i ++) {
381+ int len = input .readVInt ();
382+ BytesRef value = new BytesRef (bytes .bytes , input .getPosition (), len );
383+ input .setPosition (input .getPosition () + len );
384+ BytesRef filtered = ignoredSourceFormat .filterValue (value , v -> filter (v , filter , 0 ));
385+ if (filtered != null ) {
386+ filteredValues .add (BytesRef .deepCopyOf (filtered ));
387+ }
388+ }
389+ }
390+ } else {
391+ ByteArrayStreamInput input = new ByteArrayStreamInput ();
392+ input .reset (bytes .bytes , bytes .offset , bytes .length );
393+ int count = input .readVInt ();
394+ for (int i = 0 ; i < count ; i ++) {
395+ int len = input .readVInt ();
396+ BytesRef value = new BytesRef (bytes .bytes , input .getPosition (), len );
397+ input .setPosition (input .getPosition () + len );
398+ BytesRef filtered = ignoredSourceFormat .filterValue (value , v -> filter (v , filter , 0 ));
399+ if (filtered != null ) {
400+ filteredValues .add (BytesRef .deepCopyOf (filtered ));
401+ }
363402 }
364403 }
365- pos = -1 ;
366- return filteredValues .isEmpty () == false ;
367- }
368-
369- @ Override
370- public int docValueCount () {
371- return filteredValues == null ? 0 : filteredValues .size ();
372- }
373-
374- /**
375- * Returns a sequential local index (0-based) into {@link #filteredValues}, not a global ordinal.
376- * Callers must pass the returned value directly to {@link #lookupOrd(long)}.
377- */
378- @ Override
379- public long nextOrd () throws IOException {
380- return ++pos ;
381- }
382-
383- /** Returns the pre-filtered value at the given local index returned by {@link #nextOrd()}. */
384- @ Override
385- public BytesRef lookupOrd (long ord ) throws IOException {
386- return filteredValues .get ((int ) ord );
404+ if (filteredValues .isEmpty ()) {
405+ return false ;
406+ }
407+ try (BytesStreamOutput out = new BytesStreamOutput ()) {
408+ out .writeVInt (filteredValues .size ());
409+ for (BytesRef val : filteredValues ) {
410+ out .writeVInt (val .length );
411+ out .writeBytes (val .bytes , val .offset , val .length );
412+ }
413+ filteredValue = out .bytes ().toBytesRef ();
414+ }
415+ return true ;
387416 }
388417
389418 @ Override
390- public long getValueCount () {
391- return filteredValues == null ? 0 : filteredValues . size () ;
419+ public BytesRef binaryValue () {
420+ return filteredValue ;
392421 }
393422
394423 @ Override
0 commit comments