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 .getFirst ()
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