Continue to accept unused 'universal' params in <8.0 indexes (#59381)

We have a number of parameters which are universally parsed by almost all
mappers, whether or not they make sense. Migrating the binary and boolean
mappers to the new style of declaring their parameters explicitly has meant
that these universal parameters stopped being accepted, which would break
existing mappings.

This commit adds some extra logic to ParametrizedFieldMapper that checks
for the existence of these universal parameters, and issues a warning on
7x indexes if it finds them. Indexes created in 8.0 and beyond will throw an
error.

Fixes #59359
This commit is contained in:
Alan Woodward 2020-07-13 11:07:52 +01:00 committed by Alan Woodward
parent cdf6a054c6
commit c810a4a12e
2 changed files with 45 additions and 5 deletions

View File

@ -19,18 +19,24 @@
package org.elasticsearch.index.mapper; package org.elasticsearch.index.mapper;
import org.apache.logging.log4j.LogManager;
import org.apache.lucene.document.FieldType; import org.apache.lucene.document.FieldType;
import org.elasticsearch.Version;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.common.xcontent.support.XContentMapValues;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
@ -46,6 +52,9 @@ import java.util.function.Function;
*/ */
public abstract class ParametrizedFieldMapper extends FieldMapper { public abstract class ParametrizedFieldMapper extends FieldMapper {
private static final DeprecationLogger deprecationLogger
= new DeprecationLogger(LogManager.getLogger(ParametrizedFieldMapper.class));
/** /**
* Creates a new ParametrizedFieldMapper * Creates a new ParametrizedFieldMapper
*/ */
@ -330,6 +339,12 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
} }
Parameter<?> parameter = paramsMap.get(propName); Parameter<?> parameter = paramsMap.get(propName);
if (parameter == null) { if (parameter == null) {
if (isDeprecatedParameter(propName, parserContext.indexVersionCreated())) {
deprecationLogger.deprecatedAndMaybeLog(propName,
"Parameter [{}] has no effect on type [{}] and will be removed in future", propName, type);
iterator.remove();
continue;
}
throw new MapperParsingException("unknown parameter [" + propName throw new MapperParsingException("unknown parameter [" + propName
+ "] on mapper [" + name + "] of type [" + type + "]"); + "] on mapper [" + name + "] of type [" + type + "]");
} }
@ -341,5 +356,15 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
iterator.remove(); iterator.remove();
} }
} }
// These parameters were previously *always* parsed by TypeParsers#parseField(), even if they
// made no sense; if we've got here, that means that they're not declared on a current mapper,
// and so we emit a deprecation warning rather than failing a previously working mapping.
private static final Set<String> DEPRECATED_PARAMS
= new HashSet<>(Arrays.asList("store", "meta", "index", "doc_values", "boost", "index_options", "similarity"));
private static boolean isDeprecatedParameter(String propName, Version indexCreatedVersion) {
return DEPRECATED_PARAMS.contains(propName);
}
} }
} }

View File

@ -67,6 +67,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
= Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false); = Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false);
final Parameter<String> variable final Parameter<String> variable
= Parameter.stringParam("variable", true, m -> toType(m).variable, "default").acceptsNull(); = Parameter.stringParam("variable", true, m -> toType(m).variable, "default").acceptsNull();
final Parameter<Boolean> index = Parameter.boolParam("index", false, m -> toType(m).index, true);
protected Builder(String name) { protected Builder(String name) {
super(name); super(name);
@ -74,7 +75,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
@Override @Override
protected List<Parameter<?>> getParameters() { protected List<Parameter<?>> getParameters() {
return Arrays.asList(fixed, fixed2, variable); return Arrays.asList(fixed, fixed2, variable, index);
} }
@Override @Override
@ -99,6 +100,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
private final boolean fixed; private final boolean fixed;
private final boolean fixed2; private final boolean fixed2;
private final String variable; private final String variable;
private final boolean index;
protected TestMapper(String simpleName, String fullName, MultiFields multiFields, CopyTo copyTo, protected TestMapper(String simpleName, String fullName, MultiFields multiFields, CopyTo copyTo,
ParametrizedMapperTests.Builder builder) { ParametrizedMapperTests.Builder builder) {
@ -106,6 +108,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
this.fixed = builder.fixed.getValue(); this.fixed = builder.fixed.getValue();
this.fixed2 = builder.fixed2.getValue(); this.fixed2 = builder.fixed2.getValue();
this.variable = builder.variable.getValue(); this.variable = builder.variable.getValue();
this.index = builder.index.getValue();
} }
@Override @Override
@ -124,7 +127,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
} }
} }
private static TestMapper fromMapping(String mapping) { private static TestMapper fromMapping(String mapping, Version version) {
Mapper.TypeParser.ParserContext pc = new Mapper.TypeParser.ParserContext(s -> null, null, s -> { Mapper.TypeParser.ParserContext pc = new Mapper.TypeParser.ParserContext(s -> null, null, s -> {
if (Objects.equals("keyword", s)) { if (Objects.equals("keyword", s)) {
return new KeywordFieldMapper.TypeParser(); return new KeywordFieldMapper.TypeParser();
@ -133,12 +136,16 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
return new BinaryFieldMapper.TypeParser(); return new BinaryFieldMapper.TypeParser();
} }
return null; return null;
}, Version.CURRENT, () -> null); }, version, () -> null);
return (TestMapper) new TypeParser() return (TestMapper) new TypeParser()
.parse("field", XContentHelper.convertToMap(JsonXContent.jsonXContent, mapping, true), pc) .parse("field", XContentHelper.convertToMap(JsonXContent.jsonXContent, mapping, true), pc)
.build(new Mapper.BuilderContext(Settings.EMPTY, new ContentPath(0))); .build(new Mapper.BuilderContext(Settings.EMPTY, new ContentPath(0)));
} }
private static TestMapper fromMapping(String mapping) {
return fromMapping(mapping, Version.CURRENT);
}
// defaults - create empty builder config, and serialize with and without defaults // defaults - create empty builder config, and serialize with and without defaults
public void testDefaults() throws IOException { public void testDefaults() throws IOException {
String mapping = "{\"type\":\"test_mapper\"}"; String mapping = "{\"type\":\"test_mapper\"}";
@ -154,7 +161,8 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
builder.startObject(); builder.startObject();
mapper.toXContent(builder, params); mapper.toXContent(builder, params);
builder.endObject(); builder.endObject();
assertEquals("{\"field\":{\"type\":\"test_mapper\",\"fixed\":true,\"fixed2\":false,\"variable\":\"default\"}}", assertEquals("{\"field\":{\"type\":\"test_mapper\",\"fixed\":true," +
"\"fixed2\":false,\"variable\":\"default\",\"index\":true}}",
Strings.toString(builder)); Strings.toString(builder));
} }
@ -245,8 +253,15 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
indexService.mapperService().merge("_doc", new CompressedXContent(mapping), MapperService.MergeReason.MAPPING_UPDATE); indexService.mapperService().merge("_doc", new CompressedXContent(mapping), MapperService.MergeReason.MAPPING_UPDATE);
assertEquals(mapping, Strings.toString(indexService.mapperService().documentMapper())); assertEquals(mapping, Strings.toString(indexService.mapperService().documentMapper()));
}
public void testDeprecatedParameters() throws IOException {
// 'index' is declared explicitly, 'store' is not, but is one of the previously always-accepted params
String mapping = "{\"type\":\"test_mapper\",\"index\":false,\"store\":true}";
TestMapper mapper = fromMapping(mapping, Version.V_7_8_0);
assertWarnings("Parameter [store] has no effect on type [test_mapper] and will be removed in future");
assertFalse(mapper.index);
assertEquals("{\"field\":{\"type\":\"test_mapper\",\"index\":false}}", Strings.toString(mapper));
} }
} }