Deprecate support for chained multi-fields. (#42330)

This PR contains a straight backport of #41926, and also updates the
migration documentation and deprecation info API for 7.x.
This commit is contained in:
Julie Tibshirani 2019-05-24 15:55:06 -07:00 committed by GitHub
parent f2cfd09289
commit 3a6c2525ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 153 additions and 7 deletions

View File

@ -18,6 +18,19 @@ coming[7.3.0]
// end::notable-breaking-changes[]
[[breaking_73_mapping_changes]]
=== Mapping changes
[float]
==== Defining multi-fields within multi-fields
Previously, it was possible to define a multi-field within a multi-field.
Defining chained multi-fields is now deprecated and will no longer be supported
in 8.0. To resolve the issue, all instances of `fields` that occur within a
`fields` block should be removed from the mappings, either by flattening the
chained `fields` blocks into a single level, or by switching to `copy_to` if
appropriate.
[[breaking_73_plugin_changes]]
=== Plugins changes

View File

@ -136,10 +136,7 @@ public abstract class Mapper implements ToXContentFragment, Iterable<Mapper> {
protected Function<String, SimilarityProvider> similarityLookupService() { return similarityLookupService; }
public ParserContext createMultiFieldContext(ParserContext in) {
return new MultiFieldParserContext(in) {
@Override
public boolean isWithinMultiField() { return true; }
};
return new MultiFieldParserContext(in);
}
static class MultiFieldParserContext extends ParserContext {
@ -147,6 +144,9 @@ public abstract class Mapper implements ToXContentFragment, Iterable<Mapper> {
super(in.type(), in.similarityLookupService(), in.mapperService(), in.typeParsers(),
in.indexVersionCreated(), in.queryShardContextSupplier());
}
@Override
public boolean isWithinMultiField() { return true; }
}
}

View File

@ -19,8 +19,10 @@
package org.elasticsearch.index.mapper;
import org.apache.logging.log4j.LogManager;
import org.apache.lucene.index.IndexOptions;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.index.analysis.AnalysisMode;
@ -37,6 +39,7 @@ import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeFl
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeStringValue;
public class TypeParsers {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(TypeParsers.class));
public static final String DOC_VALUES = "doc_values";
public static final String INDEX_OPTIONS_DOCS = "docs";
@ -214,11 +217,18 @@ public class TypeParsers {
public static boolean parseMultiField(FieldMapper.Builder builder, String name, Mapper.TypeParser.ParserContext parserContext,
String propName, Object propNode) {
parserContext = parserContext.createMultiFieldContext(parserContext);
if (propName.equals("fields")) {
if (parserContext.isWithinMultiField()) {
deprecationLogger.deprecatedAndMaybeLog("multifield_within_multifield", "At least one multi-field, [" + name + "], was " +
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
"switching to [copy_to] if appropriate.");
}
parserContext = parserContext.createMultiFieldContext(parserContext);
final Map<String, Object> multiFieldsPropNodes;
if (propNode instanceof List && ((List<?>) propNode).isEmpty()) {
multiFieldsPropNodes = Collections.emptyMap();
} else if (propNode instanceof Map) {

View File

@ -170,6 +170,12 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
assertThat(raw, notNullValue());
assertThat(raw.binaryValue(), is(new BytesRef("foo")));
assertWarnings("At least one multi-field, [field], was " +
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
"switching to [copy_to] if appropriate.");
}
public void testExternalValuesWithMultifieldTwoLevels() throws Exception {
@ -235,5 +241,11 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
assertThat(doc.rootDoc().getField("field.raw"), notNullValue());
assertThat(doc.rootDoc().getField("field.raw").stringValue(), is("foo"));
assertWarnings("At least one multi-field, [field], was " +
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
"switching to [copy_to] if appropriate.");
}
}

View File

@ -24,7 +24,11 @@ import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.analysis.AbstractTokenFilterFactory;
import org.elasticsearch.index.analysis.AnalysisMode;
@ -36,6 +40,7 @@ import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.analysis.TokenFilterFactory;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -157,6 +162,38 @@ public class TypeParsersTests extends ESTestCase {
TypeParsers.parseTextField(builder, "name", new HashMap<>(fieldNode), parserContext);
}
public void testMultiFieldWithinMultiField() throws IOException {
TextFieldMapper.Builder builder = new TextFieldMapper.Builder("textField");
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
.field("type", "keyword")
.startObject("fields")
.startObject("sub-field")
.field("type", "keyword")
.startObject("fields")
.startObject("sub-sub-field")
.field("type", "keyword")
.endObject()
.endObject()
.endObject()
.endObject()
.endObject();
Map<String, Object> fieldNode = XContentHelper.convertToMap(
BytesReference.bytes(mapping), true, mapping.contentType()).v2();
Mapper.TypeParser typeParser = new KeywordFieldMapper.TypeParser();
Mapper.TypeParser.ParserContext parserContext = new Mapper.TypeParser.ParserContext("type",
null, null, type -> typeParser, Version.CURRENT, null);
TypeParsers.parseField(builder, "some-field", fieldNode, parserContext);
assertWarnings("At least one multi-field, [sub-field], was " +
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
"switching to [copy_to] if appropriate.");
}
private Analyzer createAnalyzerWithMode(String name, AnalysisMode mode) {
TokenFilterFactory tokenFilter = new AbstractTokenFilterFactory(indexSettings, name, Settings.EMPTY) {
@Override

View File

@ -47,7 +47,8 @@ public class DeprecationChecks {
static List<Function<IndexMetaData, DeprecationIssue>> INDEX_SETTINGS_CHECKS =
Collections.unmodifiableList(Arrays.asList(
IndexDeprecationChecks::oldIndicesCheck,
IndexDeprecationChecks::tooManyFieldsCheck
IndexDeprecationChecks::tooManyFieldsCheck,
IndexDeprecationChecks::chainedMultiFieldsCheck
));
static List<BiFunction<DatafeedConfig, NamedXContentRegistry, DeprecationIssue>> ML_SETTINGS_CHECKS =

View File

@ -115,6 +115,32 @@ public class IndexDeprecationChecks {
return null;
}
static DeprecationIssue chainedMultiFieldsCheck(IndexMetaData indexMetaData) {
List<String> issues = new ArrayList<>();
fieldLevelMappingIssue(indexMetaData, ((mappingMetaData, sourceAsMap) -> issues.addAll(
findInPropertiesRecursively(mappingMetaData.type(), sourceAsMap, IndexDeprecationChecks::containsChainedMultiFields))));
if (issues.size() > 0) {
return new DeprecationIssue(DeprecationIssue.Level.WARNING,
"Multi-fields within multi-fields",
"https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html" +
"#_defining_multi_fields_within_multi_fields",
"The names of fields that contain chained multi-fields: " + issues.toString());
}
return null;
}
private static boolean containsChainedMultiFields(Map<?, ?> property) {
if (property.containsKey("fields")) {
Map<?, ?> fields = (Map<?, ?>) property.get("fields");
for (Object rawSubField: fields.values()) {
Map<?, ?> subField = (Map<?, ?>) rawSubField;
if (subField.containsKey("fields")) {
return true;
}
}
}
return false;
}
private static final Set<String> TYPES_THAT_DONT_COUNT;
static {

View File

@ -9,7 +9,9 @@ package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils;
@ -110,6 +112,51 @@ public class IndexDeprecationChecksTests extends ESTestCase {
assertEquals(0, withDefaultFieldIssues.size());
}
public void testChainedMultiFields() throws IOException {
XContentBuilder xContent = XContentFactory.jsonBuilder().startObject()
.startObject("properties")
.startObject("invalid-field")
.field("type", "keyword")
.startObject("fields")
.startObject("sub-field")
.field("type", "keyword")
.startObject("fields")
.startObject("sub-sub-field")
.field("type", "keyword")
.endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.startObject("valid-field")
.field("type", "keyword")
.startObject("fields")
.startObject("sub-field")
.field("type", "keyword")
.endObject()
.endObject()
.endObject()
.endObject()
.endObject();
String mapping = BytesReference.bytes(xContent).utf8ToString();
IndexMetaData simpleIndex = IndexMetaData.builder(randomAlphaOfLengthBetween(5, 10))
.settings(settings(Version.V_7_3_0))
.numberOfShards(1)
.numberOfReplicas(1)
.putMapping("_doc", mapping)
.build();
List<DeprecationIssue> issues = DeprecationChecks.filterChecks(INDEX_SETTINGS_CHECKS, c -> c.apply(simpleIndex));
assertEquals(1, issues.size());
DeprecationIssue expected = new DeprecationIssue(DeprecationIssue.Level.WARNING,
"Multi-fields within multi-fields",
"https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html" +
"#_defining_multi_fields_within_multi_fields",
"The names of fields that contain chained multi-fields: [[type: _doc, field: invalid-field]]");
assertEquals(singletonList(expected), issues);
}
static void addRandomFields(final int fieldLimit,
XContentBuilder mappingBuilder) throws IOException {
AtomicInteger fieldCount = new AtomicInteger(0);