3535
3636import java .io .IOException ;
3737import java .util .ArrayList ;
38- import java .util .Collection ;
3938import java .util .Collections ;
4039import java .util .HashMap ;
41- import java .util .HashSet ;
4240import java .util .Iterator ;
4341import java .util .LinkedList ;
4442import java .util .List ;
4543import java .util .Map ;
4644import java .util .Optional ;
47- import java .util .Set ;
4845import java .util .function .Consumer ;
4946
5047import static org .elasticsearch .index .mapper .vectors .DenseVectorFieldMapper .MAX_DIMS_COUNT ;
@@ -148,9 +145,6 @@ private void internalParseDocument(MetadataFieldMapper[] metadataFieldsMappers,
148145
149146 executeIndexTimeScripts (context );
150147
151- // Record additional entries for {@link IgnoredSourceFieldMapper} before calling #postParse, so that they get stored.
152- addIgnoredSourceMissingValues (context );
153-
154148 for (MetadataFieldMapper metadataMapper : metadataFieldsMappers ) {
155149 metadataMapper .postParse (context );
156150 }
@@ -159,128 +153,6 @@ private void internalParseDocument(MetadataFieldMapper[] metadataFieldsMappers,
159153 }
160154 }
161155
162- private void addIgnoredSourceMissingValues (DocumentParserContext context ) throws IOException {
163- Collection <IgnoredSourceFieldMapper .NameValue > ignoredFieldsMissingValues = context .getIgnoredFieldsMissingValues ();
164- if (ignoredFieldsMissingValues .isEmpty ()) {
165- return ;
166- }
167-
168- // Clean up any conflicting ignored values, to avoid double-printing them as array elements in synthetic source.
169- Map <String , IgnoredSourceFieldMapper .NameValue > fields = new HashMap <>(ignoredFieldsMissingValues .size ());
170- for (var field : ignoredFieldsMissingValues ) {
171- fields .put (field .name (), field );
172- }
173- context .deduplicateIgnoredFieldValues (fields .keySet ());
174-
175- assert context .mappingLookup ().isSourceSynthetic ();
176- try (
177- XContentParser parser = XContentHelper .createParser (
178- parserConfiguration ,
179- context .sourceToParse ().source (),
180- context .sourceToParse ().getXContentType ()
181- )
182- ) {
183- DocumentParserContext newContext = new RootDocumentParserContext (
184- context .mappingLookup (),
185- mappingParserContext ,
186- context .sourceToParse (),
187- parser
188- );
189- var nameValues = parseDocForMissingValues (newContext , fields );
190- for (var nameValue : nameValues ) {
191- context .addIgnoredField (nameValue );
192- }
193- }
194- }
195-
196- /**
197- * Simplified parsing version for retrieving the source of a given set of fields.
198- */
199- private static List <IgnoredSourceFieldMapper .NameValue > parseDocForMissingValues (
200- DocumentParserContext context ,
201- Map <String , IgnoredSourceFieldMapper .NameValue > fields
202- ) throws IOException {
203- // Generate all possible parent names for the given fields.
204- // This is used to skip processing objects that can't generate missing values.
205- Set <String > parentNames = getPossibleParentNames (fields .keySet ());
206- List <IgnoredSourceFieldMapper .NameValue > result = new ArrayList <>();
207-
208- XContentParser parser = context .parser ();
209- XContentParser .Token currentToken = parser .nextToken ();
210- List <String > path = new ArrayList <>();
211- List <Boolean > isObjectInPath = new ArrayList <>(); // Tracks if path components correspond to an object or an array.
212- String fieldName = null ;
213- while (currentToken != null ) {
214- while (currentToken != XContentParser .Token .FIELD_NAME ) {
215- if (fieldName != null
216- && (currentToken == XContentParser .Token .START_OBJECT || currentToken == XContentParser .Token .START_ARRAY )) {
217- if (parentNames .contains (getCurrentPath (path , fieldName )) == false ) {
218- // No missing value under this parsing subtree, skip it.
219- parser .skipChildren ();
220- } else {
221- path .add (fieldName );
222- isObjectInPath .add (currentToken == XContentParser .Token .START_OBJECT );
223- }
224- fieldName = null ;
225- } else if (currentToken == XContentParser .Token .END_OBJECT || currentToken == XContentParser .Token .END_ARRAY ) {
226- // Remove the path, if the scope type matches the one when the path was added.
227- if (isObjectInPath .isEmpty () == false
228- && (isObjectInPath .get (isObjectInPath .size () - 1 ) && currentToken == XContentParser .Token .END_OBJECT
229- || isObjectInPath .get (isObjectInPath .size () - 1 ) == false && currentToken == XContentParser .Token .END_ARRAY )) {
230- path .remove (path .size () - 1 );
231- isObjectInPath .remove (isObjectInPath .size () - 1 );
232- }
233- fieldName = null ;
234- }
235- currentToken = parser .nextToken ();
236- if (currentToken == null ) {
237- return result ;
238- }
239- }
240- fieldName = parser .currentName ();
241- String fullName = getCurrentPath (path , fieldName );
242- var leaf = fields .get (fullName ); // There may be multiple matches for array elements, don't use #remove.
243- if (leaf != null ) {
244- parser .nextToken (); // Advance the parser to the value to be read.
245- result .add (leaf .cloneWithValue (context .encodeFlattenedToken ()));
246- fieldName = null ;
247- }
248- currentToken = parser .nextToken ();
249- }
250- return result ;
251- }
252-
253- private static String getCurrentPath (List <String > path , String fieldName ) {
254- assert fieldName != null ;
255- return path .isEmpty () ? fieldName : String .join ("." , path ) + "." + fieldName ;
256- }
257-
258- /**
259- * Generates all possible parent object names for the given full names.
260- * For instance, for input ['path.to.foo', 'another.path.to.bar'], it returns:
261- * [ 'path', 'path.to', 'another', 'another.path', 'another.path.to' ]
262- */
263- private static Set <String > getPossibleParentNames (Set <String > fullPaths ) {
264- if (fullPaths .isEmpty ()) {
265- return Collections .emptySet ();
266- }
267- Set <String > paths = new HashSet <>();
268- for (String fullPath : fullPaths ) {
269- String [] split = fullPath .split ("\\ ." );
270- if (split .length < 2 ) {
271- continue ;
272- }
273- StringBuilder builder = new StringBuilder (split [0 ]);
274- paths .add (builder .toString ());
275- for (int i = 1 ; i < split .length - 1 ; i ++) {
276- builder .append ("." );
277- builder .append (split [i ]);
278- paths .add (builder .toString ());
279- }
280- }
281- return paths ;
282- }
283-
284156 private static void executeIndexTimeScripts (DocumentParserContext context ) {
285157 List <FieldMapper > indexTimeScriptMappers = context .mappingLookup ().indexTimeScriptMappers ();
286158 if (indexTimeScriptMappers .isEmpty ()) {
@@ -426,7 +298,10 @@ static void parseObjectOrNested(DocumentParserContext context) throws IOExceptio
426298 throwOnConcreteValue (context .parent (), currentFieldName , context );
427299 }
428300
429- if (context .canAddIgnoredField () && getSourceKeepMode (context , context .parent ().sourceKeepMode ()) == Mapper .SourceKeepMode .ALL ) {
301+ var sourceKeepMode = getSourceKeepMode (context , context .parent ().sourceKeepMode ());
302+ if (context .canAddIgnoredField ()
303+ && (sourceKeepMode == Mapper .SourceKeepMode .ALL
304+ || (sourceKeepMode == Mapper .SourceKeepMode .ARRAYS && context .inArrayScope ()))) {
430305 context = context .addIgnoredFieldFromContext (
431306 new IgnoredSourceFieldMapper .NameValue (
432307 context .parent ().fullPath (),
@@ -571,9 +446,11 @@ static void parseObjectOrField(DocumentParserContext context, Mapper mapper) thr
571446 parseObjectOrNested (context .createFlattenContext (currentFieldName ));
572447 context .path ().add (currentFieldName );
573448 } else {
449+ var sourceKeepMode = getSourceKeepMode (context , fieldMapper .sourceKeepMode ());
574450 if (context .canAddIgnoredField ()
575451 && (fieldMapper .syntheticSourceMode () == FieldMapper .SyntheticSourceMode .FALLBACK
576- || getSourceKeepMode (context , fieldMapper .sourceKeepMode ()) == Mapper .SourceKeepMode .ALL
452+ || sourceKeepMode == Mapper .SourceKeepMode .ALL
453+ || (sourceKeepMode == Mapper .SourceKeepMode .ARRAYS && context .inArrayScope ())
577454 || (context .isWithinCopyTo () == false && context .isCopyToDestinationField (mapper .fullPath ())))) {
578455 context = context .addIgnoredFieldFromContext (
579456 IgnoredSourceFieldMapper .NameValue .fromContext (context , fieldMapper .fullPath (), null )
@@ -811,9 +688,7 @@ private static void parseNonDynamicArray(
811688 if (mapper instanceof ObjectMapper objectMapper ) {
812689 mode = getSourceKeepMode (context , objectMapper .sourceKeepMode ());
813690 objectWithFallbackSyntheticSource = mode == Mapper .SourceKeepMode .ALL
814- // Inside nested objects we always store object arrays as a workaround for #115261.
815- || ((context .inNestedScope () || mode == Mapper .SourceKeepMode .ARRAYS )
816- && objectMapper instanceof NestedObjectMapper == false );
691+ || (mode == Mapper .SourceKeepMode .ARRAYS && objectMapper instanceof NestedObjectMapper == false );
817692 }
818693 boolean fieldWithFallbackSyntheticSource = false ;
819694 boolean fieldWithStoredArraySource = false ;
0 commit comments