Custom property names must be used in SourceFilter and source fields.

Original Pull Request #1780 
Closes #1778
This commit is contained in:
Peter-Josef Meisch 2021-04-18 14:08:45 +02:00 committed by GitHub
parent 7ace63485d
commit 79087c4ada
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 39 deletions

View File

@ -19,7 +19,6 @@ import org.springframework.data.convert.EntityConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@ -64,7 +63,13 @@ public interface ElasticsearchConverter
return idValue.toString();
}
return getConversionService().convert(idValue, String.class);
String converted = getConversionService().convert(idValue, String.class);
if (converted == null) {
return idValue.toString();
}
return converted;
}
/**
@ -86,29 +91,15 @@ public interface ElasticsearchConverter
// region query
/**
* Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the
* values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
* domainClass is null or query is not a {@link CriteriaQuery}, it's a noop.
* domainClass is null it's a noop.
*
* @param query the query that is internally updated
* @param query the query that is internally updated, must not be {@literal null}
* @param domainClass the class of the object that is searched with the query
*/
default void updateQuery(Query query, @Nullable Class<?> domainClass) {
void updateQuery(Query query, @Nullable Class<?> domainClass);
if (domainClass != null && query instanceof CriteriaQuery) {
updateCriteriaQuery((CriteriaQuery) query, domainClass);
}
}
/**
* Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}.
*
* @param criteriaQuery the query that is internally updated, must not be {@literal null}
* @param domainClass the class of the object that is searched with the query, must not be {@literal null}
*/
void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass);
// endregion
}

View File

@ -42,8 +42,11 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.Field;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
@ -1024,7 +1027,61 @@ public class MappingElasticsearchConverter
// region queries
@Override
public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
public void updateQuery(Query query, @Nullable Class<?> domainClass) {
Assert.notNull(query, "query must not be null");
if (domainClass != null) {
updateFieldsAndSourceFilter(query, domainClass);
if (query instanceof CriteriaQuery) {
updateCriteriaQuery((CriteriaQuery) query, domainClass);
}
}
}
private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
if (persistentEntity != null) {
List<String> fields = query.getFields();
if (!fields.isEmpty()) {
query.setFields(updateFieldNames(fields, persistentEntity));
}
SourceFilter sourceFilter = query.getSourceFilter();
if (sourceFilter != null) {
String[] includes = null;
String[] excludes = null;
if (sourceFilter.getIncludes() != null) {
includes = updateFieldNames(Arrays.asList(sourceFilter.getIncludes()), persistentEntity)
.toArray(new String[] {});
}
if (sourceFilter.getExcludes() != null) {
excludes = updateFieldNames(Arrays.asList(sourceFilter.getExcludes()), persistentEntity)
.toArray(new String[] {});
}
query.addSourceFilter(new FetchSourceFilter(includes, excludes));
}
}
}
private List<String> updateFieldNames(List<String> fields, ElasticsearchPersistentEntity<?> persistentEntity) {
return fields.stream().map(fieldName -> {
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
}).collect(Collectors.toList());
}
private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
Assert.notNull(domainClass, "domainClass must not be null");

View File

@ -96,6 +96,15 @@ abstract class AbstractQuery implements Query {
return fields;
}
@Override
public void setFields(List<String> fields) {
Assert.notNull(fields, "fields must not be null");
this.fields.clear();
this.fields.addAll(fields);
}
@Override
public void addSourceFilter(SourceFilter sourceFilter) {
this.sourceFilter = sourceFilter;

View File

@ -103,6 +103,13 @@ public interface Query {
*/
List<String> getFields();
/**
* Set fields to be returned as part of search request
* @param fields must not be {@literal null}
* @since 4.3
*/
void setFields(List<String> fields);
/**
* Add source filter to be added as part of search request
*

View File

@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.assertj.core.api.Assertions.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import java.time.LocalDate;
@ -25,6 +24,7 @@ import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.assertj.core.api.SoftAssertions;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -44,6 +44,9 @@ import org.springframework.data.elasticsearch.core.geo.GeoJsonPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.lang.Nullable;
/**
@ -356,9 +359,7 @@ public class CriteriaQueryMappingUnitTests {
" }\n" + //
"}\n"; //
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))
);
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)));
mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class);
String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
@ -389,9 +390,7 @@ public class CriteriaQueryMappingUnitTests {
" }\n" + //
"}\n"; //
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("persons.nickName.keyword").is("Foobar")
);
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.nickName.keyword").is("Foobar"));
mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class);
String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
@ -417,14 +416,33 @@ public class CriteriaQueryMappingUnitTests {
" }\n" + //
"}\n"; //
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))
);
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)));
mappingElasticsearchConverter.updateQuery(criteriaQuery, ObjectWithPerson.class);
String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
assertEquals(expected, queryString, false);
}
@Test // #1778
@DisplayName("should map names in source fields and SourceFilters")
void shouldMapNamesInSourceFieldsAndSourceFilters() {
Query query = Query.findAll();
// Note: we don't care if these filters make sense here, this test is only about name mapping
query.addFields("firstName", "lastName");
query.addSourceFilter(new FetchSourceFilter(new String[] { "firstName" }, new String[] { "lastName" }));
mappingElasticsearchConverter.updateQuery(query, Person.class);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(query.getFields()).containsExactly("first-name", "last-name");
SourceFilter sourceFilter = query.getSourceFilter();
softly.assertThat(sourceFilter).isNotNull();
softly.assertThat(sourceFilter.getIncludes()).containsExactly("first-name");
softly.assertThat(sourceFilter.getExcludes()).containsExactly("last-name");
softly.assertAll();
}
// endregion
// region helper functions
@ -442,7 +460,8 @@ public class CriteriaQueryMappingUnitTests {
@Nullable @Id String id;
@Nullable @Field(name = "first-name") String firstName;
@Nullable @Field(name = "last-name") String lastName;
@Nullable @MultiField(mainField = @Field(name="nick-name"), otherFields = {@InnerField(suffix = "keyword", type = FieldType.Keyword)}) String nickName;
@Nullable @MultiField(mainField = @Field(name = "nick-name"),
otherFields = { @InnerField(suffix = "keyword", type = FieldType.Keyword) }) String nickName;
@Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate;
@Nullable @Field(name = "birth-date", type = FieldType.Date, format = {},
pattern = "dd.MM.uuuu") LocalDate birthDate;
@ -450,16 +469,12 @@ public class CriteriaQueryMappingUnitTests {
static class House {
@Nullable @Id String id;
@Nullable
@Field(name = "per-sons", type = FieldType.Nested)
List<Person> persons;
@Nullable @Field(name = "per-sons", type = FieldType.Nested) List<Person> persons;
}
static class ObjectWithPerson {
@Nullable @Id String id;
@Nullable
@Field(name = "per-sons", type = FieldType.Object)
List<Person> persons;
@Nullable @Field(name = "per-sons", type = FieldType.Object) List<Person> persons;
}
static class GeoShapeEntity {