mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-01 09:42:11 +00:00
Nested Criteria queries must consider sub-fields.
Original Pull Request #1760 Closes #1758
This commit is contained in:
parent
2bd4ef75cf
commit
ab73c68ca9
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
|||||||
import static org.elasticsearch.index.query.Operator.*;
|
import static org.elasticsearch.index.query.Operator.*;
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||||
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
||||||
|
import static org.springframework.util.StringUtils.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -154,11 +155,8 @@ class CriteriaQueryProcessor {
|
|||||||
|
|
||||||
addBoost(query, criteria.getBoost());
|
addBoost(query, criteria.getBoost());
|
||||||
|
|
||||||
int dotPosition = fieldName.lastIndexOf('.');
|
if (hasText(field.getPath())) {
|
||||||
|
query = nestedQuery(field.getPath(), query, ScoreMode.Avg);
|
||||||
if (dotPosition > 0) {
|
|
||||||
String nestedPath = fieldName.substring(0, dotPosition);
|
|
||||||
query = nestedQuery(nestedPath, query, ScoreMode.Avg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@ -86,21 +86,18 @@ public interface ElasticsearchConverter
|
|||||||
|
|
||||||
// region query
|
// region query
|
||||||
/**
|
/**
|
||||||
* Updates a query by renaming the property names in the query to the correct mapped field names and the values to the
|
* Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and
|
||||||
* converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
||||||
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
|
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
|
||||||
* domainClass is null, it's a noop; handling null here eliminates null checks in the caller.
|
* domainClass is null or query is not a {@link CriteriaQuery}, it's a noop.
|
||||||
*
|
*
|
||||||
* @param query the query that is internally updated
|
* @param query the query that is internally updated
|
||||||
* @param domainClass the class of the object that is searched with the query
|
* @param domainClass the class of the object that is searched with the query
|
||||||
*/
|
*/
|
||||||
default void updateQuery(Query query, @Nullable Class<?> domainClass) {
|
default void updateQuery(Query query, @Nullable Class<?> domainClass) {
|
||||||
|
|
||||||
if (domainClass != null) {
|
if (domainClass != null && query instanceof CriteriaQuery) {
|
||||||
|
updateCriteriaQuery((CriteriaQuery) query, domainClass);
|
||||||
if (query instanceof CriteriaQuery) {
|
|
||||||
updateCriteriaQuery((CriteriaQuery) query, domainClass);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,8 +106,8 @@ public interface ElasticsearchConverter
|
|||||||
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
||||||
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}.
|
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}.
|
||||||
*
|
*
|
||||||
* @param criteriaQuery the query that is internally updated
|
* @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
|
* @param domainClass the class of the object that is searched with the query, must not be {@literal null}
|
||||||
*/
|
*/
|
||||||
void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass);
|
void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass);
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -1025,6 +1025,9 @@ public class MappingElasticsearchConverter
|
|||||||
@Override
|
@Override
|
||||||
public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
||||||
|
|
||||||
|
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
|
||||||
|
Assert.notNull(domainClass, "domainClass must not be null");
|
||||||
|
|
||||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
|
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
|
||||||
|
|
||||||
if (persistentEntity != null) {
|
if (persistentEntity != null) {
|
||||||
@ -1048,12 +1051,15 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
String[] fieldNames = field.getName().split("\\.");
|
String[] fieldNames = field.getName().split("\\.");
|
||||||
|
|
||||||
ElasticsearchPersistentEntity<?> currentEntity = persistentEntity;
|
ElasticsearchPersistentEntity<?> currentEntity = persistentEntity;
|
||||||
ElasticsearchPersistentProperty persistentProperty = null;
|
ElasticsearchPersistentProperty persistentProperty = null;
|
||||||
|
int propertyCount = 0;
|
||||||
for (int i = 0; i < fieldNames.length; i++) {
|
for (int i = 0; i < fieldNames.length; i++) {
|
||||||
persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]);
|
persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]);
|
||||||
|
|
||||||
if (persistentProperty != null) {
|
if (persistentProperty != null) {
|
||||||
|
propertyCount++;
|
||||||
fieldNames[i] = persistentProperty.getFieldName();
|
fieldNames[i] = persistentProperty.getFieldName();
|
||||||
try {
|
try {
|
||||||
currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType());
|
currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType());
|
||||||
@ -1071,6 +1077,11 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
field.setName(String.join(".", fieldNames));
|
field.setName(String.join(".", fieldNames));
|
||||||
|
|
||||||
|
if (propertyCount > 1) {
|
||||||
|
List<String> propertyNames = Arrays.asList(fieldNames);
|
||||||
|
field.setPath(String.join(".", propertyNames.subList(0, propertyCount - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
if (persistentProperty != null) {
|
if (persistentProperty != null) {
|
||||||
|
|
||||||
if (persistentProperty.hasPropertyConverter()) {
|
if (persistentProperty.hasPropertyConverter()) {
|
||||||
|
@ -41,4 +41,17 @@ public interface Field {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
FieldType getFieldType();
|
FieldType getFieldType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the path if this field has a multi-part name that should be used in a nested query.
|
||||||
|
* @param path the value to set
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
void setPath(@Nullable String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the path if this is a field for a nested query
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@Nullable String getPath();
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ public class SimpleField implements Field {
|
|||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
@Nullable private FieldType fieldType;
|
@Nullable private FieldType fieldType;
|
||||||
|
@Nullable private String path;
|
||||||
|
|
||||||
public SimpleField(String name) {
|
public SimpleField(String name) {
|
||||||
|
|
||||||
@ -63,6 +64,17 @@ public class SimpleField implements Field {
|
|||||||
return fieldType;
|
return fieldType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPath(@Nullable String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getName();
|
return getName();
|
||||||
|
@ -34,6 +34,8 @@ import org.springframework.data.annotation.Id;
|
|||||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||||
import org.springframework.data.elasticsearch.annotations.Field;
|
import org.springframework.data.elasticsearch.annotations.Field;
|
||||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||||
import org.springframework.data.elasticsearch.core.convert.GeoConverters;
|
import org.springframework.data.elasticsearch.core.convert.GeoConverters;
|
||||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.document.Document;
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
@ -362,6 +364,39 @@ public class CriteriaQueryMappingUnitTests {
|
|||||||
|
|
||||||
assertEquals(expected, queryString, false);
|
assertEquals(expected, queryString, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #1753
|
||||||
|
@DisplayName("should map names and value in nested entities with sub-fields")
|
||||||
|
void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException {
|
||||||
|
|
||||||
|
String expected = "{\n" + //
|
||||||
|
" \"bool\": {\n" + //
|
||||||
|
" \"must\": [\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"nested\": {\n" + //
|
||||||
|
" \"query\": {\n" + //
|
||||||
|
" \"query_string\": {\n" + //
|
||||||
|
" \"query\": \"Foobar\",\n" + //
|
||||||
|
" \"fields\": [\n" + //
|
||||||
|
" \"per-sons.nick-name.keyword^1.0\"\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" },\n" + //
|
||||||
|
" \"path\": \"per-sons\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
|
||||||
|
CriteriaQuery criteriaQuery = new CriteriaQuery(
|
||||||
|
new Criteria("persons.nickName.keyword").is("Foobar")
|
||||||
|
);
|
||||||
|
mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class);
|
||||||
|
String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
|
||||||
|
|
||||||
|
assertEquals(expected, queryString, false);
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region helper functions
|
// region helper functions
|
||||||
@ -379,6 +414,7 @@ public class CriteriaQueryMappingUnitTests {
|
|||||||
@Nullable @Id String id;
|
@Nullable @Id String id;
|
||||||
@Nullable @Field(name = "first-name") String firstName;
|
@Nullable @Field(name = "first-name") String firstName;
|
||||||
@Nullable @Field(name = "last-name") String lastName;
|
@Nullable @Field(name = "last-name") String lastName;
|
||||||
|
@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 = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate;
|
||||||
@Nullable @Field(name = "birth-date", type = FieldType.Date, format = {},
|
@Nullable @Field(name = "birth-date", type = FieldType.Date, format = {},
|
||||||
pattern = "dd.MM.uuuu") LocalDate birthDate;
|
pattern = "dd.MM.uuuu") LocalDate birthDate;
|
||||||
|
@ -365,6 +365,7 @@ class CriteriaQueryProcessorUnitTests {
|
|||||||
"}"; //
|
"}"; //
|
||||||
|
|
||||||
Criteria criteria = new Criteria("houses.inhabitants.lastName").is("murphy");
|
Criteria criteria = new Criteria("houses.inhabitants.lastName").is("murphy");
|
||||||
|
criteria.getField().setPath("houses.inhabitants");
|
||||||
|
|
||||||
String query = queryProcessor.createQuery(criteria).toString();
|
String query = queryProcessor.createQuery(criteria).toString();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user