Add support for field aliases in the index mapping.

Original Pull Request #2847
Closes #2845
This commit is contained in:
Peter-Josef Meisch 2024-02-07 20:26:17 +01:00 committed by GitHub
parent e9ecebd9ef
commit 0f5497338a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 137 additions and 13 deletions

View File

@ -9,6 +9,7 @@
* Add shard statistics to the `SearchHit` class.
* Add support for multi search template API.
* Add support for SpEL in @Query.
* Add support for field aliases in the index mapping.
[[new-features.5-2-0]]
== New in Spring Data Elasticsearch 5.2

View File

@ -74,6 +74,13 @@ public @interface Mapping {
*/
String runtimeFieldsPath() default "";
/**
* field alias definitions to be written to the index mapping
*
* @since 5.3
*/
MappingAlias[] aliases() default {};
enum Detection {
DEFAULT, TRUE, FALSE;
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines a field alias in the index mapping.
*
* @author Peter-Josef Meisch
* @since 5.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MappingAlias {
/**
* the name of the alias.
*/
String name();
/**
* the path of the alias.
*/
String path();
}

View File

@ -214,8 +214,9 @@ public class MappingBuilder {
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
throws IOException {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
var mappingAnnotation = entity != null ? entity.findAnnotation(Mapping.class) : null;
if (mappingAnnotation != null) {
if (!mappingAnnotation.enabled()) {
objectNode.put(MAPPING_ENABLED, false);
@ -289,6 +290,16 @@ public class MappingBuilder {
LOGGER.warn(String.format("error mapping property with name %s", property.getName()), e);
}
});
}
// write the alias entries after the properties
if (mappingAnnotation != null) {
for (MappingAlias mappingAlias : mappingAnnotation.aliases()) {
var aliasNode = propertiesNode.putObject(mappingAlias.name());
aliasNode.put(FIELD_PARAM_TYPE, FIELD_PARAM_TYPE_ALIAS);
aliasNode.put(FIELD_PARAM_PATH, mappingAlias.path());
}
}
}

View File

@ -84,6 +84,8 @@ public final class MappingParameters {
static final String FIELD_PARAM_SIMILARITY = "similarity";
static final String FIELD_PARAM_TERM_VECTOR = "term_vector";
static final String FIELD_PARAM_TYPE = "type";
static final String FIELD_PARAM_PATH = "path";
static final String FIELD_PARAM_TYPE_ALIAS = "alias";
private final String analyzer;
private final boolean coerce;

View File

@ -18,10 +18,7 @@ package org.springframework.data.elasticsearch.core.index;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.Object;
import java.lang.Integer;
import java.lang.Object;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Collection;
@ -269,6 +266,12 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
operations.indexOps(SimilarityEntity.class).createWithMapping();
}
@Test // #2845
@DisplayName("should write mapping with field aliases")
void shouldWriteMappingWithFieldAliases() {
operations.indexOps(FieldAliasEntity.class).createWithMapping();
}
// region Entities
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Book {
@ -908,5 +911,19 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
@Field(type = FieldType.Dense_Vector, dims = 42, similarity = "cosine") private double[] denseVector;
}
@Mapping(aliases = {
@MappingAlias(name = "someAlly", path = "someText"),
@MappingAlias(name = "otherAlly", path = "otherText")
})
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class FieldAliasEntity {
@Id
@Nullable private String id;
@Nullable
@Field(type = Text) private String someText;
@Nullable
@Field(type = Text) private String otherText;
}
// endregion
}

View File

@ -19,12 +19,7 @@ package org.springframework.data.elasticsearch.core.index;
import static org.assertj.core.api.Assertions.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.Object;
import java.lang.Boolean;
import java.lang.Double;
import java.lang.Integer;
import java.lang.Object;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
@ -1179,6 +1174,39 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, true);
}
@Test // #2845
@DisplayName("should write field aliases to the mapping")
void shouldWriteFieldAliasesToTheMapping() throws JSONException {
var expected = """
{
"properties": {
"_class": {
"type": "keyword",
"index": false,
"doc_values": false
},
"someText": {
"type": "text"
},
"otherText": {
"type": "text"
},
"someAlly": {
"type": "alias",
"path": "someText"
},
"otherAlly": {
"type": "alias",
"path": "otherText"
}
}
}
""";
String mapping = getMappingBuilder().buildPropertyMapping(FieldAliasEntity.class);
assertEquals(expected, mapping, true);
}
// region entities
@Document(indexName = "ignore-above-index")
@ -1298,8 +1326,8 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Nullable
@MultiField(mainField = @Field(name = "alternate-description", type = FieldType.Text, analyzer = "whitespace"),
otherFields = {
@InnerField(suffix = "suff-ix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) //
otherFields = {
@InnerField(suffix = "suff-ix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) //
public String getAlternateDescription() {
return alternateDescription;
}
@ -1662,7 +1690,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@GeoPointField private String pointC;
@Nullable
@GeoPointField private double[] pointD;
// geo shape, until e have the classes for this, us a strng
@Nullable
@GeoShapeField private String shape1;
@Nullable
@ -2390,5 +2418,18 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Nullable
@Field(name = "dotted.field", type = Text) private String dottedField;
}
@Mapping(aliases = {
@MappingAlias(name = "someAlly", path = "someText"),
@MappingAlias(name = "otherAlly", path = "otherText")
})
private static class FieldAliasEntity {
@Id
@Nullable private String id;
@Nullable
@Field(type = Text) private String someText;
@Nullable
@Field(type = Text) private String otherText;
}
// endregion
}