Option to add docvalue_fields to a search query.

Original Pull Request #2446
#Closes 2316
This commit is contained in:
Peter-Josef Meisch 2023-02-06 20:39:00 +01:00 committed by GitHub
parent 0971acfe25
commit 6805fff1fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 181 additions and 13 deletions

View File

@ -31,6 +31,7 @@ import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.RuntimeField;
import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.FieldAndFormat;
import co.elastic.clients.elasticsearch._types.query_dsl.Like;
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
import co.elastic.clients.elasticsearch.core.*;
@ -1298,6 +1299,12 @@ class RequestConverter {
// noinspection unchecked
builder.indicesBoost(boosts);
}
if (!isEmpty(query.getDocValueFields())) {
builder.docvalueFields(query.getDocValueFields().stream() //
.map(docValueField -> FieldAndFormat.of(b -> b.field(docValueField.field()).format(docValueField.format())))
.toList());
}
}
private Rescore getRescore(RescorerQuery rescorerQuery) {
@ -1554,14 +1561,13 @@ class RequestConverter {
return SearchTemplateRequest.of(builder -> {
builder //
.allowNoIndices(query.getAllowNoIndices()) //
.explain(query.getExplain()) //
.id(query.getId()) //
.index(Arrays.asList(index.getIndexNames())) //
.preference(query.getPreference()) //
.routing(query.getRoute()) //
.searchType(searchType(query.getSearchType()))
.source(query.getSource()) //
.allowNoIndices(query.getAllowNoIndices()) //
.explain(query.getExplain()) //
.id(query.getId()) //
.index(Arrays.asList(index.getIndexNames())) //
.preference(query.getPreference()) //
.routing(query.getRoute()) //
.searchType(searchType(query.getSearchType())).source(query.getSource()) //
;
var expandWildcards = query.getExpandWildcards();
@ -1577,7 +1583,7 @@ class RequestConverter {
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> params = query.getParams().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.params(params);
}

View File

@ -78,9 +78,9 @@ public class BaseQuery implements Query {
@Nullable protected PointInTime pointInTime;
private boolean queryIsUpdatedByConverter = false;
@Nullable private Integer reactiveBatchSize = null;
@Nullable private Boolean allowNoIndices = null;
@Nullable private Boolean allowNoIndices = null;
private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
private List<DocValueField> docValueFields = new ArrayList<>();
public BaseQuery() {}
@ -114,6 +114,7 @@ public class BaseQuery implements Query {
this.reactiveBatchSize = builder.getReactiveBatchSize();
this.allowNoIndices = builder.getAllowNoIndices();
this.expandWildcards = builder.getExpandWildcards();
this.docValueFields = builder.getDocValueFields();
}
/**
@ -524,4 +525,22 @@ public class BaseQuery implements Query {
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
return expandWildcards;
}
/**
* @since 5.1
*/
@Override
public List<DocValueField> getDocValueFields() {
return docValueFields;
}
/**
* @since 5.1
*/
public void setDocValueFields(List<DocValueField> docValueFields) {
Assert.notNull(docValueFields, "getDocValueFields must not be null");
this.docValueFields = docValueFields;
}
}

View File

@ -70,6 +70,7 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
private EnumSet<IndicesOptions.WildcardStates> expandWildcards = EnumSet.noneOf(IndicesOptions.WildcardStates.class);
@Nullable Integer reactiveBatchSize;
private final List<DocValueField> docValueFields = new ArrayList<>();
@Nullable
public Sort getSort() {
@ -218,6 +219,13 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return expandWildcards;
}
/**
* @since 5.1
*/
public List<DocValueField> getDocValueFields() {
return docValueFields;
}
public SELF withPageable(Pageable pageable) {
this.pageable = pageable;
return self();
@ -423,6 +431,18 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return self();
}
/**
* @since 5.1
*/
public SELF withDocValueFields(List<DocValueField> docValueFields) {
Assert.notNull(docValueFields, "docValueFields must not be null");
this.docValueFields.clear();
this.docValueFields.addAll(docValueFields);
return self();
}
public abstract Q build();
private SELF self() {

View File

@ -0,0 +1,35 @@
/*
* Copyright 2023 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.core.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Record defining a docvalue_field to be used in a query.
*
* @author Peter-Josef Meisch
* @since 5.1
*/
public record DocValueField(String field, @Nullable String format) {
public DocValueField {
Assert.notNull(field, "field must not be null");
}
public DocValueField(String field) {
this(field, null);
}
}

View File

@ -472,8 +472,14 @@ public interface Query {
EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
/**
* @since 4.3
*/
* @return a possible empty list of docvalue_field values to be set on the query.
* @since 5.1
*/
List<DocValueField> getDocValueFields();
/**
* @since 4.3
*/
enum SearchType {
QUERY_THEN_FETCH, DFS_QUERY_THEN_FETCH
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2023 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.client.elc;
import static org.assertj.core.api.Assertions.*;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.DocValueField;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.lang.Nullable;
import java.util.List;
/**
* @author Peter-Josef Meisch
*/
class RequestConverterTest {
private static final SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
private static final MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext);
private JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper();
private RequestConverter requestConverter = new RequestConverter(converter, jsonpMapper);
@Test // #2316
@DisplayName("should add docvalue_fields")
void shouldAddDocvalueFields() {
var docValueFields = List.of( //
new DocValueField("field1"), //
new DocValueField("field2", "format2") //
);
// doesn't matter what type of query is used, the relevant part for docvalue_fields is in the base builder.
var query = StringQuery.builder("""
{
"match_all":{}
}
""") //
.withDocValueFields(docValueFields) //
.build();
var searchRequest = requestConverter.searchRequest(query, SampleEntity.class, IndexCoordinates.of("foo"), true);
var fieldAndFormats = searchRequest.docvalueFields();
assertThat(fieldAndFormats).hasSize(2);
assertThat(fieldAndFormats.get(0).field()).isEqualTo("field1");
assertThat(fieldAndFormats.get(0).format()).isNull();
assertThat(fieldAndFormats.get(1).field()).isEqualTo("field2");
assertThat(fieldAndFormats.get(1).format()).isEqualTo("format2");
}
@Document(indexName = "does-not-matter")
static class SampleEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Text) private String text;
}
}