Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
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;
Expand Down Expand Up @@ -64,7 +63,13 @@ default String convertId(Object idValue) {
return idValue.toString();
}

return getConversionService().convert(idValue, String.class);
String converted = getConversionService().convert(idValue, String.class);

if (converted == null) {
return idValue.toString();
}

return converted;
}

/**
Expand All @@ -86,29 +91,15 @@ default Document mapObject(@Nullable Object source) {

// 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) {

if (domainClass != null && query instanceof CriteriaQuery) {
updateCriteriaQuery((CriteriaQuery) query, domainClass);
}
}
void updateQuery(Query query, @Nullable Class<?> 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@
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;
Expand Down Expand Up @@ -1024,7 +1027,61 @@ private static Collection<?> asCollection(Object source) {

// 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ public List<String> getFields() {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ static Query findAll() {
*/
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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +24,7 @@
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;
Expand All @@ -44,6 +44,9 @@
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;

/**
Expand Down Expand Up @@ -356,9 +359,7 @@ void shouldMapNamesAndValueInNestedEntities() throws JSONException {
" }\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();

Expand Down Expand Up @@ -389,9 +390,7 @@ void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException
" }\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();

Expand All @@ -417,14 +416,33 @@ void shouldMapNamesAndValueInObjectEntities() throws JSONException {
" }\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
Expand All @@ -442,24 +460,21 @@ static class Person {
@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;
}

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 {
Expand Down