|
@@ -10,12 +10,16 @@ package org.elasticsearch.search.builder;
|
|
|
|
|
|
import org.elasticsearch.ElasticsearchException;
|
|
|
import org.elasticsearch.TransportVersions;
|
|
|
+import org.elasticsearch.action.ActionRequestValidationException;
|
|
|
+import org.elasticsearch.action.search.SearchRequest;
|
|
|
import org.elasticsearch.common.ParsingException;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
+import org.elasticsearch.common.ValidationException;
|
|
|
import org.elasticsearch.common.io.stream.StreamInput;
|
|
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
|
import org.elasticsearch.common.io.stream.Writeable;
|
|
|
import org.elasticsearch.common.logging.DeprecationLogger;
|
|
|
+import org.elasticsearch.common.util.CollectionUtils;
|
|
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
|
|
import org.elasticsearch.core.Booleans;
|
|
|
import org.elasticsearch.core.Nullable;
|
|
@@ -28,6 +32,7 @@ import org.elasticsearch.index.query.QueryRewriteContext;
|
|
|
import org.elasticsearch.index.query.Rewriteable;
|
|
|
import org.elasticsearch.script.Script;
|
|
|
import org.elasticsearch.search.SearchExtBuilder;
|
|
|
+import org.elasticsearch.search.SearchService;
|
|
|
import org.elasticsearch.search.aggregations.AggregationBuilder;
|
|
|
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
|
|
import org.elasticsearch.search.aggregations.PipelineAggregationBuilder;
|
|
@@ -43,7 +48,9 @@ import org.elasticsearch.search.retriever.RetrieverBuilder;
|
|
|
import org.elasticsearch.search.retriever.RetrieverParserContext;
|
|
|
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
|
|
|
import org.elasticsearch.search.slice.SliceBuilder;
|
|
|
+import org.elasticsearch.search.sort.FieldSortBuilder;
|
|
|
import org.elasticsearch.search.sort.ScoreSortBuilder;
|
|
|
+import org.elasticsearch.search.sort.ShardDocSortField;
|
|
|
import org.elasticsearch.search.sort.SortBuilder;
|
|
|
import org.elasticsearch.search.sort.SortBuilders;
|
|
|
import org.elasticsearch.search.sort.SortOrder;
|
|
@@ -71,6 +78,7 @@ import java.util.function.ToLongFunction;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
import static java.util.Collections.emptyMap;
|
|
|
+import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|
|
import static org.elasticsearch.index.query.AbstractQueryBuilder.parseTopLevelQuery;
|
|
|
import static org.elasticsearch.search.internal.SearchContext.DEFAULT_TERMINATE_AFTER;
|
|
|
import static org.elasticsearch.search.internal.SearchContext.TRACK_TOTAL_HITS_ACCURATE;
|
|
@@ -78,10 +86,9 @@ import static org.elasticsearch.search.internal.SearchContext.TRACK_TOTAL_HITS_D
|
|
|
|
|
|
/**
|
|
|
* A search source builder allowing to easily build search source. Simple
|
|
|
- * construction using
|
|
|
- * {@link org.elasticsearch.search.builder.SearchSourceBuilder#searchSource()}.
|
|
|
+ * construction using {@link SearchSourceBuilder#searchSource()}.
|
|
|
*
|
|
|
- * @see org.elasticsearch.action.search.SearchRequest#source(SearchSourceBuilder)
|
|
|
+ * @see SearchRequest#source(SearchSourceBuilder)
|
|
|
*/
|
|
|
public final class SearchSourceBuilder implements Writeable, ToXContentObject, Rewriteable<SearchSourceBuilder> {
|
|
|
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(SearchSourceBuilder.class);
|
|
@@ -141,6 +148,8 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
return new HighlightBuilder();
|
|
|
}
|
|
|
|
|
|
+ private transient RetrieverBuilder retrieverBuilder;
|
|
|
+
|
|
|
private List<SubSearchSourceBuilder> subSearchSourceBuilders = new ArrayList<>();
|
|
|
|
|
|
private QueryBuilder postQueryBuilder;
|
|
@@ -283,6 +292,9 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
|
|
|
@Override
|
|
|
public void writeTo(StreamOutput out) throws IOException {
|
|
|
+ if (retrieverBuilder != null) {
|
|
|
+ throw new IllegalStateException("SearchSourceBuilder should be rewritten first");
|
|
|
+ }
|
|
|
out.writeOptionalWriteable(aggregations);
|
|
|
out.writeOptionalBoolean(explain);
|
|
|
out.writeOptionalWriteable(fetchSourceContext);
|
|
@@ -367,6 +379,18 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Sets the retriever for this request.
|
|
|
+ */
|
|
|
+ public SearchSourceBuilder retriever(RetrieverBuilder retrieverBuilder) {
|
|
|
+ this.retrieverBuilder = retrieverBuilder;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public RetrieverBuilder retriever() {
|
|
|
+ return retrieverBuilder;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Sets the query for this request.
|
|
|
*/
|
|
@@ -1134,6 +1158,21 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
highlightBuilder
|
|
|
)
|
|
|
));
|
|
|
+ if (retrieverBuilder != null) {
|
|
|
+ var newRetriever = retrieverBuilder.rewrite(context);
|
|
|
+ if (newRetriever != retrieverBuilder) {
|
|
|
+ var rewritten = shallowCopy();
|
|
|
+ rewritten.retrieverBuilder = newRetriever;
|
|
|
+ return rewritten;
|
|
|
+ } else {
|
|
|
+ // retriever is transient, the rewritten version is extracted in this source.
|
|
|
+ var retriever = retrieverBuilder;
|
|
|
+ retrieverBuilder = null;
|
|
|
+ retriever.extractToSearchSourceBuilder(this, false);
|
|
|
+ validate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
List<SubSearchSourceBuilder> subSearchSourceBuilders = Rewriteable.rewrite(this.subSearchSourceBuilders, context);
|
|
|
QueryBuilder postQueryBuilder = null;
|
|
|
if (this.postQueryBuilder != null) {
|
|
@@ -1293,7 +1332,6 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
}
|
|
|
List<KnnSearchBuilder.Builder> knnBuilders = new ArrayList<>();
|
|
|
|
|
|
- RetrieverBuilder retrieverBuilder = null;
|
|
|
SearchUsage searchUsage = new SearchUsage();
|
|
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
|
if (token == XContentParser.Token.FIELD_NAME) {
|
|
@@ -1627,39 +1665,6 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
}
|
|
|
|
|
|
knnSearch = knnBuilders.stream().map(knnBuilder -> knnBuilder.build(size())).collect(Collectors.toList());
|
|
|
-
|
|
|
- if (retrieverBuilder != null) {
|
|
|
- List<String> specified = new ArrayList<>();
|
|
|
- if (subSearchSourceBuilders.isEmpty() == false) {
|
|
|
- specified.add(QUERY_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (knnSearch.isEmpty() == false) {
|
|
|
- specified.add(KNN_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (searchAfterBuilder != null) {
|
|
|
- specified.add(SEARCH_AFTER.getPreferredName());
|
|
|
- }
|
|
|
- if (terminateAfter != DEFAULT_TERMINATE_AFTER) {
|
|
|
- specified.add(TERMINATE_AFTER_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (sorts != null) {
|
|
|
- specified.add(SORT_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (rescoreBuilders != null) {
|
|
|
- specified.add(RESCORE_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (minScore != null) {
|
|
|
- specified.add(MIN_SCORE_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (rankBuilder != null) {
|
|
|
- specified.add(RANK_FIELD.getPreferredName());
|
|
|
- }
|
|
|
- if (specified.isEmpty() == false) {
|
|
|
- throw new IllegalArgumentException("cannot specify [" + RETRIEVER.getPreferredName() + "] and " + specified);
|
|
|
- }
|
|
|
- retrieverBuilder.extractToSearchSourceBuilder(this, false);
|
|
|
- }
|
|
|
-
|
|
|
searchUsageConsumer.accept(searchUsage);
|
|
|
return this;
|
|
|
}
|
|
@@ -1689,6 +1694,10 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
builder.field(TERMINATE_AFTER_FIELD.getPreferredName(), terminateAfter);
|
|
|
}
|
|
|
|
|
|
+ if (retrieverBuilder != null) {
|
|
|
+ builder.field(RETRIEVER.getPreferredName(), retrieverBuilder);
|
|
|
+ }
|
|
|
+
|
|
|
if (subSearchSourceBuilders.isEmpty() == false) {
|
|
|
if (subSearchSourceBuilders.size() == 1) {
|
|
|
builder.field(QUERY_FIELD.getPreferredName(), subSearchSourceBuilders.get(0).getQueryBuilder());
|
|
@@ -2183,4 +2192,169 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R
|
|
|
|
|
|
return collapse == null && (aggregations == null || aggregations.supportsParallelCollection(fieldCardinality));
|
|
|
}
|
|
|
+
|
|
|
+ private void validate() throws ValidationException {
|
|
|
+ var exceptions = validate(null, false);
|
|
|
+ if (exceptions != null) {
|
|
|
+ throw exceptions;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public ActionRequestValidationException validate(ActionRequestValidationException validationException, boolean isScroll) {
|
|
|
+ if (retriever() != null) {
|
|
|
+ List<String> specified = new ArrayList<>();
|
|
|
+ if (subSearches().isEmpty() == false) {
|
|
|
+ specified.add(QUERY_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (knnSearch().isEmpty() == false) {
|
|
|
+ specified.add(KNN_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (searchAfter() != null) {
|
|
|
+ specified.add(SEARCH_AFTER.getPreferredName());
|
|
|
+ }
|
|
|
+ if (terminateAfter() != DEFAULT_TERMINATE_AFTER) {
|
|
|
+ specified.add(TERMINATE_AFTER_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (sorts() != null) {
|
|
|
+ specified.add(SORT_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (rescores() != null) {
|
|
|
+ specified.add(RESCORE_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (minScore() != null) {
|
|
|
+ specified.add(MIN_SCORE_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (rankBuilder() != null) {
|
|
|
+ specified.add(RANK_FIELD.getPreferredName());
|
|
|
+ }
|
|
|
+ if (specified.isEmpty() == false) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "cannot specify [" + RETRIEVER.getPreferredName() + "] and " + specified,
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isScroll) {
|
|
|
+ if (trackTotalHitsUpTo() != null && trackTotalHitsUpTo() != SearchContext.TRACK_TOTAL_HITS_ACCURATE) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "disabling [track_total_hits] is not allowed in a scroll context",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (from() > 0) {
|
|
|
+ validationException = addValidationError("using [from] is not allowed in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ if (size() == 0) {
|
|
|
+ validationException = addValidationError("[size] cannot be [0] in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ if (rescores() != null && rescores().isEmpty() == false) {
|
|
|
+ validationException = addValidationError("using [rescore] is not allowed in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isEmpty(searchAfter()) == false) {
|
|
|
+ validationException = addValidationError("[search_after] cannot be used in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ if (collapse() != null) {
|
|
|
+ validationException = addValidationError("cannot use `collapse` in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (slice() != null) {
|
|
|
+ if (pointInTimeBuilder() == null && (isScroll == false)) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[slice] can only be used with [scroll] or [point-in-time] requests",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (from() > 0 && CollectionUtils.isEmpty(searchAfter()) == false) {
|
|
|
+ validationException = addValidationError("[from] parameter must be set to 0 when [search_after] is used", validationException);
|
|
|
+ }
|
|
|
+ if (storedFields() != null) {
|
|
|
+ if (storedFields().fetchFields() == false) {
|
|
|
+ if (fetchSource() != null && fetchSource().fetchSource()) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[stored_fields] cannot be disabled if [_source] is requested",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (fetchFields() != null) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[stored_fields] cannot be disabled when using the [fields] option",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (subSearches().size() >= 2 && rankBuilder() == null) {
|
|
|
+ validationException = addValidationError("[sub_searches] requires [rank]", validationException);
|
|
|
+ }
|
|
|
+ if (aggregations() != null) {
|
|
|
+ validationException = aggregations().validate(validationException);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rankBuilder() != null) {
|
|
|
+ int s = size() == -1 ? SearchService.DEFAULT_SIZE : size();
|
|
|
+ if (s == 0) {
|
|
|
+ validationException = addValidationError("[rank] requires [size] greater than [0]", validationException);
|
|
|
+ }
|
|
|
+ if (s > rankBuilder().rankWindowSize()) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[rank] requires [rank_window_size: "
|
|
|
+ + rankBuilder().rankWindowSize()
|
|
|
+ + "]"
|
|
|
+ + " be greater than or equal to [size: "
|
|
|
+ + s
|
|
|
+ + "]",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ int queryCount = subSearches().size() + knnSearch().size();
|
|
|
+ if (rankBuilder().isCompoundBuilder() && queryCount < 2) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[rank] requires a minimum of [2] result sets using a combination of sub searches and/or knn searches",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (isScroll) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used in a scroll context", validationException);
|
|
|
+ }
|
|
|
+ if (rescores() != null && rescores().isEmpty() == false) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [rescore]", validationException);
|
|
|
+ }
|
|
|
+ if (sorts() != null && sorts().isEmpty() == false) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [sort]", validationException);
|
|
|
+ }
|
|
|
+ if (collapse() != null) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [collapse]", validationException);
|
|
|
+ }
|
|
|
+ if (suggest() != null && suggest().getSuggestions().isEmpty() == false) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [suggest]", validationException);
|
|
|
+ }
|
|
|
+ if (highlighter() != null) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [highlighter]", validationException);
|
|
|
+ }
|
|
|
+ if (pointInTimeBuilder() != null) {
|
|
|
+ validationException = addValidationError("[rank] cannot be used with [point in time]", validationException);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rescores() != null) {
|
|
|
+ for (@SuppressWarnings("rawtypes")
|
|
|
+ var rescorer : rescores()) {
|
|
|
+ validationException = rescorer.validate(this, validationException);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pointInTimeBuilder() == null && sorts() != null) {
|
|
|
+ for (var sortBuilder : sorts()) {
|
|
|
+ if (sortBuilder instanceof FieldSortBuilder fieldSortBuilder
|
|
|
+ && ShardDocSortField.NAME.equals(fieldSortBuilder.getFieldName())) {
|
|
|
+ validationException = addValidationError(
|
|
|
+ "[" + FieldSortBuilder.SHARD_DOC_FIELD_NAME + "] sort field cannot be used without [point in time]",
|
|
|
+ validationException
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return validationException;
|
|
|
+ }
|
|
|
}
|