diff --git a/src/main/antora/modules/ROOT/pages/migration-guides/migration-guide-4.4-5.0.adoc b/src/main/antora/modules/ROOT/pages/migration-guides/migration-guide-4.4-5.0.adoc
index eb627f141..e11955fef 100644
--- a/src/main/antora/modules/ROOT/pages/migration-guides/migration-guide-4.4-5.0.adoc
+++ b/src/main/antora/modules/ROOT/pages/migration-guides/migration-guide-4.4-5.0.adoc
@@ -49,7 +49,7 @@ Also the reactive implementation that was provided up to now has been moved here
If you are using `ElasticsearchRestTemplate` directly and not the `ElasticsearchOperations` interface you'll need to adjust your imports as well.
When working with the `NativeSearchQuery` class, you'll need to switch to the `NativeQuery` class, which can take a
-`Query` instance comign from the new Elasticsearch client libraries.
+`Query` instance coming from the new Elasticsearch client libraries.
You'll find plenty of examples in the test code.
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-records]]
diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java
index fa7ccc9ec..f75d1ef76 100644
--- a/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java
+++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java
@@ -35,14 +35,17 @@ import org.springframework.util.StringUtils;
* {@link co.elastic.clients.elasticsearch.core.search.Highlight}.
*
* @author Peter-Josef Meisch
+ * @author Haibo Liu
* @since 4.4
*/
class HighlightQueryBuilder {
private final MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext;
+ private final RequestConverter requestConverter;
HighlightQueryBuilder(
- MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext) {
+ MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext, RequestConverter requestConverter) {
this.mappingContext = mappingContext;
+ this.requestConverter = requestConverter;
}
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
@@ -52,7 +55,7 @@ class HighlightQueryBuilder {
// in the old implementation we could use one addParameters method, but in the new Elasticsearch client
// the builder for highlight and highlightfield share no code
- addParameters(highlight.getParameters(), highlightBuilder);
+ addParameters(highlight.getParameters(), highlightBuilder, type);
for (HighlightField highlightField : highlight.getFields()) {
String mappedName = mapFieldName(highlightField.getName(), type);
@@ -69,7 +72,7 @@ class HighlightQueryBuilder {
* the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies
*/
private void addParameters(HighlightParameters parameters,
- co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) {
+ co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder, @Nullable Class> type) {
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
builder.boundaryChars(parameters.getBoundaryChars());
@@ -103,6 +106,10 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
+ if (parameters.getHighlightQuery() != null) {
+ builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
+ }
+
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -174,6 +181,10 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
+ if (parameters.getHighlightQuery() != null) {
+ builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
+ }
+
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java
index bb9d0dcec..607929696 100644
--- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java
+++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java
@@ -105,6 +105,7 @@ import org.springframework.util.StringUtils;
* @author Sascha Woo
* @author cdalxndr
* @author scoobyzhang
+ * @author Haibo Liu
* @since 4.4
*/
@SuppressWarnings("ClassCanBeRecord")
@@ -1494,7 +1495,7 @@ class RequestConverter {
private void addHighlight(Query query, SearchRequest.Builder builder) {
Highlight highlight = query.getHighlightQuery()
- .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
+ .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1504,7 +1505,7 @@ class RequestConverter {
private void addHighlight(Query query, MultisearchBody.Builder builder) {
Highlight highlight = query.getHighlightQuery()
- .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
+ .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1646,7 +1647,7 @@ class RequestConverter {
}
@Nullable
- private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
+ co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
@Nullable Class> clazz) {
if (query == null) {
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightCommonParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightCommonParameters.java
index 6261fb1de..84b15423e 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightCommonParameters.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightCommonParameters.java
@@ -15,10 +15,13 @@
*/
package org.springframework.data.elasticsearch.core.query.highlight;
+import org.springframework.data.elasticsearch.core.query.Query;
+import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
+ * @author Haibo Liu
* @since 4.3
*/
public abstract class HighlightCommonParameters {
@@ -31,6 +34,7 @@ public abstract class HighlightCommonParameters {
private final int fragmentSize;
private final int noMatchSize;
private final int numberOfFragments;
+ @Nullable private final Query highlightQuery;
private final String order;
private final int phraseLimit;
private final String[] preTags;
@@ -51,6 +55,7 @@ public abstract class HighlightCommonParameters {
fragmentSize = builder.fragmentSize;
noMatchSize = builder.noMatchSize;
numberOfFragments = builder.numberOfFragments;
+ highlightQuery = builder.highlightQuery;
order = builder.order;
phraseLimit = builder.phraseLimit;
preTags = builder.preTags;
@@ -95,6 +100,11 @@ public abstract class HighlightCommonParameters {
return numberOfFragments;
}
+ @Nullable
+ public Query getHighlightQuery() {
+ return highlightQuery;
+ }
+
public String getOrder() {
return order;
}
@@ -130,6 +140,11 @@ public abstract class HighlightCommonParameters {
private int fragmentSize = -1;
private int noMatchSize = -1;
private int numberOfFragments = -1;
+ /**
+ * Only the search query part of the {@link Query} takes effect,
+ * others are just ignored.
+ */
+ @Nullable private Query highlightQuery = null;
private String order = "";
private int phraseLimit = -1;
private String[] preTags = new String[0];
@@ -184,6 +199,11 @@ public abstract class HighlightCommonParameters {
return (SELF) this;
}
+ public SELF withHighlightQuery(Query highlightQuery) {
+ this.highlightQuery = highlightQuery;
+ return (SELF) this;
+ }
+
public SELF withOrder(String order) {
this.order = order;
return (SELF) this;
diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java
index a89fe30fe..c57ef8db5 100755
--- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java
@@ -66,6 +66,8 @@ import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
+import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
+import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.util.StreamUtils;
@@ -2964,6 +2966,128 @@ public abstract class ElasticsearchIntegrationTests {
assertThat(highlightField.get(1)).contains("message");
}
+ @Test // #2636
+ void shouldReturnHighlightFieldsWithHighlightQueryInSearchHit() {
+ IndexCoordinates index = createIndexCoordinatesWithHighlightMessage();
+
+ // a highlight query equals to the search query
+ var sameHighlightQuery = HighlightFieldParameters.builder()
+ .withHighlightQuery(getBuilderWithTermQuery("message", "message").build())
+ .build();
+ Query query = getBuilderWithTermQuery("message", "message") //
+ .withHighlightQuery(
+ new HighlightQuery(new Highlight(singletonList(new HighlightField("message", sameHighlightQuery))), HighlightEntity.class)
+ )
+ .build();
+ SearchHits searchHits = operations.search(query, HighlightEntity.class, index);
+
+ assertThat(searchHits).isNotNull();
+ assertThat(searchHits.getSearchHits()).hasSize(1);
+
+ SearchHit searchHit = searchHits.getSearchHit(0);
+ List highlightField = searchHit.getHighlightField("message");
+ assertThat(highlightField).hasSize(2);
+ assertThat(highlightField.get(0)).contains("message");
+ assertThat(highlightField.get(1)).contains("message");
+ }
+
+ @Test // #2636
+ void shouldReturnDifferentHighlightFieldsWithDifferentHighlightQueryInSearchHit() {
+ IndexCoordinates index = createIndexCoordinatesWithHighlightMessage();
+
+ // a different highlight query from the search query
+ var differentHighlightQueryInField = HighlightFieldParameters.builder()
+ .withHighlightQuery(getBuilderWithTermQuery("message", "initial").build())
+ .build();
+ // highlight_query in field
+ Query highlightQueryInField = getBuilderWithTermQuery("message", "message") //
+ .withHighlightQuery(
+ new HighlightQuery(new Highlight(singletonList(new HighlightField("message", differentHighlightQueryInField))), HighlightEntity.class)
+ )
+ .build();
+ assertThatHighlightFieldsIsDifferentFromHighlightQuery(highlightQueryInField, index);
+ }
+
+ @Test // #2636
+ void shouldReturnDifferentHighlightFieldsWithDifferentParamHighlightQueryInSearchHit() {
+ IndexCoordinates index = createIndexCoordinatesWithHighlightMessage();
+
+ // a different highlight query from the search query and used in highlight param rather than field
+ var differentHighlightQueryInParam = HighlightParameters.builder()
+ .withHighlightQuery(getBuilderWithTermQuery("message", "initial").build())
+ .build();
+ // highlight_query in param
+ Query highlightQueryInParam = getBuilderWithTermQuery("message", "message") //
+ .withHighlightQuery(
+ new HighlightQuery(new Highlight(differentHighlightQueryInParam, singletonList(new HighlightField("message"))), HighlightEntity.class)
+ )
+ .build();
+ assertThatHighlightFieldsIsDifferentFromHighlightQuery(highlightQueryInParam, index);
+ }
+
+ @Test // #2636
+ void shouldReturnDifferentHighlightFieldsWithDifferentHighlightCriteriaQueryInSearchHit() {
+ IndexCoordinates index = createIndexCoordinatesWithHighlightMessage();
+ // a different highlight query from the search query, written by CriteriaQuery rather than NativeQuery
+ var criteriaHighlightQueryInParam = HighlightParameters.builder()
+ .withHighlightQuery(new CriteriaQuery(new Criteria("message").is("initial")))
+ .build();
+ // highlight_query in param
+ Query differentHighlightQueryUsingCriteria = getBuilderWithTermQuery("message", "message") //
+ .withHighlightQuery(
+ new HighlightQuery(new Highlight(criteriaHighlightQueryInParam, singletonList(new HighlightField("message"))), HighlightEntity.class)
+ )
+ .build();
+ assertThatHighlightFieldsIsDifferentFromHighlightQuery(differentHighlightQueryUsingCriteria, index);
+ }
+
+ @Test // #2636
+ void shouldReturnDifferentHighlightFieldsWithDifferentHighlightStringQueryInSearchHit() {
+ IndexCoordinates index = createIndexCoordinatesWithHighlightMessage();
+ // a different highlight query from the search query, written by StringQuery
+ var stringHighlightQueryInParam = HighlightParameters.builder()
+ .withHighlightQuery(new StringQuery(
+ """
+ {
+ "term": {
+ "message": {
+ "value": "initial"
+ }
+ }
+ }
+ """
+ ))
+ .build();
+ // highlight_query in param
+ Query differentHighlightQueryUsingStringQuery = getBuilderWithTermQuery("message", "message") //
+ .withHighlightQuery(
+ new HighlightQuery(new Highlight(stringHighlightQueryInParam, singletonList(new HighlightField("message"))), HighlightEntity.class)
+ )
+ .build();
+ assertThatHighlightFieldsIsDifferentFromHighlightQuery(differentHighlightQueryUsingStringQuery, index);
+ }
+
+ private IndexCoordinates createIndexCoordinatesWithHighlightMessage() {
+ IndexCoordinates index = IndexCoordinates.of("test-index-highlight-entity-template");
+ HighlightEntity entity = new HighlightEntity("1",
+ "This message is a long text which contains the word to search for "
+ + "in two places, the first being near the beginning and the second near the end of the message. "
+ + "However, i'll use a different highlight query from the initial search query");
+ IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build();
+ operations.index(indexQuery, index);
+ operations.indexOps(index).refresh();
+ return index;
+ }
+
+ private void assertThatHighlightFieldsIsDifferentFromHighlightQuery(Query query, IndexCoordinates index) {
+ SearchHits searchHits = operations.search(query, HighlightEntity.class, index);
+
+ SearchHit searchHit = searchHits.getSearchHit(0);
+ List highlightField = searchHit.getHighlightField("message");
+ assertThat(highlightField).hasSize(1);
+ assertThat(highlightField.get(0)).contains("initial");
+ }
+
@Test // #1686
void shouldRunRescoreQueryInSearchQuery() {
IndexCoordinates index = IndexCoordinates.of(indexNameProvider.getPrefix() + "rescore-entity");