Skip to content

Commit f07b90f

Browse files
authored
Remove support for chained multi-fields. (#42333)
Follow-up to #41926, where we deprecated support for multi-fields within multi-fields. Addresses #41267.
1 parent 492efa7 commit f07b90f

File tree

5 files changed

+52
-112
lines changed

5 files changed

+52
-112
lines changed

docs/reference/migration/migrate_8_0/mappings.asciidoc

+9
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@
1414

1515
The number of completion contexts within a single completion field
1616
has been limited to 10.
17+
18+
[float]
19+
==== Defining multi-fields within multi-fields
20+
21+
Previously, it was possible to define a multi-field within a multi-field.
22+
Defining chained multi-fields was deprecated in 7.3 and is now no longer
23+
supported. To migrate the mappings, all instances of `fields` that occur within
24+
a `fields` block should be removed, either by flattening the chained `fields`
25+
blocks into a single level, or by switching to `copy_to` if appropriate.

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

+13-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.logging.log4j.LogManager;
2323
import org.apache.lucene.index.IndexOptions;
2424
import org.elasticsearch.ElasticsearchParseException;
25+
import org.elasticsearch.Version;
2526
import org.elasticsearch.common.logging.DeprecationLogger;
2627
import org.elasticsearch.common.time.DateFormatter;
2728
import org.elasticsearch.common.xcontent.support.XContentMapValues;
@@ -219,11 +220,18 @@ public static boolean parseMultiField(FieldMapper.Builder builder, String name,
219220
String propName, Object propNode) {
220221
if (propName.equals("fields")) {
221222
if (parserContext.isWithinMultiField()) {
222-
deprecationLogger.deprecatedAndMaybeLog("multifield_within_multifield", "At least one multi-field, [" + name + "], was " +
223-
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
224-
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
225-
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
226-
"switching to [copy_to] if appropriate.");
223+
// For indices created prior to 8.0, we only emit a deprecation warning and do not fail type parsing. This is to
224+
// maintain the backwards-compatibility guarantee that we can always load indexes from the previous major version.
225+
if (parserContext.indexVersionCreated().before(Version.V_8_0_0)) {
226+
deprecationLogger.deprecatedAndMaybeLog("multifield_within_multifield", "At least one multi-field, [" + name + "], " +
227+
"was encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated " +
228+
"and is not supported for indices created in 8.0 and later. To migrate the mappings, all instances of [fields] " +
229+
"that occur within a [fields] block should be removed from the mappings, either by flattening the chained " +
230+
"[fields] blocks into a single level, or switching to [copy_to] if appropriate.");
231+
} else {
232+
throw new IllegalArgumentException("Encountered a multi-field [" + name + "] which itself contains a multi-field. " +
233+
"Defining chained multi-fields is not supported.");
234+
}
227235
}
228236

229237
parserContext = parserContext.createMultiFieldContext(parserContext);

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

-89
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package org.elasticsearch.index.mapper;
2121

2222
import org.apache.lucene.index.IndexableField;
23-
import org.apache.lucene.util.BytesRef;
2423
import org.elasticsearch.Version;
2524
import org.elasticsearch.cluster.metadata.IndexMetaData;
2625
import org.elasticsearch.common.Strings;
@@ -132,12 +131,6 @@ public void testExternalValuesWithMultifield() throws Exception {
132131
.startObject("field")
133132
.field("type", "text")
134133
.field("store", true)
135-
.startObject("fields")
136-
.startObject("raw")
137-
.field("type", "keyword")
138-
.field("store", true)
139-
.endObject()
140-
.endObject()
141134
.endObject()
142135
.endObject()
143136
.endObject()
@@ -164,87 +157,5 @@ public void testExternalValuesWithMultifield() throws Exception {
164157
IndexableField field = doc.rootDoc().getField("field.field");
165158
assertThat(field, notNullValue());
166159
assertThat(field.stringValue(), is("foo"));
167-
168-
IndexableField raw = doc.rootDoc().getField("field.field.raw");
169-
170-
assertThat(raw, notNullValue());
171-
assertThat(raw.binaryValue(), is(new BytesRef("foo")));
172-
173-
assertWarnings("At least one multi-field, [field], was " +
174-
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
175-
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
176-
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
177-
"switching to [copy_to] if appropriate.");
178-
}
179-
180-
public void testExternalValuesWithMultifieldTwoLevels() throws Exception {
181-
IndexService indexService = createIndex("test");
182-
Map<String, Mapper.TypeParser> mapperParsers = new HashMap<>();
183-
mapperParsers.put(ExternalMapperPlugin.EXTERNAL, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "foo"));
184-
mapperParsers.put(ExternalMapperPlugin.EXTERNAL_BIS, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "bar"));
185-
mapperParsers.put(TextFieldMapper.CONTENT_TYPE, new TextFieldMapper.TypeParser());
186-
MapperRegistry mapperRegistry = new MapperRegistry(mapperParsers, Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER);
187-
188-
Supplier<QueryShardContext> queryShardContext = () -> {
189-
return indexService.newQueryShardContext(0, null, () -> { throw new UnsupportedOperationException(); }, null);
190-
};
191-
DocumentMapperParser parser = new DocumentMapperParser(indexService.getIndexSettings(), indexService.mapperService(),
192-
indexService.xContentRegistry(), indexService.similarityService(), mapperRegistry, queryShardContext);
193-
194-
DocumentMapper documentMapper = parser.parse("type", new CompressedXContent(
195-
Strings
196-
.toString(XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
197-
.startObject("field")
198-
.field("type", ExternalMapperPlugin.EXTERNAL)
199-
.startObject("fields")
200-
.startObject("field")
201-
.field("type", "text")
202-
.startObject("fields")
203-
.startObject("generated")
204-
.field("type", ExternalMapperPlugin.EXTERNAL_BIS)
205-
.endObject()
206-
.startObject("raw")
207-
.field("type", "text")
208-
.endObject()
209-
.endObject()
210-
.endObject()
211-
.startObject("raw")
212-
.field("type", "text")
213-
.endObject()
214-
.endObject()
215-
.endObject()
216-
.endObject().endObject().endObject())));
217-
218-
ParsedDocument doc = documentMapper.parse(new SourceToParse("test", "type", "1", BytesReference
219-
.bytes(XContentFactory.jsonBuilder()
220-
.startObject()
221-
.field("field", "1234")
222-
.endObject()),
223-
XContentType.JSON));
224-
225-
assertThat(doc.rootDoc().getField("field.bool"), notNullValue());
226-
assertThat(doc.rootDoc().getField("field.bool").stringValue(), is("T"));
227-
228-
assertThat(doc.rootDoc().getField("field.point"), notNullValue());
229-
230-
assertThat(doc.rootDoc().getField("field.shape"), notNullValue());
231-
232-
assertThat(doc.rootDoc().getField("field.field"), notNullValue());
233-
assertThat(doc.rootDoc().getField("field.field").stringValue(), is("foo"));
234-
235-
assertThat(doc.rootDoc().getField("field.field.generated.generated"), notNullValue());
236-
assertThat(doc.rootDoc().getField("field.field.generated.generated").stringValue(), is("bar"));
237-
238-
assertThat(doc.rootDoc().getField("field.field.raw"), notNullValue());
239-
assertThat(doc.rootDoc().getField("field.field.raw").stringValue(), is("foo"));
240-
241-
assertThat(doc.rootDoc().getField("field.raw"), notNullValue());
242-
assertThat(doc.rootDoc().getField("field.raw").stringValue(), is("foo"));
243-
244-
assertWarnings("At least one multi-field, [field], was " +
245-
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
246-
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
247-
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
248-
"switching to [copy_to] if appropriate.");
249160
}
250161
}

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

+2-8
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,8 @@ public void testExternalValuesWithMultifield() throws Exception {
139139
.field("type", ExternalMapperPlugin.EXTERNAL_UPPER)
140140
.startObject("fields")
141141
.startObject("g")
142-
.field("type", "text")
142+
.field("type", "keyword")
143143
.field("store", true)
144-
.startObject("fields")
145-
.startObject("raw")
146-
.field("type", "keyword")
147-
.field("store", true)
148-
.endObject()
149-
.endObject()
150144
.endObject()
151145
.endObject()
152146
.endObject()
@@ -156,7 +150,7 @@ public void testExternalValuesWithMultifield() throws Exception {
156150
refresh();
157151

158152
SearchResponse response = client().prepareSearch("test-idx")
159-
.setQuery(QueryBuilders.termQuery("f.g.raw", "FOO BAR"))
153+
.setQuery(QueryBuilders.termQuery("f.g", "FOO BAR"))
160154
.execute().actionGet();
161155

162156
assertThat(response.getHits().getTotalHits().value, equalTo((long) 1));

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

+28-10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.elasticsearch.index.analysis.NamedAnalyzer;
4040
import org.elasticsearch.index.analysis.TokenFilterFactory;
4141
import org.elasticsearch.test.ESTestCase;
42+
import org.elasticsearch.test.VersionUtils;
4243

4344
import java.io.IOException;
4445
import java.util.Collections;
@@ -48,6 +49,7 @@
4849
import static org.elasticsearch.index.analysis.AnalysisRegistry.DEFAULT_ANALYZER_NAME;
4950
import static org.elasticsearch.index.analysis.AnalysisRegistry.DEFAULT_SEARCH_ANALYZER_NAME;
5051
import static org.elasticsearch.index.analysis.AnalysisRegistry.DEFAULT_SEARCH_QUOTED_ANALYZER_NAME;
52+
import static org.hamcrest.core.IsEqual.equalTo;
5153
import static org.mockito.Mockito.mock;
5254
import static org.mockito.Mockito.when;
5355

@@ -179,19 +181,35 @@ public void testMultiFieldWithinMultiField() throws IOException {
179181
.endObject()
180182
.endObject();
181183

184+
Mapper.TypeParser typeParser = new KeywordFieldMapper.TypeParser();
185+
186+
// For indices created prior to 8.0, we should only emit a warning and not fail parsing.
182187
Map<String, Object> fieldNode = XContentHelper.convertToMap(
183188
BytesReference.bytes(mapping), true, mapping.contentType()).v2();
184189

185-
Mapper.TypeParser typeParser = new KeywordFieldMapper.TypeParser();
186-
Mapper.TypeParser.ParserContext parserContext = new Mapper.TypeParser.ParserContext("type",
187-
null, null, type -> typeParser, Version.CURRENT, null);
188-
189-
TypeParsers.parseField(builder, "some-field", fieldNode, parserContext);
190-
assertWarnings("At least one multi-field, [sub-field], was " +
191-
"encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated and will " +
192-
"no longer be supported in 8.0. To resolve the issue, all instances of [fields] that occur within a [fields] block " +
193-
"should be removed from the mappings, either by flattening the chained [fields] blocks into a single level, or " +
194-
"switching to [copy_to] if appropriate.");
190+
Version olderVersion = VersionUtils.randomPreviousCompatibleVersion(random(), Version.V_8_0_0);
191+
Mapper.TypeParser.ParserContext olderContext = new Mapper.TypeParser.ParserContext("type",
192+
null, null, type -> typeParser, olderVersion, null);
193+
194+
TypeParsers.parseField(builder, "some-field", fieldNode, olderContext);
195+
assertWarnings("At least one multi-field, [sub-field], " +
196+
"was encountered that itself contains a multi-field. Defining multi-fields within a multi-field is deprecated " +
197+
"and is not supported for indices created in 8.0 and later. To migrate the mappings, all instances of [fields] " +
198+
"that occur within a [fields] block should be removed from the mappings, either by flattening the chained " +
199+
"[fields] blocks into a single level, or switching to [copy_to] if appropriate.");
200+
201+
// For indices created in 8.0 or later, we should throw an error.
202+
Map<String, Object> fieldNodeCopy = XContentHelper.convertToMap(
203+
BytesReference.bytes(mapping), true, mapping.contentType()).v2();
204+
205+
Version version = VersionUtils.randomVersionBetween(random(), Version.V_8_0_0, Version.CURRENT);
206+
Mapper.TypeParser.ParserContext context = new Mapper.TypeParser.ParserContext("type",
207+
null, null, type -> typeParser, version, null);
208+
209+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
210+
() -> TypeParsers.parseField(builder, "some-field", fieldNodeCopy, context));
211+
assertThat(e.getMessage(), equalTo("Encountered a multi-field [sub-field] which itself contains a " +
212+
"multi-field. Defining chained multi-fields is not supported."));
195213
}
196214

197215
private Analyzer createAnalyzerWithMode(String name, AnalysisMode mode) {

0 commit comments

Comments
 (0)