Skip to content

Commit 4e4c829

Browse files
committed
Allow a passthrough mapper to merge with object under certain conditions
1 parent 7b99d34 commit 4e4c829

File tree

3 files changed

+81
-10
lines changed

3 files changed

+81
-10
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ static void throwObjectMappingConflictError(String fieldName) throws IllegalArgu
1414
throw new IllegalArgumentException("can't merge a non object mapping [" + fieldName + "] with an object mapping");
1515
}
1616

17+
static void throPassThroughMappingConflictError(String fieldName) throws IllegalArgumentException {
18+
throw new IllegalArgumentException(
19+
"can't merge a passthrough mapping [" + fieldName + "] with an object mapping that is either root or has subobjects enabled"
20+
);
21+
}
22+
1723
static void throwNestedMappingConflictError(String fieldName) throws IllegalArgumentException {
1824
throw new IllegalArgumentException("can't merge a non-nested mapping [" + fieldName + "] with a nested mapping");
1925
}

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,29 +148,58 @@ public PassThroughObjectMapper.Builder newBuilder(IndexVersion indexVersionCreat
148148

149149
@Override
150150
public PassThroughObjectMapper merge(Mapper mergeWith, MapperMergeContext parentBuilderContext) {
151-
if (mergeWith instanceof PassThroughObjectMapper == false) {
151+
if (mergeWith instanceof ObjectMapper == false) {
152152
MapperErrors.throwObjectMappingConflictError(mergeWith.fullPath());
153153
}
154+
ObjectMapper mergeWithObjectMapper = (ObjectMapper) mergeWith;
155+
if (mergeWithObjectMapper instanceof PassThroughObjectMapper mergeWithPassThrough) {
156+
final var mergeResult = MergeResult.build(this, mergeWithPassThrough, parentBuilderContext);
157+
final Explicit<Boolean> containsDimensions = (mergeWithPassThrough.timeSeriesDimensionSubFields.explicit())
158+
? mergeWithPassThrough.timeSeriesDimensionSubFields
159+
: this.timeSeriesDimensionSubFields;
154160

155-
PassThroughObjectMapper mergeWithObject = (PassThroughObjectMapper) mergeWith;
156-
final var mergeResult = MergeResult.build(this, mergeWithObject, parentBuilderContext);
157-
158-
final Explicit<Boolean> containsDimensions = (mergeWithObject.timeSeriesDimensionSubFields.explicit())
159-
? mergeWithObject.timeSeriesDimensionSubFields
160-
: this.timeSeriesDimensionSubFields;
161-
161+
return new PassThroughObjectMapper(
162+
leafName(),
163+
fullPath(),
164+
mergeResult.enabled(),
165+
mergeResult.sourceKeepMode(),
166+
mergeResult.dynamic(),
167+
mergeResult.mappers(),
168+
containsDimensions,
169+
Math.max(priority, mergeWithPassThrough.priority)
170+
);
171+
}
172+
if (mergeWithObjectMapper instanceof NestedObjectMapper nestedObjectMapper) {
173+
MapperErrors.throwNestedMappingConflictError(fullPath());
174+
}
175+
if (isEligibleForMerge(mergeWithObjectMapper) == false) {
176+
MapperErrors.throPassThroughMappingConflictError(fullPath());
177+
}
178+
MergeResult mergeResult = MergeResult.build(this, mergeWithObjectMapper, parentBuilderContext);
162179
return new PassThroughObjectMapper(
163180
leafName(),
164181
fullPath(),
165182
mergeResult.enabled(),
166183
mergeResult.sourceKeepMode(),
167184
mergeResult.dynamic(),
168185
mergeResult.mappers(),
169-
containsDimensions,
170-
Math.max(priority, mergeWithObject.priority)
186+
timeSeriesDimensionSubFields,
187+
priority
171188
);
172189
}
173190

191+
/**
192+
* An object mapper is compatible to be merged with a passthrough mapper if
193+
* - It is not a root mapper
194+
* - If it does not have subobjects true
195+
*/
196+
private boolean isEligibleForMerge(ObjectMapper objectMapper) {
197+
return objectMapper.isRoot() == false
198+
&& (objectMapper.subobjects == null
199+
|| objectMapper.subobjects.explicit() == false
200+
|| objectMapper.subobjects.value().equals(Subobjects.DISABLED));
201+
}
202+
174203
@Override
175204
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
176205
builder.startObject(leafName());

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
package org.elasticsearch.index.mapper;
1111

1212
import org.elasticsearch.common.Explicit;
13+
import org.elasticsearch.index.IndexVersion;
1314

1415
import java.io.IOException;
1516
import java.util.List;
1617
import java.util.Map;
1718
import java.util.Optional;
1819

20+
import static org.elasticsearch.index.mapper.MapperService.MergeReason.MAPPING_UPDATE;
1921
import static org.hamcrest.Matchers.containsString;
22+
import static org.hamcrest.Matchers.equalTo;
2023
import static org.hamcrest.Matchers.instanceOf;
2124

2225
public class PassThroughObjectMapperTests extends MapperServiceTestCase {
@@ -182,6 +185,39 @@ public void testCheckForDuplicatePrioritiesEmpty() throws IOException {
182185
PassThroughObjectMapper.checkForDuplicatePriorities(List.of());
183186
}
184187

188+
public void testMergingWithPassThrough() {
189+
boolean isSourceSynthetic = randomBoolean();
190+
var passThroughMapper = new RootObjectMapper.Builder("_doc").add(new PassThroughObjectMapper.Builder("metrics").setPriority(10))
191+
.build(MapperBuilderContext.root(isSourceSynthetic, true));
192+
var objectMapper = new RootObjectMapper.Builder("_doc").add(
193+
new ObjectMapper.Builder("metrics").add(new KeywordFieldMapper.Builder("cpu_usage", IndexVersion.current()))
194+
).build(MapperBuilderContext.root(isSourceSynthetic, true));
195+
196+
RootObjectMapper merged = passThroughMapper.merge(
197+
objectMapper,
198+
MapperMergeContext.root(isSourceSynthetic, true, MAPPING_UPDATE, Long.MAX_VALUE)
199+
);
200+
assertThat(merged.getMapper("metrics"), instanceOf(PassThroughObjectMapper.class));
201+
202+
var objectMapperWithSubObjectTrue = new RootObjectMapper.Builder("_doc").add(
203+
new ObjectMapper.Builder("metrics", Explicit.of(ObjectMapper.Subobjects.ENABLED)).add(
204+
new KeywordFieldMapper.Builder("cpu_usage", IndexVersion.current())
205+
)
206+
).build(MapperBuilderContext.root(isSourceSynthetic, true));
207+
208+
IllegalArgumentException error = expectThrows(
209+
IllegalArgumentException.class,
210+
() -> passThroughMapper.merge(
211+
objectMapperWithSubObjectTrue,
212+
MapperMergeContext.root(isSourceSynthetic, true, MAPPING_UPDATE, Long.MAX_VALUE)
213+
)
214+
);
215+
assertThat(
216+
error.getMessage(),
217+
equalTo("can't merge a passthrough mapping [metrics] with an object mapping that is either root or has subobjects enabled")
218+
);
219+
}
220+
185221
private PassThroughObjectMapper create(String name, int priority) {
186222
return new PassThroughObjectMapper(
187223
name,

0 commit comments

Comments
 (0)