Skip to content

Allow for Spring Data Elasticsearch queries to be added to NativeQuery #2451

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
merged 1 commit into from
Feb 12, 2023
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 @@ -30,6 +30,7 @@
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
Expand All @@ -42,6 +43,7 @@
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<>();
Expand All @@ -62,6 +64,12 @@ public NativeQuery(NativeQueryBuilder builder) {
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) {
Expand Down Expand Up @@ -107,4 +115,17 @@ public List<SortOptions> getSortOptions() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,6 +91,11 @@ public Map<String, JsonData> getSearchExtensions() {
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");
Expand Down Expand Up @@ -188,7 +195,20 @@ public NativeQueryBuilder withSearchExtensions(Map<String, JsonData> searchExten
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,8 @@ private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullab

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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}