Skip to content

Commit

Permalink
RuntimeField.Builder should not extend FieldMapper.Builder (#73840)
Browse files Browse the repository at this point in the history
RuntimeField.Builder currently extends FieldMapper.Builder so that it can
share some parsing code and re-use the Parameter infrastructure. However,
this also means that we have to have a number of no-op method implementations,
and in addition this will make it complicated to add a fields parameter within
multi-keyed object field types. This commit removes the class relationship
between these two classes.
  • Loading branch information
romseygeek committed Jun 16, 2021
1 parent 84956bf commit d4fceb5
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ public Parameter<T> acceptsNull() {
return this;
}

public boolean canAcceptNull() {
return acceptsNull;
}

/**
* Adds a deprecated parameter name.
*
Expand Down Expand Up @@ -758,7 +762,13 @@ private void init(FieldMapper toInit) {
setValue(initializer.apply(toInit));
}

private void parse(String field, ParserContext context, Object in) {
/**
* Parse the field value from an Object
* @param field the field name
* @param context the parser context
* @param in the object
*/
public void parse(String field, ParserContext context, Object in) {
setValue(parser.apply(field, context, in));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

package org.elasticsearch.index.mapper;

import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.FieldMapper.Parameter;

import java.io.IOException;
import java.util.Collection;
Expand Down Expand Up @@ -58,51 +62,69 @@ default XContentBuilder toXContent(XContentBuilder builder, Params params) throw
*/
Collection<MappedFieldType> asMappedFieldTypes();

/**
* For runtime fields the {@link RuntimeField.Parser} returns directly the {@link MappedFieldType}.
* Internally we still create a {@link RuntimeField.Builder} so we reuse the {@link FieldMapper.Parameter} infrastructure,
* but {@link RuntimeField.Builder#init(FieldMapper)} and {@link RuntimeField.Builder#build(ContentPath)} are never called as
* {@link RuntimeField.Parser#parse(String, Map, Mapper.TypeParser.ParserContext)} calls
* {@link RuntimeField.Builder#parse(String, Mapper.TypeParser.ParserContext, Map)} and returns the corresponding
* {@link MappedFieldType}.
*/
abstract class Builder extends FieldMapper.Builder {
final FieldMapper.Parameter<Map<String, String>> meta = FieldMapper.Parameter.metaParam();
abstract class Builder implements ToXContent {
final String name;
final Parameter<Map<String, String>> meta = Parameter.metaParam();

private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RuntimeField.class);

protected Builder(String name) {
super(name);
this.name = name;
}

public Map<String, String> meta() {
return meta.getValue();
}

@Override
protected List<FieldMapper.Parameter<?>> getParameters() {
protected List<Parameter<?>> getParameters() {
return Collections.singletonList(meta);
}

@Override
public FieldMapper.Builder init(FieldMapper initializer) {
throw new UnsupportedOperationException();
}
protected abstract RuntimeField createRuntimeField(Mapper.TypeParser.ParserContext parserContext);

@Override
public final FieldMapper build(ContentPath context) {
throw new UnsupportedOperationException();
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
boolean includeDefaults = params.paramAsBoolean("include_defaults", false);
for (Parameter<?> parameter : getParameters()) {
parameter.toXContent(builder, includeDefaults);
}
return builder;
}

protected abstract RuntimeField createRuntimeField(Mapper.TypeParser.ParserContext parserContext);

private void validate() {
ContentPath contentPath = parentPath(name());
FieldMapper.MultiFields multiFields = multiFieldsBuilder.build(this, contentPath);
if (multiFields.iterator().hasNext()) {
throw new IllegalArgumentException("runtime field [" + name + "] does not support [fields]");
public final void parse(String name, Mapper.TypeParser.ParserContext parserContext, Map<String, Object> fieldNode) {
Map<String, Parameter<?>> paramsMap = new HashMap<>();
for (Parameter<?> param : getParameters()) {
paramsMap.put(param.name, param);
}
FieldMapper.CopyTo copyTo = this.copyTo.build();
if (copyTo.copyToFields().isEmpty() == false) {
throw new IllegalArgumentException("runtime field [" + name + "] does not support [copy_to]");
String type = (String) fieldNode.remove("type");
for (Iterator<Map.Entry<String, Object>> iterator = fieldNode.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
final String propName = entry.getKey();
final Object propNode = entry.getValue();
Parameter<?> parameter = paramsMap.get(propName);
if (parameter == null) {
if (parserContext.isFromDynamicTemplate()) {
// The parameter is unknown, but this mapping is from a dynamic template.
// Until 7.x it was possible to use unknown parameters there, so for bwc we need to ignore it
deprecationLogger.deprecate(DeprecationCategory.API, propName,
"Parameter [{}] is used in a dynamic template mapping and has no effect on type [{}]. "
+ "Usage will result in an error in future major versions and should be removed.",
propName,
type
);
iterator.remove();
continue;
}
throw new MapperParsingException(
"unknown parameter [" + propName + "] on runtime field [" + name + "] of type [" + type + "]"
);
}
if (propNode == null && parameter.canAcceptNull() == false) {
throw new MapperParsingException("[" + propName + "] on runtime field [" + name
+ "] of type [" + type + "] must not have a [null] value");
}
parameter.parse(name, parserContext, propNode);
iterator.remove();
}
}
}
Expand All @@ -123,7 +145,6 @@ RuntimeField parse(String name, Map<String, Object> node, Mapper.TypeParser.Pars

RuntimeField.Builder builder = builderFunction.apply(name);
builder.parse(name, parserContext, node);
builder.validate();
return builder.createRuntimeField(parserContext);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Collections;
import java.util.function.BiConsumer;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -107,7 +108,7 @@ public void testCopyToIsNotSupported() throws IOException {
b.field("copy_to", "target");
});
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> createMapperService(mapping));
assertEquals("Failed to parse mapping [_doc]: runtime field [field] does not support [copy_to]", exception.getMessage());
assertThat(exception.getMessage(), containsString("unknown parameter [copy_to] on runtime field"));
}

public void testMultiFieldsIsNotSupported() throws IOException {
Expand All @@ -116,7 +117,7 @@ public void testMultiFieldsIsNotSupported() throws IOException {
b.startObject("fields").startObject("test").field("type", "keyword").endObject().endObject();
});
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> createMapperService(mapping));
assertEquals("Failed to parse mapping [_doc]: runtime field [field] does not support [fields]", exception.getMessage());
assertThat(exception.getMessage(), containsString("unknown parameter [fields] on runtime field"));
}

public void testStoredScriptsAreNotSupported() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ public void testIllegalDynamicTemplateUnknownAttributeRuntime() throws Exception
assertWarnings("dynamic template [my_template] has invalid content [" +
"{\"match_mapping_type\":\"string\",\"runtime\":{\"foo\":\"bar\",\"type\":\"keyword\"}}], " +
"attempted to validate it with the following match_mapping_type: [string], " +
"caused by [unknown parameter [foo] on mapper [__dynamic__my_template] of type [keyword]]");
"caused by [unknown parameter [foo] on runtime field [__dynamic__my_template] of type [keyword]]");
}

public void testIllegalDynamicTemplateInvalidAttribute() throws Exception {
Expand Down Expand Up @@ -523,7 +523,7 @@ public void testIllegalDynamicTemplateNoMappingTypeRuntime() throws Exception {
String expected = "dynamic template [my_template] has invalid content [{" + matchError +
",\"runtime\":{\"foo\":\"bar\",\"type\":\"{dynamic_type}\"}}], " +
"attempted to validate it with the following match_mapping_type: [string, long, double, boolean, date], " +
"caused by [unknown parameter [foo] on mapper [__dynamic__my_template] of type [date]]";
"caused by [unknown parameter [foo] on runtime field [__dynamic__my_template] of type [date]]";
assertWarnings(expected);
}

Expand Down Expand Up @@ -705,7 +705,8 @@ public void testRuntimeSectionWrongFormat() throws IOException {
public void testRuntimeSectionRemainingField() throws IOException {
XContentBuilder mapping = runtimeFieldMapping(builder -> builder.field("type", "keyword").field("unsupported", "value"));
MapperParsingException e = expectThrows(MapperParsingException.class, () -> createMapperService(mapping));
assertEquals("Failed to parse mapping [_doc]: unknown parameter [unsupported] on mapper [field] of type [keyword]", e.getMessage());
assertEquals("Failed to parse mapping [_doc]: unknown parameter [unsupported] on runtime field [field] of type [keyword]",
e.getMessage());
}

public void testTemplateWithoutMatchPredicates() throws Exception {
Expand Down

0 comments on commit d4fceb5

Please sign in to comment.