Skip to content

Commit b8f8a60

Browse files
authored
Custom Order class with specific parameters for Elasticsearch.
Original Pull Request #1955 Closes #1911
1 parent 7ae55b9 commit b8f8a60

File tree

5 files changed

+179
-28
lines changed

5 files changed

+179
-28
lines changed

Diff for: src/main/asciidoc/reference/elasticsearch-misc.adoc

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ It is recommended to add those operations as custom implementation as described
77
[[elasticsearc.misc.index.settings]]
88
== Index settings
99

10-
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available:
10+
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation.
11+
The following arguments are available:
1112

1213
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
1314
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
@@ -42,20 +43,24 @@ class Entity {
4243
// getter and setter...
4344
}
4445
----
46+
4547
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
4648
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
4749
====
4850

4951
[[elasticsearch.misc.mappings]]
5052
== Index Mapping
5153

52-
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties:
54+
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
55+
In addition to that it is possible to add the `@Mapping` annotation to a class.
56+
This annotation has the following properties:
5357

5458
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
5559
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
5660
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
5761
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
5862
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
63+
5964
====
6065
[source,json]
6166
----
@@ -165,7 +170,10 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
165170
[[elasticsearch.misc.sorts]]
166171
== Sort options
167172

168-
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
173+
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
174+
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
175+
176+
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
169177

170178
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
171179

Diff for: src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,15 @@ private void prepareSort(Query query, SearchRequestBuilder searchRequestBuilder,
12121212
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
12131213
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
12141214

1215+
Order.Mode mode = Order.DEFAULT_MODE;
1216+
String unmappedType = null;
1217+
1218+
if (order instanceof Order) {
1219+
Order o = (Order) order;
1220+
mode = o.getMode();
1221+
unmappedType = o.getUnmappedType();
1222+
}
1223+
12151224
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
12161225
return SortBuilders //
12171226
.scoreSort() //
@@ -1229,14 +1238,23 @@ private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchP
12291238
geoDistanceOrder.getGeoPoint().getLon());
12301239

12311240
sort.geoDistance(GeoDistance.fromString(geoDistanceOrder.getDistanceType().name()));
1232-
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
1233-
sort.sortMode(SortMode.fromString(geoDistanceOrder.getMode().name()));
1241+
sort.sortMode(SortMode.fromString(mode.name()));
12341242
sort.unit(DistanceUnit.fromString(geoDistanceOrder.getUnit()));
1243+
1244+
if (geoDistanceOrder.getIgnoreUnmapped() != GeoDistanceOrder.DEFAULT_IGNORE_UNMAPPED) {
1245+
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
1246+
}
1247+
12351248
return sort;
12361249
} else {
12371250
FieldSortBuilder sort = SortBuilders //
12381251
.fieldSort(fieldName) //
1239-
.order(sortOrder);
1252+
.order(sortOrder) //
1253+
.sortMode(SortMode.fromString(mode.name()));
1254+
1255+
if (unmappedType != null) {
1256+
sort.unmappedType(unmappedType);
1257+
}
12401258

12411259
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
12421260
sort.missing("_first");

Diff for: src/main/java/org/springframework/data/elasticsearch/core/query/GeoDistanceOrder.java

+6-13
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,18 @@
2121
/**
2222
* {@link org.springframework.data.domain.Sort.Order} derived class to be able to define a _geo_distance order for a
2323
* search.
24-
*
24+
*
2525
* @author Peter-Josef Meisch
2626
* @since 4.0
2727
*/
28-
public class GeoDistanceOrder extends Sort.Order {
28+
public class GeoDistanceOrder extends Order {
2929

30-
private static final DistanceType DEFAULT_DISTANCE_TYPE = DistanceType.arc;
31-
private static final Mode DEFAULT_MODE = Mode.min;
32-
private static final String DEFAULT_UNIT = "m";
33-
private static final Boolean DEFAULT_IGNORE_UNMAPPED = false;
30+
public static final DistanceType DEFAULT_DISTANCE_TYPE = DistanceType.arc;
31+
public static final String DEFAULT_UNIT = "m";
32+
public static final Boolean DEFAULT_IGNORE_UNMAPPED = false;
3433

3534
private final GeoPoint geoPoint;
3635
private final DistanceType distanceType;
37-
private final Mode mode;
3836
private final String unit;
3937
private final Boolean ignoreUnmapped;
4038

@@ -45,10 +43,9 @@ public GeoDistanceOrder(String property, GeoPoint geoPoint) {
4543

4644
private GeoDistanceOrder(String property, GeoPoint geoPoint, Sort.Direction direction, DistanceType distanceType,
4745
Mode mode, String unit, Boolean ignoreUnmapped) {
48-
super(direction, property);
46+
super(direction, property, mode);
4947
this.geoPoint = geoPoint;
5048
this.distanceType = distanceType;
51-
this.mode = mode;
5249
this.unit = unit;
5350
this.ignoreUnmapped = ignoreUnmapped;
5451
}
@@ -119,8 +116,4 @@ public String toString() {
119116
public enum DistanceType {
120117
arc, plane
121118
}
122-
123-
public enum Mode {
124-
min, max, median, avg
125-
}
126119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2021 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.data.domain.Sort;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* Extends the {@link Sort.Order} with properties that can be set on Elasticsearch order options.
23+
*
24+
* @author Peter-Josef Meisch
25+
* @since 4.3
26+
*/
27+
public class Order extends Sort.Order {
28+
29+
public static final Mode DEFAULT_MODE = Mode.min;
30+
public static final Sort.NullHandling DEFAULT_NULL_HANDLING = Sort.NullHandling.NATIVE;
31+
32+
protected final Mode mode;
33+
@Nullable protected final String unmappedType;
34+
35+
public Order(Sort.Direction direction, String property) {
36+
this(direction, property, DEFAULT_MODE, null);
37+
}
38+
39+
public Order(Sort.Direction direction, String property, Mode mode) {
40+
this(direction, property, DEFAULT_NULL_HANDLING, mode, null);
41+
}
42+
43+
public Order(Sort.Direction direction, String property, @Nullable String unmappedType) {
44+
this(direction, property, DEFAULT_NULL_HANDLING, DEFAULT_MODE, unmappedType);
45+
}
46+
47+
public Order(Sort.Direction direction, String property, Mode mode, @Nullable String unmappedType) {
48+
this(direction, property, DEFAULT_NULL_HANDLING, mode, unmappedType);
49+
}
50+
51+
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint) {
52+
this(direction, property, nullHandlingHint, DEFAULT_MODE, null);
53+
}
54+
55+
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, Mode mode) {
56+
this(direction, property, nullHandlingHint, mode, null);
57+
}
58+
59+
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint,
60+
@Nullable String unmappedType) {
61+
this(direction, property, nullHandlingHint, DEFAULT_MODE, unmappedType);
62+
}
63+
64+
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, Mode mode,
65+
@Nullable String unmappedType) {
66+
super(direction, property, nullHandlingHint);
67+
this.mode = mode;
68+
this.unmappedType = unmappedType;
69+
}
70+
71+
@Nullable
72+
public String getUnmappedType() {
73+
return unmappedType;
74+
}
75+
76+
@Override
77+
public Sort.Order with(Sort.Direction direction) {
78+
return new Order(direction, getProperty(), getNullHandling(), mode, unmappedType);
79+
}
80+
81+
@Override
82+
public Sort.Order withProperty(String property) {
83+
return new Order(getDirection(), property, getNullHandling(), mode, unmappedType);
84+
}
85+
86+
@Override
87+
public Sort.Order with(Sort.NullHandling nullHandling) {
88+
return new Order(getDirection(), getProperty(), nullHandling, getMode(), unmappedType);
89+
}
90+
91+
public Order withUnmappedType(@Nullable String unmappedType) {
92+
return new Order(getDirection(), getProperty(), getNullHandling(), getMode(), unmappedType);
93+
}
94+
95+
public Order with(Mode mode) {
96+
return new Order(getDirection(), getProperty(), getNullHandling(), mode, unmappedType);
97+
}
98+
99+
public Mode getMode() {
100+
return mode;
101+
}
102+
103+
public enum Mode {
104+
min, max, median, avg
105+
}
106+
107+
}

Diff for: src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java

+34-9
Original file line numberDiff line numberDiff line change
@@ -686,9 +686,11 @@ public void shouldSortResultsGivenMultipleSortCriteria() {
686686

687687
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
688688

689-
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
690-
.withSort(new FieldSortBuilder("rate").order(SortOrder.ASC))
691-
.withSort(new FieldSortBuilder("message").order(SortOrder.ASC)).build();
689+
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) //
690+
.withSorts( //
691+
new FieldSortBuilder("rate").order(SortOrder.ASC), //
692+
new FieldSortBuilder("message").order(SortOrder.ASC)) //
693+
.build();
692694

693695
// when
694696
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
@@ -725,8 +727,8 @@ public void shouldSortResultsGivenNullFirstSortCriteria() {
725727

726728
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
727729

728-
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
729-
.withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsFirst()))).build();
730+
Query searchQuery = operations.matchAllQuery();
731+
searchQuery.setPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsFirst())));
730732

731733
// when
732734
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
@@ -763,8 +765,8 @@ public void shouldSortResultsGivenNullLastSortCriteria() {
763765

764766
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
765767

766-
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
767-
.withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsLast()))).build();
768+
Query searchQuery = operations.matchAllQuery();
769+
searchQuery.setPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsLast())));
768770

769771
// when
770772
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
@@ -1139,8 +1141,8 @@ public void shouldReturnResultsWithScanAndScrollForGivenSearchQuery() {
11391141

11401142
// then
11411143

1142-
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
1143-
.withPageable(PageRequest.of(0, 10)).build();
1144+
Query searchQuery = operations.matchAllQuery();
1145+
searchQuery.setPageable(PageRequest.of(0, 10));
11441146

11451147
SearchScrollHits<SampleEntity> scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000,
11461148
searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName()));
@@ -3591,6 +3593,29 @@ void shouldIndexDocumentFromSourceWithVersion() {
35913593
operations.index(query, IndexCoordinates.of(indexNameProvider.indexName()));
35923594
}
35933595

3596+
@Test // #1945
3597+
@DisplayName("should error on sort with unmapped field and default settings")
3598+
void shouldErrorOnSortWithUnmappedFieldAndDefaultSettings() {
3599+
3600+
Sort.Order order = new Sort.Order(Sort.Direction.ASC, "unmappedField");
3601+
Query query = operations.matchAllQuery().addSort(Sort.by(order));
3602+
3603+
assertThatThrownBy(() -> {
3604+
operations.search(query, SampleEntity.class);
3605+
});
3606+
}
3607+
3608+
@Test // #1945
3609+
@DisplayName("should not error on sort with unmapped field and unmapped_type settings")
3610+
void shouldNotErrorOnSortWithUnmappedFieldAndUnmappedTypeSettings() {
3611+
3612+
org.springframework.data.elasticsearch.core.query.Order order = new org.springframework.data.elasticsearch.core.query.Order(
3613+
Sort.Direction.ASC, "unmappedField").withUnmappedType("long");
3614+
Query query = operations.matchAllQuery().addSort(Sort.by(order));
3615+
3616+
operations.search(query, SampleEntity.class);
3617+
}
3618+
35943619
// region entities
35953620
@Document(indexName = "#{@indexNameProvider.indexName()}")
35963621
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")

0 commit comments

Comments
 (0)