mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-08 22:14:59 +00:00
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:
parent
f2cfd09289
commit
3a6c2525ca
@ -18,6 +18,19 @@ coming[7.3.0]
|
|||||||
|
|
||||||
// end::notable-breaking-changes[]
|
// 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]]
|
[[breaking_73_plugin_changes]]
|
||||||
=== Plugins changes
|
=== Plugins changes
|
||||||
|
|
||||||
|
@ -136,10 +136,7 @@ public abstract class Mapper implements ToXContentFragment, Iterable<Mapper> {
|
|||||||
protected Function<String, SimilarityProvider> similarityLookupService() { return similarityLookupService; }
|
protected Function<String, SimilarityProvider> similarityLookupService() { return similarityLookupService; }
|
||||||
|
|
||||||
public ParserContext createMultiFieldContext(ParserContext in) {
|
public ParserContext createMultiFieldContext(ParserContext in) {
|
||||||
return new MultiFieldParserContext(in) {
|
return new MultiFieldParserContext(in);
|
||||||
@Override
|
|
||||||
public boolean isWithinMultiField() { return true; }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MultiFieldParserContext extends ParserContext {
|
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(),
|
super(in.type(), in.similarityLookupService(), in.mapperService(), in.typeParsers(),
|
||||||
in.indexVersionCreated(), in.queryShardContextSupplier());
|
in.indexVersionCreated(), in.queryShardContextSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWithinMultiField() { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,10 @@
|
|||||||
|
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.lucene.index.IndexOptions;
|
import org.apache.lucene.index.IndexOptions;
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
import org.elasticsearch.ElasticsearchParseException;
|
||||||
|
import org.elasticsearch.common.logging.DeprecationLogger;
|
||||||
import org.elasticsearch.common.time.DateFormatter;
|
import org.elasticsearch.common.time.DateFormatter;
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||||
import org.elasticsearch.index.analysis.AnalysisMode;
|
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;
|
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeStringValue;
|
||||||
|
|
||||||
public class TypeParsers {
|
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 DOC_VALUES = "doc_values";
|
||||||
public static final String INDEX_OPTIONS_DOCS = "docs";
|
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,
|
public static boolean parseMultiField(FieldMapper.Builder builder, String name, Mapper.TypeParser.ParserContext parserContext,
|
||||||
String propName, Object propNode) {
|
String propName, Object propNode) {
|
||||||
parserContext = parserContext.createMultiFieldContext(parserContext);
|
|
||||||
if (propName.equals("fields")) {
|
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;
|
final Map<String, Object> multiFieldsPropNodes;
|
||||||
|
|
||||||
if (propNode instanceof List && ((List<?>) propNode).isEmpty()) {
|
if (propNode instanceof List && ((List<?>) propNode).isEmpty()) {
|
||||||
multiFieldsPropNodes = Collections.emptyMap();
|
multiFieldsPropNodes = Collections.emptyMap();
|
||||||
} else if (propNode instanceof Map) {
|
} else if (propNode instanceof Map) {
|
||||||
|
@ -170,6 +170,12 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
|
|||||||
|
|
||||||
assertThat(raw, notNullValue());
|
assertThat(raw, notNullValue());
|
||||||
assertThat(raw.binaryValue(), is(new BytesRef("foo")));
|
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 {
|
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"), notNullValue());
|
||||||
assertThat(doc.rootDoc().getField("field.raw").stringValue(), is("foo"));
|
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@ import org.apache.lucene.analysis.TokenStream;
|
|||||||
import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
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.IndexSettings;
|
||||||
import org.elasticsearch.index.analysis.AbstractTokenFilterFactory;
|
import org.elasticsearch.index.analysis.AbstractTokenFilterFactory;
|
||||||
import org.elasticsearch.index.analysis.AnalysisMode;
|
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.index.analysis.TokenFilterFactory;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -157,6 +162,38 @@ public class TypeParsersTests extends ESTestCase {
|
|||||||
TypeParsers.parseTextField(builder, "name", new HashMap<>(fieldNode), parserContext);
|
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) {
|
private Analyzer createAnalyzerWithMode(String name, AnalysisMode mode) {
|
||||||
TokenFilterFactory tokenFilter = new AbstractTokenFilterFactory(indexSettings, name, Settings.EMPTY) {
|
TokenFilterFactory tokenFilter = new AbstractTokenFilterFactory(indexSettings, name, Settings.EMPTY) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,7 +47,8 @@ public class DeprecationChecks {
|
|||||||
static List<Function<IndexMetaData, DeprecationIssue>> INDEX_SETTINGS_CHECKS =
|
static List<Function<IndexMetaData, DeprecationIssue>> INDEX_SETTINGS_CHECKS =
|
||||||
Collections.unmodifiableList(Arrays.asList(
|
Collections.unmodifiableList(Arrays.asList(
|
||||||
IndexDeprecationChecks::oldIndicesCheck,
|
IndexDeprecationChecks::oldIndicesCheck,
|
||||||
IndexDeprecationChecks::tooManyFieldsCheck
|
IndexDeprecationChecks::tooManyFieldsCheck,
|
||||||
|
IndexDeprecationChecks::chainedMultiFieldsCheck
|
||||||
));
|
));
|
||||||
|
|
||||||
static List<BiFunction<DatafeedConfig, NamedXContentRegistry, DeprecationIssue>> ML_SETTINGS_CHECKS =
|
static List<BiFunction<DatafeedConfig, NamedXContentRegistry, DeprecationIssue>> ML_SETTINGS_CHECKS =
|
||||||
|
@ -115,6 +115,32 @@ public class IndexDeprecationChecks {
|
|||||||
return null;
|
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;
|
private static final Set<String> TYPES_THAT_DONT_COUNT;
|
||||||
static {
|
static {
|
||||||
|
@ -9,7 +9,9 @@ package org.elasticsearch.xpack.deprecation;
|
|||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.VersionUtils;
|
import org.elasticsearch.test.VersionUtils;
|
||||||
@ -110,6 +112,51 @@ public class IndexDeprecationChecksTests extends ESTestCase {
|
|||||||
assertEquals(0, withDefaultFieldIssues.size());
|
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,
|
static void addRandomFields(final int fieldLimit,
|
||||||
XContentBuilder mappingBuilder) throws IOException {
|
XContentBuilder mappingBuilder) throws IOException {
|
||||||
AtomicInteger fieldCount = new AtomicInteger(0);
|
AtomicInteger fieldCount = new AtomicInteger(0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user