3838import org .elasticsearch .common .unit .Fuzziness ;
3939import org .elasticsearch .common .util .BigArrays ;
4040import org .elasticsearch .core .Nullable ;
41+ import org .elasticsearch .features .NodeFeature ;
4142import org .elasticsearch .index .analysis .NamedAnalyzer ;
4243import org .elasticsearch .index .fielddata .FieldData ;
4344import org .elasticsearch .index .fielddata .FieldDataContext ;
7374import org .elasticsearch .xcontent .XContentParser ;
7475
7576import java .io .IOException ;
77+ import java .util .ArrayList ;
7678import java .util .Collections ;
79+ import java .util .HashMap ;
7780import java .util .List ;
7881import java .util .Map ;
82+ import java .util .Set ;
7983import java .util .function .Function ;
8084
8185/**
105109 */
106110public final class FlattenedFieldMapper extends FieldMapper {
107111
112+ public static final NodeFeature IGNORE_ABOVE_SUPPORT = new NodeFeature ("flattened.ignore_above_support" );
113+
108114 public static final String CONTENT_TYPE = "flattened" ;
109115 public static final String KEYED_FIELD_SUFFIX = "._keyed" ;
110116 public static final String TIME_SERIES_DIMENSIONS_ARRAY_PARAM = "time_series_dimensions" ;
@@ -214,7 +220,8 @@ public FlattenedFieldMapper build(MapperBuilderContext context) {
214220 meta .get (),
215221 splitQueriesOnWhitespace .get (),
216222 eagerGlobalOrdinals .get (),
217- dimensions .get ()
223+ dimensions .get (),
224+ ignoreAbove .getValue ()
218225 );
219226 return new FlattenedFieldMapper (leafName (), ft , builderParams (this , context ), this );
220227 }
@@ -642,16 +649,18 @@ public static final class RootFlattenedFieldType extends StringFieldType impleme
642649 private final boolean eagerGlobalOrdinals ;
643650 private final List <String > dimensions ;
644651 private final boolean isDimension ;
652+ private final int ignoreAbove ;
645653
646654 public RootFlattenedFieldType (
647655 String name ,
648656 boolean indexed ,
649657 boolean hasDocValues ,
650658 Map <String , String > meta ,
651659 boolean splitQueriesOnWhitespace ,
652- boolean eagerGlobalOrdinals
660+ boolean eagerGlobalOrdinals ,
661+ int ignoreAbove
653662 ) {
654- this (name , indexed , hasDocValues , meta , splitQueriesOnWhitespace , eagerGlobalOrdinals , Collections .emptyList ());
663+ this (name , indexed , hasDocValues , meta , splitQueriesOnWhitespace , eagerGlobalOrdinals , Collections .emptyList (), ignoreAbove );
655664 }
656665
657666 public RootFlattenedFieldType (
@@ -661,7 +670,8 @@ public RootFlattenedFieldType(
661670 Map <String , String > meta ,
662671 boolean splitQueriesOnWhitespace ,
663672 boolean eagerGlobalOrdinals ,
664- List <String > dimensions
673+ List <String > dimensions ,
674+ int ignoreAbove
665675 ) {
666676 super (
667677 name ,
@@ -675,6 +685,7 @@ public RootFlattenedFieldType(
675685 this .eagerGlobalOrdinals = eagerGlobalOrdinals ;
676686 this .dimensions = dimensions ;
677687 this .isDimension = dimensions .isEmpty () == false ;
688+ this .ignoreAbove = ignoreAbove ;
678689 }
679690
680691 @ Override
@@ -708,7 +719,67 @@ public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext
708719
709720 @ Override
710721 public ValueFetcher valueFetcher (SearchExecutionContext context , String format ) {
711- return SourceValueFetcher .identity (name (), context , format );
722+ if (format != null ) {
723+ throw new IllegalArgumentException ("Field [" + name () + "] of type [" + typeName () + "] doesn't support formats." );
724+ }
725+ return sourceValueFetcher (context .isSourceEnabled () ? context .sourcePath (name ()) : Collections .emptySet ());
726+ }
727+
728+ private SourceValueFetcher sourceValueFetcher (Set <String > sourcePaths ) {
729+ return new SourceValueFetcher (sourcePaths , null ) {
730+ @ Override
731+ @ SuppressWarnings ("unchecked" )
732+ protected Object parseSourceValue (Object value ) {
733+ if (value instanceof Map <?, ?> valueAsMap && valueAsMap .isEmpty () == false ) {
734+ final Map <String , Object > result = filterIgnoredValues ((Map <String , Object >) valueAsMap );
735+ return result .isEmpty () ? null : result ;
736+ }
737+ if (value instanceof String valueAsString && valueAsString .length () <= ignoreAbove ) {
738+ return valueAsString ;
739+ }
740+ return null ;
741+ }
742+
743+ private Map <String , Object > filterIgnoredValues (final Map <String , Object > values ) {
744+ final Map <String , Object > result = new HashMap <>();
745+ for (final Map .Entry <String , Object > entry : values .entrySet ()) {
746+ Object value = filterIgnoredValues (entry .getValue ());
747+ if (value != null ) {
748+ result .put (entry .getKey (), value );
749+ }
750+ }
751+ return result ;
752+ }
753+
754+ private Object filterIgnoredValues (final Object entryValue ) {
755+ if (entryValue instanceof List <?> valueAsList ) {
756+ final List <Object > validValues = new ArrayList <>();
757+ for (Object value : valueAsList ) {
758+ if (value instanceof String valueAsString ) {
759+ if (valueAsString .length () <= ignoreAbove ) {
760+ validValues .add (valueAsString );
761+ }
762+ } else {
763+ validValues .add (value );
764+ }
765+ }
766+ if (validValues .isEmpty ()) {
767+ return null ;
768+ }
769+ if (validValues .size () == 1 ) {
770+ // NOTE: for single-value flattened fields do not return an array
771+ return validValues .get (0 );
772+ }
773+ return validValues ;
774+ } else if (entryValue instanceof String valueAsString ) {
775+ if (valueAsString .length () <= ignoreAbove ) {
776+ return valueAsString ;
777+ }
778+ return null ;
779+ }
780+ return entryValue ;
781+ }
782+ };
712783 }
713784
714785 @ Override
0 commit comments