Commit 685f692c authored by neelesh thakur's avatar neelesh thakur
Browse files

Merge branch 'sort' into 'master'

enable sort on text fields inside the data block

See merge request !122
parents 639dfce6 d5d56df8
Pipeline #45746 failed with stages
in 30 minutes and 12 seconds
......@@ -413,11 +413,7 @@ We can combine both types of queries in one request, eg:
```
## Sort <a name="sort-queries"></a>
The sort feature supports int, float, double, long and datetime.
It does not support array object or string field as of now, and for the records contain such types won't return to the response.
Starting from version 0.9.0 we can set "nested" hints in data schemes object array nodes.
And use such way indexed data for sorting. See in below "Sort by nested arrays objects".
Starting from version 0.9.0 we can set "nested" hints in data schemes object array nodes and use such way indexed data for sorting. See in below "Sort by nested arrays objects".
The records either does not have the sorted fields or have empty value will be listed last in the result.
......@@ -437,7 +433,7 @@ E.g. Given
}
}
```
The above request payload asks search service to sort on "data.Id" in an ascending order, and the expected response will have "totalCount: 10" (instead of 20, please note that the 10 returned records are only from common:welldb:wellbore:1.0.0 because the data.Id in common:welldb:well:1.0.0 is of data type string, which is not currently supported - and therefore, will not be returned) and should list the 5 records which have empty data.Id value at last.
The above request payload asks search service to sort on "data.Id" in ascending order, and the expected response will have "totalCount: 10" (instead of 20, please note that the 10 returned records are only from common:welldb:wellbore:1.0.0 because the data.Id in common:welldb:well:1.0.0 is of data type string, which is not currently supported - and therefore, will not be returned) and should list the 5 records which have empty data.Id value at last.
**NOTE:** Search service does not validate the provided sort field, whether it exists or is of the supported data types. Different kinds may have attributes with the same names, but are different data types. Therefore, it is the user's responsibility to be aware and validate this in one's own workflow.
......
// Copyright © Amazon Web Services
//
// 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
//
// http://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.opengroup.osdu.search.provider.aws.cache;
import org.opengroup.osdu.core.common.cache.RedisCache;
import org.opengroup.osdu.search.cache.IFieldTypeMappingCache;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class FieldTypeMappingCacheImpl implements IFieldTypeMappingCache {
private RedisCache<String, Map> cache;
/**
* Initializes a Cursor Cache with Redis connection parameters specified in the application
* properties file.
*
* @param REDIS_SEARCH_HOST - the hostname of the Cursor Cache Redis cluster.
* @param REDIS_SEARCH_PORT - the port of the Cursor Cache Redis cluster.
*/
public FieldTypeMappingCacheImpl(@Value("${aws.elasticache.cluster.cursor.endpoint}") final String REDIS_SEARCH_HOST,
@Value("${aws.elasticache.cluster.cursor.port}") final String REDIS_SEARCH_PORT) {
cache = new RedisCache<String, Map>(REDIS_SEARCH_HOST, Integer.parseInt(REDIS_SEARCH_PORT),
5 * 60, String.class, Map.class);
}
/**
* Insert a Map object into the Redis cache
*
* @param s the key of the object, used for retrieval
* @param o the Map object to store
*/
@Override
public void put(String s, Map o) {
this.cache.put(s, o);
}
/**
* Gets a cached Map object by key
*
* @param s the key of the cached Map object to get
* @return
*/
@Override
public Map get(String s) {
return this.cache.get(s);
}
/**
* Deletes a Map item in the cache with the given key
*
* @param s the key of the cached Map object to delete
*/
@Override
public void delete(String s) {
this.cache.delete(s);
}
/**
* Clears the entire Redis cache
*/
@Override
public void clearAll() {
this.cache.clearAll();
}
}
......@@ -40,7 +40,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.locationtech.jts.geom.Coordinate;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.entitlements.AclRole;
......@@ -49,8 +48,8 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.search.*;
import org.opengroup.osdu.search.policy.service.IPolicyService;
import org.opengroup.osdu.search.policy.service.PartitionPolicyStatusService;
import org.opengroup.osdu.search.provider.aws.service.FieldMappingTypeService;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.service.IFieldMappingTypeService;
import org.opengroup.osdu.search.util.AggregationParserUtil;
import org.opengroup.osdu.search.util.CrossTenantUtils;
import org.opengroup.osdu.search.util.IQueryParserUtil;
......@@ -77,7 +76,7 @@ abstract class QueryBase {
@Inject
private CrossTenantUtils crossTenantUtils;
@Inject
private FieldMappingTypeService fieldMappingTypeService;
private IFieldMappingTypeService fieldMappingTypeService;
@Autowired(required = false)
private IPolicyService iPolicyService;
@Inject
......@@ -295,18 +294,6 @@ abstract class QueryBase {
sourceBuilder.highlighter(highlightBuilder);
}
// sort: text is not suitable for sorting or aggregation, refer to: this: https://github.com/elastic/elasticsearch/issues/28638,
// so keyword is recommended for unmappedType in general because it can handle both string and number.
// It will ignore the characters longer than the threshold when sorting.
if (request.getSort() != null) {
for (int idx = 0; idx < request.getSort().getField().size(); idx++) {
sourceBuilder.sort(sortParserUtil.parseSort(
request.getSort().getFieldByIndex(idx),
request.getSort().getOrderByIndex(idx).name())
);
}
}
// set the return fields
List<String> returnedFields = request.getReturnedFields();
if (returnedFields == null) {
......@@ -332,11 +319,18 @@ abstract class QueryBase {
SearchResponse searchResponse = null;
try {
String index = this.getIndex(searchRequest);
if (searchRequest.getSpatialFilter() != null) {
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, this.getIndex(searchRequest));
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, index);
}
elasticSearchRequest = createElasticRequest(searchRequest);
if (searchRequest.getSort() != null) {
List<FieldSortBuilder> sortBuilders = this.sortParserUtil.getSortQuery(client, searchRequest.getSort(), index);
for (FieldSortBuilder fieldSortBuilder : sortBuilders) {
elasticSearchRequest.source().sort(fieldSortBuilder);
}
}
startTime = System.currentTimeMillis();
searchResponse = client.search(elasticSearchRequest, RequestOptions.DEFAULT);
return searchResponse;
......@@ -345,7 +339,7 @@ abstract class QueryBase {
case NOT_FOUND:
throw new AppException(HttpServletResponse.SC_NOT_FOUND, "Not Found", "Resource you are trying to find does not exists", e);
case BAD_REQUEST:
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Invalid parameters were given on search request", e);
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", getDetailedBadRequestMessage(elasticSearchRequest, e), e);
case SERVICE_UNAVAILABLE:
throw new AppException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Search error", "Please re-try search after some time.", e);
default:
......@@ -388,4 +382,26 @@ abstract class QueryBase {
}
this.queryFailedAuditLogger(searchRequest);
}
private String getDetailedBadRequestMessage(SearchRequest searchRequest, Exception e) {
String defaultErrorMessage = "Invalid parameters were given on search request";
if (e.getCause() == null) return defaultErrorMessage;
String msg = getKeywordFieldErrorMessage(searchRequest, e.getCause().getMessage());
if (msg != null) return msg;
return defaultErrorMessage;
}
private String getKeywordFieldErrorMessage(SearchRequest searchRequest, String msg) {
if (msg == null) return null;
if (msg.contains("Text fields are not optimised for operations that require per-document field data like aggregations and sorting")
|| msg.contains("can't sort on geo_shape field without using specific sorting feature, like geo_distance")) {
if (searchRequest.source().sorts() != null && !searchRequest.source().sorts().isEmpty()) {
return "Sort is not supported for one or more of the requested fields";
}
if (searchRequest.source().aggregations() != null && searchRequest.source().aggregations().count() > 0) {
return "Aggregations are not supported for one or more of the specified fields";
}
}
return null;
}
}
\ No newline at end of file
// Copyright 2017-2019, Schlumberger
//
// 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
//
// http://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.opengroup.osdu.search.provider.aws.service;
import com.google.common.base.Strings;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.opengroup.osdu.core.common.search.Preconditions;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@Component
@RequestScope
public class FieldMappingTypeService {
public Set<String> getFieldTypes(RestHighLevelClient restClient, String fieldName, String indexPattern) throws IOException {
Preconditions.checkNotNull(restClient, "restClient cannot be null");
Preconditions.checkNotNullOrEmpty(fieldName, "fieldName cannot be null or empty");
Preconditions.checkNotNullOrEmpty(indexPattern, "indexPattern cannot be null or empty");
Set<String> fieldTypes = new HashSet<>();
String fieldLeafNodeLabel = fieldName.substring(fieldName.lastIndexOf(".") + 1);
GetFieldMappingsRequest request = new GetFieldMappingsRequest();
request.fields(fieldName);
if (!Strings.isNullOrEmpty(indexPattern)) request.indices(indexPattern);
GetFieldMappingsResponse response = restClient.indices().getFieldMapping(request, RequestOptions.DEFAULT);
Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> mappings = response.mappings();
for (Map.Entry<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping : mappings.entrySet()) {
if (indexMapping.getValue().isEmpty()) continue;
Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = indexMapping.getValue();
Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = typeMapping.values().iterator().next();
GetFieldMappingsResponse.FieldMappingMetadata fieldMappingMetaData = fieldMapping.get(fieldName);
if (fieldMappingMetaData == null) continue;
Map<String, Object> mapping = fieldMappingMetaData.sourceAsMap();
LinkedHashMap<String, Object> typeMap = (LinkedHashMap<String, Object>) mapping.get(fieldLeafNodeLabel);
Object type = typeMap.get("type");
if (type == null) continue;
fieldTypes.add(type.toString());
}
return fieldTypes;
}
}
\ No newline at end of file
package org.opengroup.osdu.search.provider.azure.cache.impl;
import org.opengroup.osdu.core.common.cache.VmCache;
import org.opengroup.osdu.search.cache.IFieldTypeMappingCache;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class FieldTypeMappingCacheImpl extends VmCache<String, Map> implements IFieldTypeMappingCache {
public FieldTypeMappingCacheImpl() {
super(1440 * 60, 1000);
}
}
\ No newline at end of file
......@@ -14,13 +14,13 @@
package org.opengroup.osdu.search.provider.azure.provider.impl;
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
......@@ -40,7 +40,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.locationtech.jts.geom.Coordinate;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.entitlements.AclRole;
......@@ -49,7 +48,7 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.search.*;
import org.opengroup.osdu.search.policy.service.IPolicyService;
import org.opengroup.osdu.search.policy.service.PartitionPolicyStatusService;
import org.opengroup.osdu.search.provider.azure.service.FieldMappingTypeService;
import org.opengroup.osdu.search.service.IFieldMappingTypeService;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.util.AggregationParserUtil;
import org.opengroup.osdu.search.util.CrossTenantUtils;
......@@ -75,8 +74,7 @@ abstract class QueryBase {
@Inject
private CrossTenantUtils crossTenantUtils;
@Inject
private FieldMappingTypeService fieldMappingTypeService;
private IFieldMappingTypeService fieldMappingTypeService;
@Autowired(required = false)
private IPolicyService iPolicyService;
@Inject
......@@ -137,7 +135,7 @@ abstract class QueryBase {
queryBuilder = queryBuilder != null ? boolQuery().must(queryBuilder).must(spatialQueryBuilder) : boolQuery().must(spatialQueryBuilder);
}
if(this.iPolicyService != null && this.statusService.policyEnabled(this.dpsHeaders.getPartitionId())) {
if (this.iPolicyService != null && this.statusService.policyEnabled(this.dpsHeaders.getPartitionId())) {
return queryBuilder;
} else {
return getQueryBuilderWithAuthorization(queryBuilder, asOwner);
......@@ -294,18 +292,6 @@ abstract class QueryBase {
sourceBuilder.highlighter(highlightBuilder);
}
// sort: text is not suitable for sorting or aggregation, refer to: this: https://github.com/elastic/elasticsearch/issues/28638,
// so keyword is recommended for unmappedType in general because it can handle both string and number.
// It will ignore the characters longer than the threshold when sorting.
if (request.getSort() != null) {
for (int idx = 0; idx < request.getSort().getField().size(); idx++) {
sourceBuilder.sort(sortParserUtil.parseSort(
request.getSort().getFieldByIndex(idx),
request.getSort().getOrderByIndex(idx).name())
);
}
}
// set the return fields
List<String> returnedFields = request.getReturnedFields();
if (returnedFields == null) {
......@@ -331,11 +317,18 @@ abstract class QueryBase {
SearchResponse searchResponse = null;
try {
String index = this.getIndex(searchRequest);
if (searchRequest.getSpatialFilter() != null) {
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, this.getIndex(searchRequest));
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, index);
}
elasticSearchRequest = createElasticRequest(searchRequest);
if (searchRequest.getSort() != null) {
List<FieldSortBuilder> sortBuilders = this.sortParserUtil.getSortQuery(client, searchRequest.getSort(), index);
for (FieldSortBuilder fieldSortBuilder : sortBuilders) {
elasticSearchRequest.source().sort(fieldSortBuilder);
}
}
startTime = System.currentTimeMillis();
searchResponse = client.search(elasticSearchRequest, RequestOptions.DEFAULT);
return searchResponse;
......@@ -344,7 +337,7 @@ abstract class QueryBase {
case NOT_FOUND:
throw new AppException(HttpServletResponse.SC_NOT_FOUND, "Not Found", "Resource you are trying to find does not exists", e);
case BAD_REQUEST:
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Invalid parameters were given on search request", e);
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", getDetailedBadRequestMessage(elasticSearchRequest, e), e);
case SERVICE_UNAVAILABLE:
throw new AppException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Search error", "Please re-try search after some time.", e);
default:
......@@ -387,4 +380,26 @@ abstract class QueryBase {
}
this.queryFailedAuditLogger(searchRequest);
}
private String getDetailedBadRequestMessage(SearchRequest searchRequest, Exception e) {
String defaultErrorMessage = "Invalid parameters were given on search request";
if (e.getCause() == null) return defaultErrorMessage;
String msg = getKeywordFieldErrorMessage(searchRequest, e.getCause().getMessage());
if (msg != null) return msg;
return defaultErrorMessage;
}
private String getKeywordFieldErrorMessage(SearchRequest searchRequest, String msg) {
if (msg == null) return null;
if (msg.contains("Text fields are not optimised for operations that require per-document field data like aggregations and sorting")
|| msg.contains("can't sort on geo_shape field without using specific sorting feature, like geo_distance")) {
if (searchRequest.source().sorts() != null && !searchRequest.source().sorts().isEmpty()) {
return "Sort is not supported for one or more of the requested fields";
}
if (searchRequest.source().aggregations() != null && searchRequest.source().aggregations().count() > 0) {
return "Aggregations are not supported for one or more of the specified fields";
}
}
return null;
}
}
\ No newline at end of file
// Copyright 2017-2019, Schlumberger
//
// 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
//
// http://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.opengroup.osdu.search.provider.azure.service;
import com.google.common.base.Strings;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.opengroup.osdu.core.common.search.Preconditions;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@Component
@RequestScope
public class FieldMappingTypeService {
public Set<String> getFieldTypes(RestHighLevelClient restClient, String fieldName, String indexPattern) throws IOException {
Preconditions.checkNotNull(restClient, "restClient cannot be null");
Preconditions.checkNotNullOrEmpty(fieldName, "fieldName cannot be null or empty");
Preconditions.checkNotNullOrEmpty(indexPattern, "indexPattern cannot be null or empty");
Set<String> fieldTypes = new HashSet<>();
String fieldLeafNodeLabel = fieldName.substring(fieldName.lastIndexOf(".") + 1);
GetFieldMappingsRequest request = new GetFieldMappingsRequest();
request.fields(fieldName);
if (!Strings.isNullOrEmpty(indexPattern)) request.indices(indexPattern);
GetFieldMappingsResponse response = restClient.indices().getFieldMapping(request, RequestOptions.DEFAULT);
Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> mappings = response.mappings();
for (Map.Entry<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping : mappings.entrySet()) {
if (indexMapping.getValue().isEmpty()) continue;
Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = indexMapping.getValue();
Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = typeMapping.values().iterator().next();
GetFieldMappingsResponse.FieldMappingMetadata fieldMappingMetaData = fieldMapping.get(fieldName);
if (fieldMappingMetaData == null) continue;
Map<String, Object> mapping = fieldMappingMetaData.sourceAsMap();
LinkedHashMap<String, Object> typeMap = (LinkedHashMap<String, Object>) mapping.get(fieldLeafNodeLabel);
Object type = typeMap.get("type");
if (type == null) continue;
fieldTypes.add(type.toString());
}
return fieldTypes;
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@
package org.opengroup.osdu.search.provider.azure.provider.impl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
......@@ -28,6 +29,7 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
......@@ -45,10 +47,12 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.search.Point;
import org.opengroup.osdu.core.common.model.search.QueryRequest;
import org.opengroup.osdu.core.common.model.search.QueryResponse;
import org.opengroup.osdu.core.common.model.search.SortOrder;
import org.opengroup.osdu.core.common.model.search.SortQuery;
import org.opengroup.osdu.core.common.model.search.SpatialFilter;
import org.opengroup.osdu.search.config.SearchConfigurationProperties;
import org.opengroup.osdu.search.logging.AuditLogger;
import org.opengroup.osdu.search.provider.azure.service.FieldMappingTypeService;
import org.opengroup.osdu.search.service.FieldMappingTypeService;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.util.AggregationParserUtil;
import org.opengroup.osdu.search.util.CrossTenantUtils;
......@@ -64,12 +68,13 @@ import org.opengroup.osdu.search.util.SortParserUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class QueryServiceImplTest {
......@@ -380,6 +385,30 @@ public class QueryServiceImplTest {
}
}
@Test(expected = AppException.class)
public void testQueryBase_whenUnsupportedSortRequested_statusBadRequest_throwsException() throws IOException {
String dummySortError = "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead";
ElasticsearchStatusException exception = new ElasticsearchStatusException("blah", RestStatus.BAD_REQUEST, new ElasticsearchException(dummySortError));
doThrow(exception).when(client).search(any(), any(RequestOptions.class));
doReturn(new HashSet<>()).when(fieldMappingTypeService).getFieldTypes(eq(client), eq(fieldName), eq(indexName));
SortQuery sortQuery = new SortQuery();
sortQuery.setField(Collections.singletonList("data.name"));
sortQuery.setOrder(Collections.singletonList(SortOrder.DESC));
when(searchRequest.getSort()).thenReturn(sortQuery);
doReturn(Collections.singletonList(new FieldSortBuilder("data.name").order(org.elasticsearch.search.sort.SortOrder.DESC)))
.when(sortParserUtil).getSortQuery(eq(client), eq(sortQuery), eq(indexName));
try {
sut.queryIndex(