Skip to content

Commit 1860d73

Browse files
authored
Allow for Spring Data Elasticsearch queries to be added to NativeQuery.
Original Pull Request #2451 #Closes 2391
1 parent 4f30a49 commit 1860d73

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed

src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQuery.java

+21
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.elasticsearch.core.query.BaseQuery;
3131
import org.springframework.data.elasticsearch.core.query.ScriptedField;
3232
import org.springframework.lang.Nullable;
33+
import org.springframework.util.Assert;
3334

3435
/**
3536
* A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new
@@ -42,6 +43,7 @@
4243
public class NativeQuery extends BaseQuery {
4344

4445
@Nullable private final Query query;
46+
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
4547
@Nullable private Query filter;
4648
// note: the new client does not have pipeline aggs, these are just set up as normal aggs
4749
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@@ -62,6 +64,12 @@ public NativeQuery(NativeQueryBuilder builder) {
6264
this.scriptedFields = builder.getScriptedFields();
6365
this.sortOptions = builder.getSortOptions();
6466
this.searchExtensions = builder.getSearchExtensions();
67+
68+
if (builder.getSpringDataQuery() != null) {
69+
Assert.isTrue(!NativeQuery.class.isAssignableFrom(builder.getSpringDataQuery().getClass()),
70+
"Cannot add an NativeQuery in a NativeQuery");
71+
}
72+
this.springDataQuery = builder.getSpringDataQuery();
6573
}
6674

6775
public NativeQuery(@Nullable Query query) {
@@ -107,4 +115,17 @@ public List<SortOptions> getSortOptions() {
107115
public Map<String, JsonData> getSearchExtensions() {
108116
return searchExtensions;
109117
}
118+
119+
/**
120+
* @see NativeQueryBuilder#withQuery(org.springframework.data.elasticsearch.core.query.Query).
121+
* @since 5.1
122+
*/
123+
public void setSpringDataQuery(@Nullable org.springframework.data.elasticsearch.core.query.Query springDataQuery) {
124+
this.springDataQuery = springDataQuery;
125+
}
126+
127+
@Nullable
128+
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
129+
return springDataQuery;
130+
}
110131
}

src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQueryBuilder.java

+20
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
5151
private List<SortOptions> sortOptions = new ArrayList<>();
5252
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
5353

54+
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
55+
5456
public NativeQueryBuilder() {}
5557

5658
@Nullable
@@ -89,6 +91,11 @@ public Map<String, JsonData> getSearchExtensions() {
8991
return this.searchExtensions;
9092
}
9193

94+
@Nullable
95+
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
96+
return springDataQuery;
97+
}
98+
9299
public NativeQueryBuilder withQuery(Query query) {
93100

94101
Assert.notNull(query, "query must not be null");
@@ -188,7 +195,20 @@ public NativeQueryBuilder withSearchExtensions(Map<String, JsonData> searchExten
188195
return this;
189196
}
190197

198+
/**
199+
* Allows to use a {@link org.springframework.data.elasticsearch.core.query.Query} within a NativeQuery. Cannot be
200+
* used together with {@link #withQuery(Query)} that sets an Elasticsearch query. Passing in a {@link NativeQuery}
201+
* will result in an exception when {@link #build()} is called.
202+
*
203+
* @since 5.1
204+
*/
205+
public NativeQueryBuilder withQuery(org.springframework.data.elasticsearch.core.query.Query query) {
206+
this.springDataQuery = query;
207+
return this;
208+
}
209+
191210
public NativeQuery build() {
211+
Assert.isTrue(query == null || springDataQuery == null, "Cannot have both a native query and a Spring Data query");
192212
return new NativeQuery(this);
193213
}
194214
}

src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java

+2
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,8 @@ private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullab
14641464

14651465
if (nativeQuery.getQuery() != null) {
14661466
esQuery = nativeQuery.getQuery();
1467+
} else if (nativeQuery.getSpringDataQuery() != null) {
1468+
esQuery = getQuery(nativeQuery.getSpringDataQuery(), clazz);
14671469
}
14681470
} else {
14691471
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.query;
17+
18+
import org.springframework.context.annotation.Bean;
19+
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.context.annotation.Import;
21+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
22+
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
23+
import org.springframework.test.context.ContextConfiguration;
24+
25+
/**
26+
* @author Peter-Josef Meisch
27+
*/
28+
@ContextConfiguration(classes = { NativeQueryELCIntegrationTests.Config.class })
29+
public class NativeQueryELCIntegrationTests extends NativeQueryIntegrationTests {
30+
@Configuration
31+
@Import({ ElasticsearchTemplateConfiguration.class })
32+
static class Config {
33+
@Bean
34+
IndexNameProvider indexNameProvider() {
35+
return new IndexNameProvider("criteria");
36+
}
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.query;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.DisplayName;
22+
import org.junit.jupiter.api.Order;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.data.annotation.Id;
26+
import org.springframework.data.elasticsearch.annotations.Document;
27+
import org.springframework.data.elasticsearch.annotations.Field;
28+
import org.springframework.data.elasticsearch.annotations.FieldType;
29+
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
30+
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
31+
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
32+
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
33+
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
34+
import org.springframework.lang.Nullable;
35+
36+
/**
37+
* @author Peter-Josef Meisch
38+
*/
39+
@SpringIntegrationTest
40+
public abstract class NativeQueryIntegrationTests {
41+
@Autowired private ElasticsearchOperations operations;
42+
@Autowired private IndexNameProvider indexNameProvider;
43+
44+
@BeforeEach
45+
public void before() {
46+
indexNameProvider.increment();
47+
operations.indexOps(SampleEntity.class).createWithMapping();
48+
}
49+
50+
@Test
51+
@Order(java.lang.Integer.MAX_VALUE)
52+
void cleanup() {
53+
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
54+
}
55+
56+
@Test // #2391
57+
@DisplayName("should be able to use CriteriaQuery in a NativeQuery")
58+
void shouldBeAbleToUseCriteriaQueryInANativeQuery() {
59+
60+
var entity = new SampleEntity();
61+
entity.setId("7");
62+
entity.setText("seven");
63+
operations.save(entity);
64+
entity = new SampleEntity();
65+
entity.setId("42");
66+
entity.setText("criteria");
67+
operations.save(entity);
68+
69+
var criteriaQuery = CriteriaQuery.builder(Criteria.where("text").is("criteria")).build();
70+
var nativeQuery = NativeQuery.builder().withQuery(criteriaQuery).build();
71+
72+
var searchHits = operations.search(nativeQuery, SampleEntity.class);
73+
74+
assertThat(searchHits.getTotalHits()).isEqualTo(1);
75+
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
76+
}
77+
78+
@Test // #2391
79+
@DisplayName("should be able to use StringQuery in a NativeQuery")
80+
void shouldBeAbleToUseStringQueryInANativeQuery() {
81+
82+
var entity = new SampleEntity();
83+
entity.setId("7");
84+
entity.setText("seven");
85+
operations.save(entity);
86+
entity = new SampleEntity();
87+
entity.setId("42");
88+
entity.setText("string");
89+
operations.save(entity);
90+
91+
var stringQuery = StringQuery.builder("""
92+
{
93+
"bool": {
94+
"must": [
95+
{
96+
"match": {
97+
"text": "string"
98+
}
99+
}
100+
]
101+
}
102+
}
103+
""").build();
104+
var nativeQuery = NativeQuery.builder().withQuery(stringQuery).build();
105+
106+
var searchHits = operations.search(nativeQuery, SampleEntity.class);
107+
108+
assertThat(searchHits.getTotalHits()).isEqualTo(1);
109+
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
110+
}
111+
112+
@Document(indexName = "#{@indexNameProvider.indexName()}")
113+
static class SampleEntity {
114+
115+
@Nullable
116+
public String getId() {
117+
return id;
118+
}
119+
120+
public void setId(@Nullable String id) {
121+
this.id = id;
122+
}
123+
124+
public String getText() {
125+
return text;
126+
}
127+
128+
public void setText(String text) {
129+
this.text = text;
130+
}
131+
132+
@Nullable
133+
@Id private String id;
134+
135+
@Field(type = FieldType.Text) private String text;
136+
}
137+
}

0 commit comments

Comments
 (0)