99
1010package  org .elasticsearch .action .bulk ;
1111
12+ import  org .apache .lucene .document .StringField ;
13+ import  org .apache .lucene .index .IndexableField ;
1214import  org .elasticsearch .action .ActionListener ;
1315import  org .elasticsearch .action .DocWriteRequest ;
1416import  org .elasticsearch .action .admin .indices .template .post .TransportSimulateIndexTemplateAction ;
3335import  org .elasticsearch .common .util .concurrent .AtomicArray ;
3436import  org .elasticsearch .common .xcontent .XContentHelper ;
3537import  org .elasticsearch .core .Nullable ;
38+ import  org .elasticsearch .core .Tuple ;
3639import  org .elasticsearch .features .NodeFeature ;
3740import  org .elasticsearch .index .IndexSettingProvider ;
3841import  org .elasticsearch .index .IndexSettingProviders ;
3942import  org .elasticsearch .index .IndexVersion ;
4043import  org .elasticsearch .index .IndexingPressure ;
4144import  org .elasticsearch .index .VersionType ;
4245import  org .elasticsearch .index .engine .Engine ;
46+ import  org .elasticsearch .index .mapper .IgnoredFieldMapper ;
47+ import  org .elasticsearch .index .mapper .LuceneDocument ;
4348import  org .elasticsearch .index .mapper .MapperService ;
4449import  org .elasticsearch .index .mapper .SourceToParse ;
4550import  org .elasticsearch .index .seqno .SequenceNumbers ;
6065import  org .elasticsearch .xcontent .XContentType ;
6166
6267import  java .io .IOException ;
68+ import  java .util .Collection ;
6369import  java .util .HashMap ;
6470import  java .util .List ;
6571import  java .util .Map ;
@@ -85,6 +91,7 @@ public class TransportSimulateBulkAction extends TransportAbstractBulkAction {
8591    public  static  final  NodeFeature  SIMULATE_INDEX_TEMPLATE_SUBSTITUTIONS  = new  NodeFeature ("simulate.index.template.substitutions" );
8692    public  static  final  NodeFeature  SIMULATE_MAPPING_ADDITION  = new  NodeFeature ("simulate.mapping.addition" );
8793    public  static  final  NodeFeature  SIMULATE_SUPPORT_NON_TEMPLATE_MAPPING  = new  NodeFeature ("simulate.support.non.template.mapping" );
94+     public  static  final  NodeFeature  SIMULATE_IGNORED_FIELDS  = new  NodeFeature ("simulate.ignored.fields" );
8895    private  final  IndicesService  indicesService ;
8996    private  final  NamedXContentRegistry  xContentRegistry ;
9097    private  final  Set <IndexSettingProvider > indexSettingProviders ;
@@ -137,12 +144,13 @@ protected void doInternalExecute(
137144            DocWriteRequest <?> docRequest  = bulkRequest .requests .get (i );
138145            assert  docRequest  instanceof  IndexRequest  : "TransportSimulateBulkAction should only ever be called with IndexRequests" ;
139146            IndexRequest  request  = (IndexRequest ) docRequest ;
140-             Exception   mappingValidationException  = validateMappings (
147+             Tuple < Collection < String >,  Exception >  validationResult  = validateMappings (
141148                componentTemplateSubstitutions ,
142149                indexTemplateSubstitutions ,
143150                mappingAddition ,
144151                request 
145152            );
153+             Exception  mappingValidationException  = validationResult .v2 ();
146154            responses .set (
147155                i ,
148156                BulkItemResponse .success (
@@ -155,6 +163,7 @@ protected void doInternalExecute(
155163                        request .source (),
156164                        request .getContentType (),
157165                        request .getExecutedPipelines (),
166+                         validationResult .v1 (),
158167                        mappingValidationException 
159168                    )
160169                )
@@ -168,11 +177,12 @@ protected void doInternalExecute(
168177    /** 
169178     * This creates a temporary index with the mappings of the index in the request, and then attempts to index the source from the request 
170179     * into it. If there is a mapping exception, that exception is returned. On success the returned exception is null. 
171-      * @parem  componentTemplateSubstitutions The component template definitions to use in place of existing ones for validation 
180+      * @param  componentTemplateSubstitutions The component template definitions to use in place of existing ones for validation 
172181     * @param request The IndexRequest whose source will be validated against the mapping (if it exists) of its index 
173-      * @return a mapping exception if the source does not match the mappings, otherwise null 
182+      * @return a Tuple containing: (1) in v1 the names of any fields that would be ignored upon indexing and (2) in v2 the mapping 
183+      * exception if the source does not match the mappings, otherwise null 
174184     */ 
175-     private  Exception  validateMappings (
185+     private  Tuple < Collection < String >,  Exception >  validateMappings (
176186        Map <String , ComponentTemplate > componentTemplateSubstitutions ,
177187        Map <String , ComposableIndexTemplate > indexTemplateSubstitutions ,
178188        Map <String , Object > mappingAddition ,
@@ -189,6 +199,7 @@ private Exception validateMappings(
189199
190200        ClusterState  state  = clusterService .state ();
191201        Exception  mappingValidationException  = null ;
202+         Collection <String > ignoredFields  = List .of ();
192203        IndexAbstraction  indexAbstraction  = state .metadata ().getIndicesLookup ().get (request .index ());
193204        try  {
194205            if  (indexAbstraction  != null 
@@ -275,7 +286,7 @@ private Exception validateMappings(
275286                    );
276287                    CompressedXContent  mappings  = template .mappings ();
277288                    CompressedXContent  mergedMappings  = mergeMappings (mappings , mappingAddition );
278-                     validateUpdatedMappings (mappings , mergedMappings , request , sourceToParse );
289+                     ignoredFields  =  validateUpdatedMappings (mappings , mergedMappings , request , sourceToParse );
279290                } else  {
280291                    List <IndexTemplateMetadata > matchingTemplates  = findV1Templates (simulatedState .metadata (), request .index (), false );
281292                    if  (matchingTemplates .isEmpty () == false ) {
@@ -289,7 +300,7 @@ private Exception validateMappings(
289300                            xContentRegistry 
290301                        );
291302                        final  CompressedXContent  combinedMappings  = mergeMappings (new  CompressedXContent (mappingsMap ), mappingAddition );
292-                         validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
303+                         ignoredFields  =  validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
293304                    } else  if  (indexAbstraction  != null  && mappingAddition .isEmpty () == false ) {
294305                        /* 
295306                         * The index matched no templates of any kind, including the substitutions. But it might have a mapping. So we 
@@ -298,35 +309,36 @@ private Exception validateMappings(
298309                        MappingMetadata  mappingFromIndex  = clusterService .state ().metadata ().index (indexAbstraction .getName ()).mapping ();
299310                        CompressedXContent  currentIndexCompressedXContent  = mappingFromIndex  == null  ? null  : mappingFromIndex .source ();
300311                        CompressedXContent  combinedMappings  = mergeMappings (currentIndexCompressedXContent , mappingAddition );
301-                         validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
312+                         ignoredFields  =  validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
302313                    } else  {
303314                        /* 
304315                         * The index matched no templates and had no mapping of its own. If there were component template substitutions 
305316                         * or index template substitutions, they didn't match anything. So just apply the mapping addition if it exists, 
306317                         * and validate. 
307318                         */ 
308319                        final  CompressedXContent  combinedMappings  = mergeMappings (null , mappingAddition );
309-                         validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
320+                         ignoredFields  =  validateUpdatedMappings (null , combinedMappings , request , sourceToParse );
310321                    }
311322                }
312323            }
313324        } catch  (Exception  e ) {
314325            mappingValidationException  = e ;
315326        }
316-         return  mappingValidationException ;
327+         return  Tuple . tuple ( ignoredFields ,  mappingValidationException ) ;
317328    }
318329
319330    /* 
320-      * Validates that when updatedMappings are applied 
331+      * Validates that when updatedMappings are applied. If any fields would be ignored while indexing, then those field names are returned. 
332+      * Otherwise the returned Collection is empty. 
321333     */ 
322-     private  void  validateUpdatedMappings (
334+     private  Collection < String >  validateUpdatedMappings (
323335        @ Nullable  CompressedXContent  originalMappings ,
324336        @ Nullable  CompressedXContent  updatedMappings ,
325337        IndexRequest  request ,
326338        SourceToParse  sourceToParse 
327339    ) throws  IOException  {
328340        if  (updatedMappings  == null ) {
329-             return ; // no validation to do 
341+             return   List . of () ; // no validation to do 
330342        }
331343        Settings  dummySettings  = Settings .builder ()
332344            .put (IndexMetadata .SETTING_VERSION_CREATED , IndexVersion .current ())
@@ -343,7 +355,7 @@ private void validateUpdatedMappings(
343355            .settings (dummySettings )
344356            .putMapping (new  MappingMetadata (updatedMappings ))
345357            .build ();
346-         indicesService .withTempIndexService (originalIndexMetadata , indexService  -> {
358+         Engine . Index   result  =  indicesService .withTempIndexService (originalIndexMetadata , indexService  -> {
347359            indexService .mapperService ().merge (updatedIndexMetadata , MapperService .MergeReason .MAPPING_UPDATE );
348360            return  IndexShard .prepareIndex (
349361                indexService .mapperService (),
@@ -360,6 +372,24 @@ private void validateUpdatedMappings(
360372                0 
361373            );
362374        });
375+         final  Collection <String > ignoredFields ;
376+         if  (result  == null ) {
377+             ignoredFields  = List .of ();
378+         } else  {
379+             List <LuceneDocument > luceneDocuments  = result .parsedDoc ().docs ();
380+             assert  luceneDocuments  == null  || luceneDocuments .size () == 1  : "Expected a single lucene document from index attempt" ;
381+             if  (luceneDocuments  != null  && luceneDocuments .size () == 1 ) {
382+                 ignoredFields  = luceneDocuments .get (0 )
383+                     .getFields ()
384+                     .stream ()
385+                     .filter (field  -> field .name ().equals (IgnoredFieldMapper .NAME ) && field  instanceof  StringField )
386+                     .map (IndexableField ::stringValue )
387+                     .toList ();
388+             } else  {
389+                 ignoredFields  = List .of ();
390+             }
391+         }
392+         return  ignoredFields ;
363393    }
364394
365395    private  static  CompressedXContent  mergeMappings (@ Nullable  CompressedXContent  originalMapping , Map <String , Object > mappingAddition )
0 commit comments