Skip to content

Commit 0f54973

Browse files
authored
Add support for field aliases in the index mapping.
Original Pull Request #2847 Closes #2845
1 parent e9ecebd commit 0f54973

File tree

7 files changed

+137
-13
lines changed

7 files changed

+137
-13
lines changed

src/main/antora/modules/ROOT/pages/elasticsearch/elasticsearch-new.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Add shard statistics to the `SearchHit` class.
1010
* Add support for multi search template API.
1111
* Add support for SpEL in @Query.
12+
* Add support for field aliases in the index mapping.
1213

1314
[[new-features.5-2-0]]
1415
== New in Spring Data Elasticsearch 5.2

src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
*/
7575
String runtimeFieldsPath() default "";
7676

77+
/**
78+
* field alias definitions to be written to the index mapping
79+
*
80+
* @since 5.3
81+
*/
82+
MappingAlias[] aliases() default {};
83+
7784
enum Detection {
7885
DEFAULT, TRUE, FALSE;
7986
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Inherited;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Defines a field alias in the index mapping.
27+
*
28+
* @author Peter-Josef Meisch
29+
* @since 5.3
30+
*/
31+
@Retention(RetentionPolicy.RUNTIME)
32+
@Target(ElementType.FIELD)
33+
@Documented
34+
@Inherited
35+
public @interface MappingAlias {
36+
/**
37+
* the name of the alias.
38+
*/
39+
String name();
40+
41+
/**
42+
* the path of the alias.
43+
*/
44+
String path();
45+
}

src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentE
214214
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
215215
throws IOException {
216216

217-
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
218-
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
217+
var mappingAnnotation = entity != null ? entity.findAnnotation(Mapping.class) : null;
218+
219+
if (mappingAnnotation != null) {
219220

220221
if (!mappingAnnotation.enabled()) {
221222
objectNode.put(MAPPING_ENABLED, false);
@@ -289,6 +290,16 @@ private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentE
289290
LOGGER.warn(String.format("error mapping property with name %s", property.getName()), e);
290291
}
291292
});
293+
294+
}
295+
296+
// write the alias entries after the properties
297+
if (mappingAnnotation != null) {
298+
for (MappingAlias mappingAlias : mappingAnnotation.aliases()) {
299+
var aliasNode = propertiesNode.putObject(mappingAlias.name());
300+
aliasNode.put(FIELD_PARAM_TYPE, FIELD_PARAM_TYPE_ALIAS);
301+
aliasNode.put(FIELD_PARAM_PATH, mappingAlias.path());
302+
}
292303
}
293304
}
294305

src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public final class MappingParameters {
8484
static final String FIELD_PARAM_SIMILARITY = "similarity";
8585
static final String FIELD_PARAM_TERM_VECTOR = "term_vector";
8686
static final String FIELD_PARAM_TYPE = "type";
87+
static final String FIELD_PARAM_PATH = "path";
88+
static final String FIELD_PARAM_TYPE_ALIAS = "alias";
8789

8890
private final String analyzer;
8991
private final boolean coerce;

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818

1919
import static org.assertj.core.api.Assertions.*;
2020
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
21-
import static org.springframework.data.elasticsearch.annotations.FieldType.Object;
2221

23-
import java.lang.Integer;
24-
import java.lang.Object;
2522
import java.time.Instant;
2623
import java.time.LocalDate;
2724
import java.util.Collection;
@@ -269,6 +266,12 @@ void shouldWriteCorrectMappingForDenseVectorProperty() {
269266
operations.indexOps(SimilarityEntity.class).createWithMapping();
270267
}
271268

269+
@Test // #2845
270+
@DisplayName("should write mapping with field aliases")
271+
void shouldWriteMappingWithFieldAliases() {
272+
operations.indexOps(FieldAliasEntity.class).createWithMapping();
273+
}
274+
272275
// region Entities
273276
@Document(indexName = "#{@indexNameProvider.indexName()}")
274277
static class Book {
@@ -908,5 +911,19 @@ static class SimilarityEntity {
908911
@Field(type = FieldType.Dense_Vector, dims = 42, similarity = "cosine") private double[] denseVector;
909912
}
910913

914+
@Mapping(aliases = {
915+
@MappingAlias(name = "someAlly", path = "someText"),
916+
@MappingAlias(name = "otherAlly", path = "otherText")
917+
})
918+
@Document(indexName = "#{@indexNameProvider.indexName()}")
919+
private static class FieldAliasEntity {
920+
@Id
921+
@Nullable private String id;
922+
@Nullable
923+
@Field(type = Text) private String someText;
924+
@Nullable
925+
@Field(type = Text) private String otherText;
926+
}
927+
911928
// endregion
912929
}

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@
1919
import static org.assertj.core.api.Assertions.*;
2020
import static org.skyscreamer.jsonassert.JSONAssert.*;
2121
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
22-
import static org.springframework.data.elasticsearch.annotations.FieldType.Object;
2322

24-
import java.lang.Boolean;
25-
import java.lang.Double;
26-
import java.lang.Integer;
27-
import java.lang.Object;
2823
import java.math.BigDecimal;
2924
import java.time.Instant;
3025
import java.time.LocalDate;
@@ -1179,6 +1174,39 @@ void shouldUseCustomNameWithDots() throws JSONException {
11791174
assertEquals(expected, mapping, true);
11801175
}
11811176

1177+
@Test // #2845
1178+
@DisplayName("should write field aliases to the mapping")
1179+
void shouldWriteFieldAliasesToTheMapping() throws JSONException {
1180+
1181+
var expected = """
1182+
{
1183+
"properties": {
1184+
"_class": {
1185+
"type": "keyword",
1186+
"index": false,
1187+
"doc_values": false
1188+
},
1189+
"someText": {
1190+
"type": "text"
1191+
},
1192+
"otherText": {
1193+
"type": "text"
1194+
},
1195+
"someAlly": {
1196+
"type": "alias",
1197+
"path": "someText"
1198+
},
1199+
"otherAlly": {
1200+
"type": "alias",
1201+
"path": "otherText"
1202+
}
1203+
}
1204+
}
1205+
""";
1206+
String mapping = getMappingBuilder().buildPropertyMapping(FieldAliasEntity.class);
1207+
1208+
assertEquals(expected, mapping, true);
1209+
}
11821210
// region entities
11831211

11841212
@Document(indexName = "ignore-above-index")
@@ -1298,8 +1326,8 @@ static class MultiFieldEntity {
12981326

12991327
@Nullable
13001328
@MultiField(mainField = @Field(name = "alternate-description", type = FieldType.Text, analyzer = "whitespace"),
1301-
otherFields = {
1302-
@InnerField(suffix = "suff-ix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) //
1329+
otherFields = {
1330+
@InnerField(suffix = "suff-ix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) //
13031331
public String getAlternateDescription() {
13041332
return alternateDescription;
13051333
}
@@ -1662,7 +1690,7 @@ static class GeoEntity {
16621690
@GeoPointField private String pointC;
16631691
@Nullable
16641692
@GeoPointField private double[] pointD;
1665-
// geo shape, until e have the classes for this, us a strng
1693+
16661694
@Nullable
16671695
@GeoShapeField private String shape1;
16681696
@Nullable
@@ -2390,5 +2418,18 @@ private static class FieldNameDotsEntity {
23902418
@Nullable
23912419
@Field(name = "dotted.field", type = Text) private String dottedField;
23922420
}
2421+
2422+
@Mapping(aliases = {
2423+
@MappingAlias(name = "someAlly", path = "someText"),
2424+
@MappingAlias(name = "otherAlly", path = "otherText")
2425+
})
2426+
private static class FieldAliasEntity {
2427+
@Id
2428+
@Nullable private String id;
2429+
@Nullable
2430+
@Field(type = Text) private String someText;
2431+
@Nullable
2432+
@Field(type = Text) private String otherText;
2433+
}
23932434
// endregion
23942435
}

0 commit comments

Comments
 (0)