Skip to content

Commit 60c4019

Browse files
authored
Revert "Add mappings for enrich fields (#96056)" (#98683)
We implemented a mapping change to enrich indices to support enrich in ESQL. Unfortunately, that change does not interact well with object fields. While we are actively addressing this issue, a comprehensive solution will take some time. To mitigate the impact of this bug, this PR will revert the mapping change for 8.9 and 8.10. The reason is that enrich for ESQL isn't required until 8.11. Relates #98019
1 parent a60ab5c commit 60c4019

File tree

3 files changed

+291
-497
lines changed

3 files changed

+291
-497
lines changed

docs/changelog/98683.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 98683
2+
summary: Revert "Add mappings for enrich fields"
3+
area: Ingest Node
4+
type: bug
5+
issues: []

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPolicyRunner.java

Lines changed: 93 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@
3939
import org.elasticsearch.common.Strings;
4040
import org.elasticsearch.common.bytes.BytesArray;
4141
import org.elasticsearch.common.settings.Settings;
42-
import org.elasticsearch.common.util.Maps;
43-
import org.elasticsearch.common.util.iterable.Iterables;
42+
import org.elasticsearch.core.CheckedFunction;
4443
import org.elasticsearch.index.mapper.MapperService;
4544
import org.elasticsearch.index.query.QueryBuilders;
4645
import org.elasticsearch.index.reindex.BulkByScrollResponse;
4746
import org.elasticsearch.index.reindex.ReindexRequest;
4847
import org.elasticsearch.index.reindex.ScrollableHitSource;
4948
import org.elasticsearch.search.builder.SearchSourceBuilder;
5049
import org.elasticsearch.tasks.TaskCancelledException;
50+
import org.elasticsearch.xcontent.ObjectPath;
5151
import org.elasticsearch.xcontent.XContentBuilder;
5252
import org.elasticsearch.xcontent.XContentType;
5353
import org.elasticsearch.xcontent.json.JsonXContent;
@@ -57,7 +57,6 @@
5757

5858
import java.io.IOException;
5959
import java.io.UncheckedIOException;
60-
import java.util.HashMap;
6160
import java.util.HashSet;
6261
import java.util.List;
6362
import java.util.Map;
@@ -181,9 +180,9 @@ static void validateMappings(
181180
}
182181
// Validate the key and values
183182
try {
184-
validateAndGetMappingTypeAndFormat(mapping, policy.getMatchField(), true);
183+
validateField(mapping, policy.getMatchField(), true);
185184
for (String valueFieldName : policy.getEnrichFields()) {
186-
validateAndGetMappingTypeAndFormat(mapping, valueFieldName, false);
185+
validateField(mapping, valueFieldName, false);
187186
}
188187
} catch (ElasticsearchException e) {
189188
throw new ElasticsearchException(
@@ -195,64 +194,11 @@ static void validateMappings(
195194
}
196195
}
197196

198-
private record MappingTypeAndFormat(String type, String format) {
199-
200-
}
201-
202-
private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
203-
String fieldName,
204-
EnrichPolicy policy,
205-
boolean strictlyRequired,
206-
List<Map<String, Object>> sourceMappings
207-
) {
208-
var fieldMappings = sourceMappings.stream()
209-
.map(mapping -> validateAndGetMappingTypeAndFormat(mapping, fieldName, strictlyRequired))
210-
.filter(Objects::nonNull)
211-
.toList();
212-
Set<String> types = fieldMappings.stream().map(tf -> tf.type).collect(Collectors.toSet());
213-
if (types.size() > 1) {
214-
if (strictlyRequired) {
215-
throw new ElasticsearchException(
216-
"Multiple distinct mapping types for field '{}' - indices({}) types({})",
217-
fieldName,
218-
Strings.collectionToCommaDelimitedString(policy.getIndices()),
219-
Strings.collectionToCommaDelimitedString(types)
220-
);
221-
}
222-
return null;
223-
}
224-
if (types.isEmpty()) {
225-
return null;
226-
}
227-
Set<String> formats = fieldMappings.stream().map(tf -> tf.format).filter(Objects::nonNull).collect(Collectors.toSet());
228-
if (formats.size() > 1) {
229-
if (strictlyRequired) {
230-
throw new ElasticsearchException(
231-
"Multiple distinct formats specified for field '{}' - indices({}) format entries({})",
232-
policy.getMatchField(),
233-
Strings.collectionToCommaDelimitedString(policy.getIndices()),
234-
Strings.collectionToCommaDelimitedString(formats)
235-
);
236-
}
237-
return null;
238-
}
239-
return new MappingTypeAndFormat(Iterables.get(types, 0), formats.isEmpty() ? null : Iterables.get(formats, 0));
240-
}
241-
242-
@SuppressWarnings("unchecked")
243-
private static <T> T extractValues(Map<String, Object> properties, String path) {
244-
return (T) properties.get(path);
245-
}
246-
247-
private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
248-
Map<String, Object> properties,
249-
String fieldName,
250-
boolean fieldRequired
251-
) {
197+
private static void validateField(Map<?, ?> properties, String fieldName, boolean fieldRequired) {
252198
assert Strings.isEmpty(fieldName) == false : "Field name cannot be null or empty";
253199
String[] fieldParts = fieldName.split("\\.");
254200
StringBuilder parent = new StringBuilder();
255-
Map<String, Object> currentField = properties;
201+
Map<?, ?> currentField = properties;
256202
boolean onRoot = true;
257203
for (String fieldPart : fieldParts) {
258204
// Ensure that the current field is of object type only (not a nested type or a non compound field)
@@ -265,7 +211,7 @@ private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
265211
type
266212
);
267213
}
268-
Map<String, Object> currentProperties = extractValues(currentField, "properties");
214+
Map<?, ?> currentProperties = ((Map<?, ?>) currentField.get("properties"));
269215
if (currentProperties == null) {
270216
if (fieldRequired) {
271217
throw new ElasticsearchException(
@@ -274,10 +220,10 @@ private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
274220
onRoot ? "root" : parent.toString()
275221
);
276222
} else {
277-
return null;
223+
return;
278224
}
279225
}
280-
currentField = extractValues(currentProperties, fieldPart);
226+
currentField = ((Map<?, ?>) currentProperties.get(fieldPart));
281227
if (currentField == null) {
282228
if (fieldRequired) {
283229
throw new ElasticsearchException(
@@ -287,7 +233,7 @@ private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
287233
onRoot ? "root" : parent.toString()
288234
);
289235
} else {
290-
return null;
236+
return;
291237
}
292238
}
293239
if (onRoot) {
@@ -297,70 +243,95 @@ private static MappingTypeAndFormat validateAndGetMappingTypeAndFormat(
297243
}
298244
parent.append(fieldPart);
299245
}
300-
if (currentField == null) {
301-
return null;
246+
}
247+
248+
private XContentBuilder resolveEnrichMapping(final EnrichPolicy enrichPolicy, final List<Map<String, Object>> mappings) {
249+
if (EnrichPolicy.MATCH_TYPE.equals(enrichPolicy.getType())) {
250+
return createEnrichMappingBuilder((builder) -> builder.field("type", "keyword").field("doc_values", false));
251+
} else if (EnrichPolicy.RANGE_TYPE.equals(enrichPolicy.getType())) {
252+
return createRangeEnrichMappingBuilder(enrichPolicy, mappings);
253+
} else if (EnrichPolicy.GEO_MATCH_TYPE.equals(enrichPolicy.getType())) {
254+
return createEnrichMappingBuilder((builder) -> builder.field("type", "geo_shape"));
255+
} else {
256+
throw new ElasticsearchException("Unrecognized enrich policy type [{}]", enrichPolicy.getType());
302257
}
303-
final String type = (String) currentField.getOrDefault("type", "object");
304-
final String format = (String) currentField.get("format");
305-
return new MappingTypeAndFormat(type, format);
306258
}
307259

308-
static final Set<String> RANGE_TYPES = Set.of("integer_range", "float_range", "long_range", "double_range", "ip_range", "date_range");
260+
private XContentBuilder createRangeEnrichMappingBuilder(EnrichPolicy enrichPolicy, List<Map<String, Object>> mappings) {
261+
String matchFieldPath = "properties." + enrichPolicy.getMatchField().replace(".", ".properties.");
262+
List<Map<String, String>> matchFieldMappings = mappings.stream()
263+
.map(map -> ObjectPath.<Map<String, String>>eval(matchFieldPath, map))
264+
.filter(Objects::nonNull)
265+
.toList();
266+
267+
Set<String> types = matchFieldMappings.stream().map(map -> map.get("type")).collect(Collectors.toSet());
268+
if (types.size() == 1) {
269+
String type = types.iterator().next();
270+
if (type == null) {
271+
// when no type is defined in a field mapping then it is of type object:
272+
throw new ElasticsearchException(
273+
"Field '{}' has type [object] which doesn't appear to be a range type",
274+
enrichPolicy.getMatchField(),
275+
type
276+
);
277+
}
309278

310-
static Map<String, Object> mappingForMatchField(EnrichPolicy policy, List<Map<String, Object>> sourceMappings) {
311-
MappingTypeAndFormat typeAndFormat = validateAndGetMappingTypeAndFormat(policy.getMatchField(), policy, true, sourceMappings);
312-
if (typeAndFormat == null) {
313-
throw new ElasticsearchException(
314-
"Match field '{}' doesn't have a correct mapping type for policy type '{}'",
315-
policy.getMatchField(),
316-
policy.getType()
317-
);
318-
}
319-
return switch (policy.getType()) {
320-
case EnrichPolicy.MATCH_TYPE -> Map.of("type", "keyword", "doc_values", false);
321-
case EnrichPolicy.GEO_MATCH_TYPE -> Map.of("type", "geo_shape");
322-
case EnrichPolicy.RANGE_TYPE -> {
323-
if (RANGE_TYPES.contains(typeAndFormat.type) == false) {
279+
switch (type) {
280+
case "integer_range":
281+
case "float_range":
282+
case "long_range":
283+
case "double_range":
284+
case "ip_range":
285+
return createEnrichMappingBuilder((builder) -> builder.field("type", type).field("doc_values", false));
286+
287+
// date_range types mappings allow for the format to be specified, should be preserved in the created index
288+
case "date_range":
289+
Set<String> formatEntries = matchFieldMappings.stream().map(map -> map.get("format")).collect(Collectors.toSet());
290+
if (formatEntries.size() == 1) {
291+
return createEnrichMappingBuilder((builder) -> {
292+
builder.field("type", type).field("doc_values", false);
293+
String format = formatEntries.iterator().next();
294+
if (format != null) {
295+
builder.field("format", format);
296+
}
297+
return builder;
298+
});
299+
}
300+
if (formatEntries.isEmpty()) {
301+
// no format specify rely on default
302+
return createEnrichMappingBuilder((builder) -> builder.field("type", type).field("doc_values", false));
303+
}
324304
throw new ElasticsearchException(
325-
"Field '{}' has type [{}] which doesn't appear to be a range type",
326-
policy.getMatchField(),
327-
typeAndFormat.type
305+
"Multiple distinct date format specified for match field '{}' - indices({}) format entries({})",
306+
enrichPolicy.getMatchField(),
307+
Strings.collectionToCommaDelimitedString(enrichPolicy.getIndices()),
308+
(formatEntries.contains(null) ? "(DEFAULT), " : "") + Strings.collectionToCommaDelimitedString(formatEntries)
328309
);
329-
}
330-
Map<String, Object> mapping = Maps.newMapWithExpectedSize(3);
331-
mapping.put("type", typeAndFormat.type);
332-
mapping.put("doc_values", false);
333-
if (typeAndFormat.format != null) {
334-
mapping.put("format", typeAndFormat.format);
335-
}
336-
yield mapping;
337-
}
338-
default -> throw new ElasticsearchException("Unrecognized enrich policy type [{}]", policy.getType());
339-
};
340-
}
341310

342-
private XContentBuilder createEnrichMapping(List<Map<String, Object>> sourceMappings) {
343-
Map<String, Map<String, Object>> fieldMappings = new HashMap<>();
344-
Map<String, Object> mappingForMatchField = mappingForMatchField(policy, sourceMappings);
345-
for (String enrichField : policy.getEnrichFields()) {
346-
if (enrichField.equals(policy.getMatchField())) {
347-
mappingForMatchField = new HashMap<>(mappingForMatchField);
348-
mappingForMatchField.remove("doc_values"); // enable doc_values
349-
} else {
350-
var typeAndFormat = validateAndGetMappingTypeAndFormat(enrichField, policy, false, sourceMappings);
351-
if (typeAndFormat != null) {
352-
Map<String, Object> mapping = Maps.newMapWithExpectedSize(3);
353-
mapping.put("type", typeAndFormat.type);
354-
if (typeAndFormat.format != null) {
355-
mapping.put("format", typeAndFormat.format);
356-
}
357-
mapping.put("index", false); // disable index
358-
fieldMappings.put(enrichField, mapping);
359-
}
311+
default:
312+
throw new ElasticsearchException(
313+
"Field '{}' has type [{}] which doesn't appear to be a range type",
314+
enrichPolicy.getMatchField(),
315+
type
316+
);
360317
}
361318
}
362-
fieldMappings.put(policy.getMatchField(), mappingForMatchField);
319+
if (types.isEmpty()) {
320+
throw new ElasticsearchException(
321+
"No mapping type found for match field '{}' - indices({})",
322+
enrichPolicy.getMatchField(),
323+
Strings.collectionToCommaDelimitedString(enrichPolicy.getIndices())
324+
);
325+
}
326+
throw new ElasticsearchException(
327+
"Multiple distinct mapping types for match field '{}' - indices({}) types({})",
328+
enrichPolicy.getMatchField(),
329+
Strings.collectionToCommaDelimitedString(enrichPolicy.getIndices()),
330+
Strings.collectionToCommaDelimitedString(types)
331+
);
332+
}
363333

334+
private XContentBuilder createEnrichMappingBuilder(CheckedFunction<XContentBuilder, XContentBuilder, IOException> matchFieldMapping) {
364335
// Enable _source on enrich index. Explicitly mark key mapping type.
365336
try {
366337
XContentBuilder builder = JsonXContent.contentBuilder();
@@ -376,7 +347,9 @@ private XContentBuilder createEnrichMapping(List<Map<String, Object>> sourceMapp
376347
builder.endObject();
377348
builder.startObject("properties");
378349
{
379-
builder.mapContents(fieldMappings);
350+
builder.startObject(policy.getMatchField());
351+
matchFieldMapping.apply(builder);
352+
builder.endObject();
380353
}
381354
builder.endObject();
382355
builder.startObject("_meta");
@@ -407,7 +380,7 @@ private void prepareAndCreateEnrichIndex(List<Map<String, Object>> mappings) {
407380
.put("index.warmer.enabled", false)
408381
.build();
409382
CreateIndexRequest createEnrichIndexRequest = new CreateIndexRequest(enrichIndexName, enrichIndexSettings);
410-
createEnrichIndexRequest.mapping(createEnrichMapping(mappings));
383+
createEnrichIndexRequest.mapping(resolveEnrichMapping(policy, mappings));
411384
logger.debug("Policy [{}]: Creating new enrich index [{}]", policyName, enrichIndexName);
412385
enrichOriginClient().admin()
413386
.indices()

0 commit comments

Comments
 (0)