Allow for Spring Data Elasticsearch queries to be added to NativeQuery.

Original Pull Request #2451
#Closes 2391
This commit is contained in:
Peter-Josef Meisch 2023-02-12 17:50:55 +01:00 committed by GitHub
parent 4f30a492b9
commit 1860d7353a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 219 additions and 0 deletions

View File

@ -30,6 +30,7 @@ import java.util.Map;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new
@ -42,6 +43,7 @@ import org.springframework.lang.Nullable;
public class NativeQuery extends BaseQuery {
@Nullable private final Query query;
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
@Nullable private Query filter;
// note: the new client does not have pipeline aggs, these are just set up as normal aggs
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@ -62,6 +64,12 @@ public class NativeQuery extends BaseQuery {
this.scriptedFields = builder.getScriptedFields();
this.sortOptions = builder.getSortOptions();
this.searchExtensions = builder.getSearchExtensions();
if (builder.getSpringDataQuery() != null) {
Assert.isTrue(!NativeQuery.class.isAssignableFrom(builder.getSpringDataQuery().getClass()),
"Cannot add an NativeQuery in a NativeQuery");
}
this.springDataQuery = builder.getSpringDataQuery();
}
public NativeQuery(@Nullable Query query) {
@ -107,4 +115,17 @@ public class NativeQuery extends BaseQuery {
public Map<String, JsonData> getSearchExtensions() {
return searchExtensions;
}
/**
* @see NativeQueryBuilder#withQuery(org.springframework.data.elasticsearch.core.query.Query).
* @since 5.1
*/
public void setSpringDataQuery(@Nullable org.springframework.data.elasticsearch.core.query.Query springDataQuery) {
this.springDataQuery = springDataQuery;
}
@Nullable
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
return springDataQuery;
}
}

View File

@ -51,6 +51,8 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
private List<SortOptions> sortOptions = new ArrayList<>();
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
public NativeQueryBuilder() {}
@Nullable
@ -89,6 +91,11 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this.searchExtensions;
}
@Nullable
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
return springDataQuery;
}
public NativeQueryBuilder withQuery(Query query) {
Assert.notNull(query, "query must not be null");
@ -188,7 +195,20 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
/**
* Allows to use a {@link org.springframework.data.elasticsearch.core.query.Query} within a NativeQuery. Cannot be
* used together with {@link #withQuery(Query)} that sets an Elasticsearch query. Passing in a {@link NativeQuery}
* will result in an exception when {@link #build()} is called.
*
* @since 5.1
*/
public NativeQueryBuilder withQuery(org.springframework.data.elasticsearch.core.query.Query query) {
this.springDataQuery = query;
return this;
}
public NativeQuery build() {
Assert.isTrue(query == null || springDataQuery == null, "Cannot have both a native query and a Spring Data query");
return new NativeQuery(this);
}
}

View File

@ -1464,6 +1464,8 @@ class RequestConverter {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getQuery(nativeQuery.getSpringDataQuery(), clazz);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());

View File

@ -0,0 +1,39 @@
/*
* 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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
*/
@ContextConfiguration(classes = { NativeQueryELCIntegrationTests.Config.class })
public class NativeQueryELCIntegrationTests extends NativeQueryIntegrationTests {
@Configuration
@Import({ ElasticsearchTemplateConfiguration.class })
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("criteria");
}
}
}

View File

@ -0,0 +1,137 @@
/*
* 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 static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
*/
@SpringIntegrationTest
public abstract class NativeQueryIntegrationTests {
@Autowired private ElasticsearchOperations operations;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
public void before() {
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping();
}
@Test
@Order(java.lang.Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
}
@Test // #2391
@DisplayName("should be able to use CriteriaQuery in a NativeQuery")
void shouldBeAbleToUseCriteriaQueryInANativeQuery() {
var entity = new SampleEntity();
entity.setId("7");
entity.setText("seven");
operations.save(entity);
entity = new SampleEntity();
entity.setId("42");
entity.setText("criteria");
operations.save(entity);
var criteriaQuery = CriteriaQuery.builder(Criteria.where("text").is("criteria")).build();
var nativeQuery = NativeQuery.builder().withQuery(criteriaQuery).build();
var searchHits = operations.search(nativeQuery, SampleEntity.class);
assertThat(searchHits.getTotalHits()).isEqualTo(1);
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
}
@Test // #2391
@DisplayName("should be able to use StringQuery in a NativeQuery")
void shouldBeAbleToUseStringQueryInANativeQuery() {
var entity = new SampleEntity();
entity.setId("7");
entity.setText("seven");
operations.save(entity);
entity = new SampleEntity();
entity.setId("42");
entity.setText("string");
operations.save(entity);
var stringQuery = StringQuery.builder("""
{
"bool": {
"must": [
{
"match": {
"text": "string"
}
}
]
}
}
""").build();
var nativeQuery = NativeQuery.builder().withQuery(stringQuery).build();
var searchHits = operations.search(nativeQuery, SampleEntity.class);
assertThat(searchHits.getTotalHits()).isEqualTo(1);
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleEntity {
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Nullable
@Id private String id;
@Field(type = FieldType.Text) private String text;
}
}