Skip to content

Commit 51613ec

Browse files
authored
Use Explicit<> rather than Optional<> for Subobjects in ObjectMapper (#135014)
Using `Optional<>` here gets confusing when trying to work out what the default value is, and storing `Optional<>` as fields or using them as method parameters always feels wrong.
1 parent eb1945e commit 51613ec

File tree

13 files changed

+119
-117
lines changed

13 files changed

+119
-117
lines changed

server/src/main/java/org/elasticsearch/common/Explicit.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ public static Explicit<Boolean> explicitBoolean(boolean value) {
3535
return value ? EXPLICIT_TRUE : EXPLICIT_FALSE;
3636
}
3737

38+
/**
39+
* Create an explicitly set value
40+
*/
41+
public static <T> Explicit<T> of(T value) {
42+
return new Explicit<>(value, true);
43+
}
44+
45+
/**
46+
* Create an implicitly set value
47+
*/
48+
public static <T> Explicit<T> implicit(T value) {
49+
return new Explicit<>(value, false);
50+
}
51+
3852
/**
3953
* Create a value with an indication if this was an explicit choice
4054
* @param value a setting value

server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ protected SyntheticSourceSupport syntheticSourceSupport() {
10481048

10491049
private static class NoOpObjectMapper extends ObjectMapper {
10501050
NoOpObjectMapper(String name, String fullPath) {
1051-
super(name, fullPath, Explicit.IMPLICIT_TRUE, Optional.empty(), Optional.empty(), Dynamic.RUNTIME, Collections.emptyMap());
1051+
super(name, fullPath, Explicit.IMPLICIT_TRUE, Defaults.SUBOBJECTS, Optional.empty(), Dynamic.RUNTIME, Collections.emptyMap());
10521052
}
10531053

10541054
@Override

server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public Builder(
6060
Function<Query, BitSetProducer> bitSetProducer,
6161
IndexSettings indexSettings
6262
) {
63-
super(name, Optional.empty());
63+
super(name, Defaults.SUBOBJECTS);
6464
this.indexCreatedVersion = indexCreatedVersion;
6565
this.bitSetProducer = bitSetProducer;
6666
this.indexSettings = indexSettings;
@@ -145,7 +145,7 @@ public static class TypeParser extends ObjectMapper.TypeParser {
145145
@Override
146146
public Mapper.Builder parse(String name, Map<String, Object> node, MappingParserContext parserContext)
147147
throws MapperParsingException {
148-
if (parseSubobjects(node).isPresent()) {
148+
if (parseSubobjects(node).explicit()) {
149149
throw new MapperParsingException("Nested type [" + name + "] does not support [subobjects] parameter");
150150
}
151151
NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder(
@@ -236,7 +236,7 @@ public MapperBuilderContext createChildContext(String name, Dynamic dynamic) {
236236
Function<Query, BitSetProducer> bitsetProducer,
237237
IndexSettings indexSettings
238238
) {
239-
super(name, fullPath, enabled, Optional.empty(), sourceKeepMode, dynamic, mappers);
239+
super(name, fullPath, enabled, Defaults.SUBOBJECTS, sourceKeepMode, dynamic, mappers);
240240
this.parentTypeFilter = parentTypeFilter;
241241
this.nestedTypePath = nestedTypePath;
242242
this.nestedTypeFilter = nestedTypeFilter;

server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public String toString() {
9696

9797
public static class Defaults {
9898
public static final boolean ENABLED = true;
99-
public static final Optional<Subobjects> SUBOBJECTS = Optional.empty();
99+
public static final Explicit<Subobjects> SUBOBJECTS = Explicit.implicit(Subobjects.ENABLED);
100100
public static final Explicit<Boolean> STORE_ARRAY_SOURCE = Explicit.IMPLICIT_FALSE;
101101
public static final Dynamic DYNAMIC = Dynamic.TRUE;
102102
}
@@ -133,13 +133,17 @@ static Dynamic getRootDynamic(MappingLookup mappingLookup) {
133133
}
134134

135135
public static class Builder extends Mapper.Builder {
136-
protected Optional<Subobjects> subobjects;
136+
protected Explicit<Subobjects> subobjects;
137137
protected Explicit<Boolean> enabled = Explicit.IMPLICIT_TRUE;
138138
protected Optional<SourceKeepMode> sourceKeepMode = Optional.empty();
139139
protected Dynamic dynamic;
140140
protected final List<Mapper.Builder> mappersBuilders = new ArrayList<>();
141141

142-
public Builder(String name, Optional<Subobjects> subobjects) {
142+
public Builder(String name) {
143+
this(name, Defaults.SUBOBJECTS);
144+
}
145+
146+
public Builder(String name, Explicit<Subobjects> subobjects) {
143147
super(name);
144148
this.subobjects = subobjects;
145149
}
@@ -184,7 +188,7 @@ public Mapper build(MapperBuilderContext context) {
184188
public final void addDynamic(String name, String prefix, Mapper mapper, DocumentParserContext context) {
185189
// If the mapper to add has no dots, or the current object mapper has subobjects set to false,
186190
// we just add it as it is for sure a leaf mapper
187-
if (name.contains(".") == false || (subobjects.isPresent() && (subobjects.get() == Subobjects.DISABLED))) {
191+
if (name.contains(".") == false || subobjects.value() == Subobjects.DISABLED) {
188192
add(name, mapper);
189193
} else {
190194
// We strip off the first object path of the mapper name, load or create
@@ -198,7 +202,7 @@ public final void addDynamic(String name, String prefix, Mapper mapper, Document
198202
if (parentBuilder != null) {
199203
parentBuilder.addDynamic(name.substring(firstDotIndex + 1), immediateChildFullName, mapper, context);
200204
add(parentBuilder);
201-
} else if (subobjects.isPresent() && subobjects.get() == Subobjects.AUTO) {
205+
} else if (subobjects.value() == Subobjects.AUTO) {
202206
// No matching parent object was found, the mapper is added as a leaf - similar to subobjects false.
203207
add(name, mapper);
204208
} else {
@@ -236,7 +240,7 @@ protected final Map<String, Mapper> buildMappers(MapperBuilderContext mapperBuil
236240
// mix of object notation and dot notation.
237241
mapper = existing.merge(mapper, MapperMergeContext.from(mapperBuilderContext, Long.MAX_VALUE));
238242
}
239-
if (subobjects.isPresent() && subobjects.get() == Subobjects.DISABLED && mapper instanceof ObjectMapper objectMapper) {
243+
if (subobjects.value() == Subobjects.DISABLED && mapper instanceof ObjectMapper objectMapper) {
240244
// We're parsing a mapping that has set `subobjects: false` but has defined sub-objects
241245
objectMapper.asFlattenedFieldMappers(mapperBuilderContext).forEach(m -> mappers.put(m.leafName(), m));
242246
} else {
@@ -279,7 +283,7 @@ public boolean supportsVersion(IndexVersion indexCreatedVersion) {
279283
public Mapper.Builder parse(String name, Map<String, Object> node, MappingParserContext parserContext)
280284
throws MapperParsingException {
281285
parserContext.incrementMappingObjectDepth(); // throws MapperParsingException if depth limit is exceeded
282-
Optional<Subobjects> subobjects = parseSubobjects(node);
286+
Explicit<Subobjects> subobjects = parseSubobjects(node);
283287
Builder builder = new Builder(name, subobjects);
284288
parseObjectFields(node, parserContext, builder);
285289
parserContext.decrementMappingObjectDepth();
@@ -344,10 +348,10 @@ protected static boolean parseObjectOrDocumentTypeProperties(
344348
return false;
345349
}
346350

347-
protected static Optional<Subobjects> parseSubobjects(Map<String, Object> node) {
351+
protected static Explicit<Subobjects> parseSubobjects(Map<String, Object> node) {
348352
Object subobjectsNode = node.remove("subobjects");
349353
if (subobjectsNode != null) {
350-
return Optional.of(Subobjects.from(subobjectsNode));
354+
return Explicit.of(Subobjects.from(subobjectsNode));
351355
}
352356
return Defaults.SUBOBJECTS;
353357
}
@@ -384,9 +388,7 @@ protected static void parseProperties(Builder objBuilder, Map<String, Object> pr
384388
}
385389
}
386390

387-
if (objBuilder.subobjects.isPresent()
388-
&& objBuilder.subobjects.get() == Subobjects.DISABLED
389-
&& type.equals(NestedObjectMapper.CONTENT_TYPE)) {
391+
if (objBuilder.subobjects.value() == Subobjects.DISABLED && type.equals(NestedObjectMapper.CONTENT_TYPE)) {
390392
throw new MapperParsingException(
391393
"Tried to add nested object ["
392394
+ fieldName
@@ -408,7 +410,7 @@ protected static void parseProperties(Builder objBuilder, Map<String, Object> pr
408410
);
409411
}
410412
Mapper.Builder fieldBuilder;
411-
if (objBuilder.subobjects.isPresent() && objBuilder.subobjects.get() != Subobjects.ENABLED) {
413+
if (objBuilder.subobjects.value() != Subobjects.ENABLED) {
412414
fieldBuilder = typeParser.parse(fieldName, propNode, parserContext);
413415
} else {
414416
String[] fieldNameParts = fieldName.split("\\.");
@@ -456,7 +458,7 @@ private static void validateFieldName(String fieldName, IndexVersion indexCreate
456458
private final String fullPath;
457459

458460
protected final Explicit<Boolean> enabled;
459-
protected final Optional<Subobjects> subobjects;
461+
protected final Explicit<Subobjects> subobjects;
460462
protected final Optional<SourceKeepMode> sourceKeepMode;
461463
protected final Dynamic dynamic;
462464

@@ -466,7 +468,7 @@ private static void validateFieldName(String fieldName, IndexVersion indexCreate
466468
String name,
467469
String fullPath,
468470
Explicit<Boolean> enabled,
469-
Optional<Subobjects> subobjects,
471+
Explicit<Subobjects> subobjects,
470472
Optional<SourceKeepMode> sourceKeepMode,
471473
Dynamic dynamic,
472474
Map<String, Mapper> mappers
@@ -484,9 +486,7 @@ private static void validateFieldName(String fieldName, IndexVersion indexCreate
484486
} else {
485487
this.mappers = Map.copyOf(mappers);
486488
}
487-
assert subobjects.isEmpty()
488-
|| subobjects.get() != Subobjects.DISABLED
489-
|| this.mappers.values().stream().noneMatch(m -> m instanceof ObjectMapper)
489+
assert subobjects.value() != Subobjects.DISABLED || this.mappers.values().stream().noneMatch(m -> m instanceof ObjectMapper)
490490
: "When subobjects is false, mappers must not contain an ObjectMapper";
491491
}
492492

@@ -540,7 +540,7 @@ public final Dynamic dynamic() {
540540
}
541541

542542
public final Subobjects subobjects() {
543-
return subobjects.orElse(Subobjects.ENABLED);
543+
return subobjects.value();
544544
}
545545

546546
public final Optional<SourceKeepMode> sourceKeepMode() {
@@ -581,7 +581,7 @@ public ObjectMapper merge(Mapper mergeWith, MapperMergeContext parentMergeContex
581581

582582
protected record MergeResult(
583583
Explicit<Boolean> enabled,
584-
Optional<Subobjects> subObjects,
584+
Explicit<Subobjects> subObjects,
585585
Optional<SourceKeepMode> sourceKeepMode,
586586
Dynamic dynamic,
587587
Map<String, Mapper> mappers
@@ -602,8 +602,8 @@ static MergeResult build(ObjectMapper existing, ObjectMapper mergeWithObject, Ma
602602
} else {
603603
enabled = existing.enabled;
604604
}
605-
final Optional<Subobjects> subObjects;
606-
if (mergeWithObject.subobjects.isPresent()) {
605+
final Explicit<Subobjects> subObjects;
606+
if (mergeWithObject.subobjects.explicit()) {
607607
if (reason == MergeReason.INDEX_TEMPLATE) {
608608
subObjects = mergeWithObject.subobjects;
609609
} else if (existing.subobjects() != mergeWithObject.subobjects()) {
@@ -650,13 +650,11 @@ private static Map<String, Mapper> buildMergedMappers(
650650
ObjectMapper existing,
651651
ObjectMapper mergeWithObject,
652652
MapperMergeContext objectMergeContext,
653-
Optional<Subobjects> subobjects
653+
Explicit<Subobjects> subobjects
654654
) {
655655
Map<String, Mapper> mergedMappers = new HashMap<>();
656656
for (Mapper childOfExistingMapper : existing.mappers.values()) {
657-
if (subobjects.isPresent()
658-
&& subobjects.get() == Subobjects.DISABLED
659-
&& childOfExistingMapper instanceof ObjectMapper objectMapper) {
657+
if (subobjects.value() == Subobjects.DISABLED && childOfExistingMapper instanceof ObjectMapper objectMapper) {
660658
// An existing mapping with sub-objects is merged with a mapping that has set `subobjects: false`
661659
objectMapper.asFlattenedFieldMappers(objectMergeContext.getMapperBuilderContext())
662660
.forEach(m -> mergedMappers.put(m.leafName(), m));
@@ -667,9 +665,7 @@ private static Map<String, Mapper> buildMergedMappers(
667665
for (Mapper mergeWithMapper : mergeWithObject) {
668666
Mapper mergeIntoMapper = mergedMappers.get(mergeWithMapper.leafName());
669667
if (mergeIntoMapper == null) {
670-
if (subobjects.isPresent()
671-
&& subobjects.get() == Subobjects.DISABLED
672-
&& mergeWithMapper instanceof ObjectMapper objectMapper) {
668+
if (subobjects.value() == Subobjects.DISABLED && mergeWithMapper instanceof ObjectMapper objectMapper) {
673669
// An existing mapping that has set `subobjects: false` is merged with a mapping with sub-objects.
674670
List<FieldMapper> flattenedMappers = objectMapper.asFlattenedFieldMappers(
675671
objectMergeContext.getMapperBuilderContext()
@@ -691,7 +687,7 @@ private static Map<String, Mapper> buildMergedMappers(
691687
putMergedMapper(mergedMappers, truncateObjectMapper(objectMergeContext, om));
692688
}
693689
} else if (mergeIntoMapper instanceof ObjectMapper objectMapper) {
694-
assert subobjects.isEmpty() || subobjects.get() != Subobjects.DISABLED
690+
assert subobjects.explicit() == false || subobjects.value() != Subobjects.DISABLED
695691
: "existing object mappers are supposed to be flattened if subobjects is false";
696692
putMergedMapper(mergedMappers, objectMapper.merge(mergeWithMapper, objectMergeContext));
697693
} else {
@@ -803,7 +799,7 @@ private void ensureFlattenable(MapperBuilderContext context, ContentPath path) {
803799
if (isEnabled() == false) {
804800
throwAutoFlatteningException(path, "the value of [enabled] is [false]");
805801
}
806-
if (subobjects.isPresent() && subobjects.get() == Subobjects.ENABLED) {
802+
if (subobjects.explicit() && subobjects.value() == Subobjects.ENABLED) {
807803
throwAutoFlatteningException(path, "the value of [subobjects] is [true]");
808804
}
809805
}
@@ -838,8 +834,8 @@ void toXContent(XContentBuilder builder, Params params, ToXContent custom) throw
838834
if (isEnabled() != Defaults.ENABLED) {
839835
builder.field("enabled", enabled.value());
840836
}
841-
if (subobjects.isPresent()) {
842-
builder.field("subobjects", subobjects.get().printedValue);
837+
if (subobjects.explicit()) {
838+
builder.field("subobjects", subobjects.value().printedValue);
843839
}
844840
if (sourceKeepMode.isPresent()) {
845841
builder.field("synthetic_source_keep", sourceKeepMode.get());

server/src/main/java/org/elasticsearch/index/mapper/PassThroughObjectMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static class Builder extends ObjectMapper.Builder {
4848

4949
public Builder(String name) {
5050
// Subobjects are not currently supported.
51-
super(name, Optional.of(Subobjects.DISABLED));
51+
super(name, Explicit.implicit(Subobjects.DISABLED));
5252
}
5353

5454
@Override
@@ -101,7 +101,7 @@ public PassThroughObjectMapper build(MapperBuilderContext context) {
101101
int priority
102102
) {
103103
// Subobjects are not currently supported.
104-
super(name, fullPath, enabled, Optional.of(Subobjects.DISABLED), sourceKeepMode, dynamic, mappers);
104+
super(name, fullPath, enabled, Explicit.implicit(Subobjects.DISABLED), sourceKeepMode, dynamic, mappers);
105105
this.timeSeriesDimensionSubFields = timeSeriesDimensionSubFields;
106106
this.priority = priority;
107107
if (priority < 0) {

server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ public static class Builder extends ObjectMapper.Builder {
7777
protected Explicit<Boolean> dateDetection = Defaults.DATE_DETECTION;
7878
protected Explicit<Boolean> numericDetection = Defaults.NUMERIC_DETECTION;
7979

80-
public Builder(String name, Optional<Subobjects> subobjects) {
80+
public Builder(String name) {
81+
this(name, ObjectMapper.Defaults.SUBOBJECTS);
82+
}
83+
84+
public Builder(String name, Explicit<Subobjects> subobjects) {
8185
super(name, subobjects);
8286
}
8387

@@ -134,7 +138,7 @@ public RootObjectMapper build(MapperBuilderContext context) {
134138
RootObjectMapper(
135139
String name,
136140
Explicit<Boolean> enabled,
137-
Optional<Subobjects> subobjects,
141+
Explicit<Subobjects> subobjects,
138142
Optional<SourceKeepMode> sourceKeepMode,
139143
Dynamic dynamic,
140144
Map<String, Mapper> mappers,
@@ -449,7 +453,7 @@ protected boolean isRoot() {
449453

450454
public static RootObjectMapper.Builder parse(String name, Map<String, Object> node, MappingParserContext parserContext)
451455
throws MapperParsingException {
452-
Optional<Subobjects> subobjects = parseSubobjects(node);
456+
Explicit<Subobjects> subobjects = parseSubobjects(node);
453457
RootObjectMapper.Builder builder = new Builder(name, subobjects);
454458
Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
455459
while (iterator.hasNext()) {

server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.IOException;
2020
import java.util.List;
2121
import java.util.Map;
22-
import java.util.Optional;
2322

2423
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
2524

@@ -70,7 +69,7 @@ public void testCreateDynamicStringFieldAsKeywordForDimension() throws IOExcepti
7069
SourceToParse sourceToParse = new SourceToParse("test", new BytesArray(source), XContentType.JSON);
7170

7271
SourceFieldMapper sourceMapper = new SourceFieldMapper.Builder(null, Settings.EMPTY, false, false, false).setSynthetic().build();
73-
RootObjectMapper root = new RootObjectMapper.Builder("_doc", Optional.empty()).add(
72+
RootObjectMapper root = new RootObjectMapper.Builder("_doc").add(
7473
new PassThroughObjectMapper.Builder("labels").setPriority(0).setContainsDimensions().dynamic(ObjectMapper.Dynamic.TRUE)
7574
).build(MapperBuilderContext.root(false, false));
7675
Mapping mapping = new Mapping(root, new MetadataFieldMapper[] { sourceMapper }, Map.of());

server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private static ObjectMapper createObjectMapper(String name) {
178178
name,
179179
name,
180180
Explicit.IMPLICIT_TRUE,
181-
Optional.empty(),
181+
ObjectMapper.Defaults.SUBOBJECTS,
182182
Optional.empty(),
183183
ObjectMapper.Dynamic.FALSE,
184184
emptyMap()

server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void testSubfieldOverride() {
8383
"object",
8484
"object",
8585
Explicit.EXPLICIT_TRUE,
86-
Optional.empty(),
86+
ObjectMapper.Defaults.SUBOBJECTS,
8787
Optional.empty(),
8888
ObjectMapper.Dynamic.TRUE,
8989
Collections.singletonMap("object.subfield", fieldMapper)

0 commit comments

Comments
 (0)