Skip to content

Commit 0fe8493

Browse files
committed
Enforce that field aliases can only be specified on indexes with a single type.
1 parent 457d204 commit 0fe8493

File tree

12 files changed

+131
-42
lines changed

12 files changed

+131
-42
lines changed

docs/reference/mapping/types/alias.asciidoc

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[[alias]]
22
=== Alias datatype
33

4+
NOTE: Field aliases can only be specified on indexes with a single mapping type. To add a field
5+
alias, the index must therefore have been created in 6.0 or later, or be an older index with
6+
the setting `index.mapping.single_type: true`. Please see <<removal-of-types>> for more information.
7+
48
An `alias` mapping defines an alternate name for a field in the index.
59
The alias can be used in place of the target field in <<search, search>> requests,
610
and selected other APIs like <<search-field-caps, field capabilities>>.

modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,20 @@ protected Collection<Class<? extends Plugin>> getPlugins() {
9797
@Override
9898
protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
9999
queryField = randomAlphaOfLength(4);
100-
aliasField = randomAlphaOfLength(4);
101100

102101
String docType = "_doc";
103102
mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
104-
queryField, "type=percolator", aliasField, "type=alias,path=" + queryField
103+
queryField, "type=percolator"
105104
))), MapperService.MergeReason.MAPPING_UPDATE, false);
105+
106+
// Field aliases are only supported on indexes with a single type.
107+
if (mapperService.getIndexSettings().isSingleType()) {
108+
aliasField = randomAlphaOfLength(4);
109+
mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
110+
aliasField, "type=alias,path=" + queryField
111+
))), MapperService.MergeReason.MAPPING_UPDATE, false);
112+
}
113+
106114
mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
107115
STRING_FIELD_NAME, "type=text"
108116
))), MapperService.MergeReason.MAPPING_UPDATE, false);
@@ -369,6 +377,8 @@ public void testSerializationFailsUnlessFetched() throws IOException {
369377
}
370378

371379
public void testFieldAlias() throws IOException {
380+
assumeTrue("Test only runs when there is a single mapping type.", isSingleType());
381+
372382
QueryShardContext shardContext = createShardContext();
373383

374384
PercolateQueryBuilder builder = doCreateTestQueryBuilder(false);

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

+14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.common.xcontent.XContentBuilder;
2323
import org.elasticsearch.common.xcontent.support.XContentMapValues;
24+
import org.elasticsearch.index.IndexSettings;
2425

2526
import java.io.IOException;
2627
import java.util.Collections;
@@ -74,6 +75,10 @@ public Mapper merge(Mapper mergeWith, boolean updateAllTypes) {
7475
return mergeWith;
7576
}
7677

78+
/**
79+
* Note: this method is a no-op because field aliases cannot be specified on indexes
80+
* with more than one type.
81+
*/
7782
@Override
7883
public Mapper updateFieldType(Map<String, MappedFieldType> fullNameToFieldType) {
7984
return this;
@@ -96,6 +101,8 @@ public static class TypeParser implements Mapper.TypeParser {
96101
@Override
97102
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext)
98103
throws MapperParsingException {
104+
checkIndexCompatibility(parserContext.mapperService().getIndexSettings(), name);
105+
99106
FieldAliasMapper.Builder builder = new FieldAliasMapper.Builder(name);
100107
Object pathField = node.remove(Names.PATH);
101108
String path = XContentMapValues.nodeStringValue(pathField, null);
@@ -104,6 +111,13 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
104111
}
105112
return builder.path(path);
106113
}
114+
115+
private static void checkIndexCompatibility(IndexSettings settings, String name) {
116+
if (!settings.isSingleType()) {
117+
throw new IllegalStateException("Cannot create a field alias [" + name + "] " +
118+
"on index [" + settings.getIndex().getName() + "], as it has multiple types.");
119+
}
120+
}
107121
}
108122

109123
public static class Builder extends Mapper.Builder<FieldAliasMapper.Builder, FieldAliasMapper> {

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

+39
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,32 @@
1919

2020
package org.elasticsearch.index.mapper;
2121

22+
import org.elasticsearch.Version;
23+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2224
import org.elasticsearch.common.Strings;
2325
import org.elasticsearch.common.compress.CompressedXContent;
26+
import org.elasticsearch.common.settings.Settings;
2427
import org.elasticsearch.common.xcontent.XContentFactory;
2528
import org.elasticsearch.index.IndexService;
2629
import org.elasticsearch.index.mapper.MapperService.MergeReason;
30+
import org.elasticsearch.plugins.Plugin;
2731
import org.elasticsearch.test.ESSingleNodeTestCase;
32+
import org.elasticsearch.test.InternalSettingsPlugin;
33+
import org.elasticsearch.test.VersionUtils;
2834
import org.junit.Before;
2935

3036
import java.io.IOException;
37+
import java.util.Collection;
3138

3239
public class FieldAliasMapperTests extends ESSingleNodeTestCase {
3340
private MapperService mapperService;
3441
private DocumentMapperParser parser;
3542

43+
@Override
44+
protected Collection<Class<? extends Plugin>> getPlugins() {
45+
return pluginList(InternalSettingsPlugin.class);
46+
}
47+
3648
@Before
3749
public void setup() {
3850
IndexService indexService = createIndex("test");
@@ -164,4 +176,31 @@ public void testMergeFailure() throws IOException {
164176
assertEquals("Cannot merge a field alias mapping [alias-field] with a mapping that is not for a field alias.",
165177
exception.getMessage());
166178
}
179+
180+
public void testFieldAliasDisallowedWithMultipleTypes() throws IOException {
181+
Version version = VersionUtils.randomVersionBetween(random(), null, Version.V_5_6_0);
182+
Settings settings = Settings.builder()
183+
.put(IndexMetaData.SETTING_VERSION_CREATED, version)
184+
.build();
185+
IndexService indexService = createIndex("alias-test", settings);
186+
DocumentMapperParser parser = indexService.mapperService().documentMapperParser();
187+
188+
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject()
189+
.startObject("type")
190+
.startObject("properties")
191+
.startObject("alias-field")
192+
.field("type", "alias")
193+
.field("path", "concrete-field")
194+
.endObject()
195+
.startObject("concrete-field")
196+
.field("type", "keyword")
197+
.endObject()
198+
.endObject()
199+
.endObject()
200+
.endObject());
201+
IllegalStateException e = expectThrows(IllegalStateException.class,
202+
() -> parser.parse("type", new CompressedXContent(mapping)));
203+
assertEquals("Cannot create a field alias [alias-field] on index [alias-test],"
204+
+ " as it has multiple types.", e.getMessage());
205+
}
167206
}

server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,12 @@ public void testToQueryFieldsWildcard() throws Exception {
235235
DisjunctionMaxQuery dQuery = (DisjunctionMaxQuery) query;
236236
assertThat(dQuery.getTieBreakerMultiplier(), equalTo(1.0f));
237237
assertThat(dQuery.getDisjuncts().size(), equalTo(3));
238-
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test")));
239-
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test")));
240-
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 2).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test")));
238+
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 0).getTerm(),
239+
equalTo(new Term(expectedFieldName(STRING_ALIAS_FIELD_NAME), "test")));
240+
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 1).getTerm(),
241+
equalTo(new Term(STRING_FIELD_NAME_2, "test")));
242+
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 2).getTerm(),
243+
equalTo(new Term(STRING_FIELD_NAME, "test")));
241244
}
242245

243246
public void testToQueryFieldMissing() throws Exception {

server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ public void testToQueryFieldsWildcard() throws Exception {
507507
DisjunctionMaxQuery dQuery = (DisjunctionMaxQuery) query;
508508
assertThat(dQuery.getDisjuncts().size(), equalTo(3));
509509
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 0).getTerm(),
510-
equalTo(new Term(STRING_FIELD_NAME, "test")));
510+
equalTo(new Term(expectedFieldName(STRING_ALIAS_FIELD_NAME), "test")));
511511
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 1).getTerm(),
512512
equalTo(new Term(STRING_FIELD_NAME_2, "test")));
513513
assertThat(assertDisjunctionSubQuery(query, TermQuery.class, 2).getTerm(),

server/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ protected void doAssertLuceneQuery(RangeQueryBuilder queryBuilder, Query query,
150150

151151
} else if (getCurrentTypes().length == 0 ||
152152
(expectedFieldName.equals(DATE_FIELD_NAME) == false
153+
&& expectedFieldName.equals(DATE_ALIAS_FIELD_NAME) == false
153154
&& expectedFieldName.equals(INT_FIELD_NAME) == false
155+
&& expectedFieldName.equals(INT_ALIAS_FIELD_NAME) == false
154156
&& expectedFieldName.equals(DATE_RANGE_FIELD_NAME) == false
155157
&& expectedFieldName.equals(INT_RANGE_FIELD_NAME) == false)) {
156158
assertThat(query, instanceOf(TermRangeQuery.class));

server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java

+13-8
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,16 @@ protected void initializeAdditionalMappings(MapperService mapperService) throws
6262
.startObject("prefix_field")
6363
.field("type", "text")
6464
.startObject("index_prefixes").endObject()
65-
.endObject()
66-
.startObject("prefix_field_alias")
67-
.field("type", "alias")
68-
.field("path", "prefix_field")
69-
.endObject()
70-
.endObject().endObject().endObject();
71-
65+
.endObject();
66+
67+
// Field aliases are only supported on indexes with a single type.
68+
if (mapperService.getIndexSettings().isSingleType()) {
69+
mapping.startObject("prefix_field_alias")
70+
.field("type", "alias")
71+
.field("path", "prefix_field")
72+
.endObject();
73+
}
74+
mapping.endObject().endObject().endObject();
7275
mapperService.merge("_doc",
7376
new CompressedXContent(Strings.toString(mapping)), MapperService.MergeReason.MAPPING_UPDATE, true);
7477
}
@@ -178,7 +181,9 @@ public void testToQueryInnerSpanMultiTerm() throws IOException {
178181
}
179182

180183
public void testToQueryInnerTermQuery() throws IOException {
181-
String fieldName = randomFrom("prefix_field", "prefix_field_alias");
184+
String fieldName = isSingleType()
185+
? randomFrom("prefix_field", "prefix_field_alias")
186+
: "prefix_field";
182187
final QueryShardContext context = createShardContext();
183188
if (context.getIndexSettings().getIndexVersionCreated().onOrAfter(Version.V_6_4_0)) {
184189
Query query = new SpanMultiTermQueryBuilder(new PrefixQueryBuilder(fieldName, "foo"))

server/src/test/java/org/elasticsearch/index/query/TermsSetQueryBuilderTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ public void testDoToQuery_msmScriptField() throws Exception {
263263
}
264264

265265
public void testFieldAlias() {
266+
assumeTrue("Test runs only when there is a single mapping type.", isSingleType());
267+
266268
List<String> randomTerms = Arrays.asList(generateRandomStringArray(5, 10, false, false));
267269
TermsSetQueryBuilder queryBuilder = new TermsSetQueryBuilder(STRING_ALIAS_FIELD_NAME, randomTerms)
268270
.setMinimumShouldMatchField("m_s_m");

server/src/test/java/org/elasticsearch/indices/mapping/SimpleGetFieldMappingsIT.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ private XContentBuilder getMappingForType(String type) throws IOException {
7777
.startObject("field1")
7878
.field("type", "text")
7979
.endObject()
80-
.startObject("alias")
81-
.field("type", "alias")
82-
.field("path", "field1")
83-
.endObject()
8480
.startObject("obj")
8581
.startObject("properties")
8682
.startObject("subfield")
@@ -232,7 +228,18 @@ public void testSimpleGetFieldMappingsWithDefaults() throws Exception {
232228

233229
@SuppressWarnings("unchecked")
234230
public void testGetFieldMappingsWithFieldAlias() throws Exception {
235-
assertAcked(prepareCreate("test").addMapping("type", getMappingForType("type")));
231+
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
232+
.startObject("properties")
233+
.startObject("field1")
234+
.field("type", "text")
235+
.endObject()
236+
.startObject("alias")
237+
.field("type", "alias")
238+
.field("path", "field1")
239+
.endObject()
240+
.endObject()
241+
.endObject();
242+
assertAcked(prepareCreate("test").addMapping("type", mapping));
236243

237244
GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings()
238245
.setFields("alias", "field1").get();

server/src/test/java/org/elasticsearch/search/geo/GeoPolygonIT.java

+1-18
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,12 @@
1919

2020
package org.elasticsearch.search.geo;
2121

22-
import org.elasticsearch.Version;
2322
import org.elasticsearch.action.search.SearchResponse;
24-
import org.elasticsearch.cluster.metadata.IndexMetaData;
2523
import org.elasticsearch.common.geo.GeoPoint;
26-
import org.elasticsearch.common.settings.Settings;
27-
import org.elasticsearch.plugins.Plugin;
2824
import org.elasticsearch.search.SearchHit;
2925
import org.elasticsearch.test.ESIntegTestCase;
30-
import org.elasticsearch.test.InternalSettingsPlugin;
31-
import org.elasticsearch.test.VersionUtils;
3226

3327
import java.util.ArrayList;
34-
import java.util.Arrays;
35-
import java.util.Collection;
3628
import java.util.List;
3729

3830
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
@@ -46,18 +38,9 @@
4638
@ESIntegTestCase.SuiteScopeTestCase
4739
public class GeoPolygonIT extends ESIntegTestCase {
4840

49-
@Override
50-
protected Collection<Class<? extends Plugin>> nodePlugins() {
51-
return Arrays.asList(InternalSettingsPlugin.class); // uses index.version.created
52-
}
53-
5441
@Override
5542
protected void setupSuiteScopeCluster() throws Exception {
56-
Version version = VersionUtils.randomVersionBetween(random(), Version.V_5_0_0,
57-
Version.CURRENT);
58-
Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, version).build();
59-
60-
assertAcked(prepareCreate("test").setSettings(settings).addMapping("type1", "location",
43+
assertAcked(prepareCreate("test").addMapping("type1", "location",
6144
"type=geo_point", "alias",
6245
"type=alias,path=location"));
6346
ensureGreen();

test/framework/src/main/java/org/elasticsearch/test/AbstractBuilderTestCase.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ protected static String[] getCurrentTypes() {
143143
return currentTypes;
144144
}
145145

146+
protected static boolean isSingleType() {
147+
return serviceHolder.idxSettings.isSingleType();
148+
}
149+
146150
protected Collection<Class<? extends Plugin>> getPlugins() {
147151
return Collections.emptyList();
148152
}
@@ -217,7 +221,7 @@ protected Settings indexSettings() {
217221
}
218222

219223
protected static String expectedFieldName(String builderFieldName) {
220-
if (currentTypes.length == 0) {
224+
if (currentTypes.length == 0 || !isSingleType()) {
221225
return builderFieldName;
222226
}
223227
return ALIAS_TO_CONCRETE_FIELD_NAME.getOrDefault(builderFieldName, builderFieldName);
@@ -384,20 +388,36 @@ public void onRemoval(ShardId shardId, Accountable accountable) {
384388
mapperService.merge(type, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(type,
385389
STRING_FIELD_NAME, "type=text",
386390
STRING_FIELD_NAME_2, "type=keyword",
387-
STRING_ALIAS_FIELD_NAME, "type=alias,path=" + STRING_FIELD_NAME,
388391
INT_FIELD_NAME, "type=integer",
389-
INT_ALIAS_FIELD_NAME, "type=alias,path=" + INT_FIELD_NAME,
390392
INT_RANGE_FIELD_NAME, "type=integer_range",
391393
DOUBLE_FIELD_NAME, "type=double",
392394
BOOLEAN_FIELD_NAME, "type=boolean",
393395
DATE_FIELD_NAME, "type=date",
394-
DATE_ALIAS_FIELD_NAME, "type=alias,path=" + DATE_FIELD_NAME,
395396
DATE_RANGE_FIELD_NAME, "type=date_range",
396397
OBJECT_FIELD_NAME, "type=object",
397398
GEO_POINT_FIELD_NAME, "type=geo_point",
398-
GEO_POINT_ALIAS_FIELD_NAME, "type=alias,path=" + GEO_POINT_FIELD_NAME,
399399
GEO_SHAPE_FIELD_NAME, "type=geo_shape"
400400
))), MapperService.MergeReason.MAPPING_UPDATE, false);
401+
402+
// Field aliases are only supported on indexes with a single type. If the index has multiple types, we
403+
// still create fields with the same names as the alias fields, but with a concrete definition. This
404+
// avoids the need for various test classes to check whether the index contains a single type.
405+
if (idxSettings.isSingleType()) {
406+
mapperService.merge(type, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(type,
407+
STRING_ALIAS_FIELD_NAME, "type=alias,path=" + STRING_FIELD_NAME,
408+
INT_ALIAS_FIELD_NAME, "type=alias,path=" + INT_FIELD_NAME,
409+
DATE_ALIAS_FIELD_NAME, "type=alias,path=" + DATE_FIELD_NAME,
410+
GEO_POINT_ALIAS_FIELD_NAME, "type=alias,path=" + GEO_POINT_FIELD_NAME
411+
))), MapperService.MergeReason.MAPPING_UPDATE, false);
412+
} else {
413+
mapperService.merge(type, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(type,
414+
STRING_ALIAS_FIELD_NAME, "type=text",
415+
INT_ALIAS_FIELD_NAME, "type=integer",
416+
DATE_ALIAS_FIELD_NAME, "type=date",
417+
GEO_POINT_ALIAS_FIELD_NAME, "type=geo_point"
418+
))), MapperService.MergeReason.MAPPING_UPDATE, false);
419+
}
420+
401421
// also add mappings for two inner field in the object field
402422
mapperService.merge(type, new CompressedXContent("{\"properties\":{\"" + OBJECT_FIELD_NAME + "\":{\"type\":\"object\","
403423
+ "\"properties\":{\"" + DATE_FIELD_NAME + "\":{\"type\":\"date\"},\"" +

0 commit comments

Comments
 (0)