org.elasticsearch.search.searchafter.SearchAfterBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.search.searchafter;
import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.SortedSetSortField;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
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.text.Text;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.sort.SortAndFormats;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class SearchAfterBuilder implements ToXContentObject, Writeable {
public static final ParseField SEARCH_AFTER = new ParseField("search_after");
private static final Object[] EMPTY_SORT_VALUES = new Object[0];
private Object[] sortValues = EMPTY_SORT_VALUES;
public SearchAfterBuilder() {}
/**
* Read from a stream.
*/
public SearchAfterBuilder(StreamInput in) throws IOException {
int size = in.readVInt();
sortValues = new Object[size];
for (int i = 0; i < size; i++) {
sortValues[i] = in.readGenericValue();
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(sortValues.length);
for (Object fieldValue : sortValues) {
out.writeGenericValue(fieldValue);
}
}
public SearchAfterBuilder setSortValues(Object[] values) {
if (values == null) {
throw new NullPointerException("Values cannot be null.");
}
if (values.length == 0) {
throw new IllegalArgumentException("Values must contains at least one value.");
}
for (int i = 0; i < values.length; i++) {
if (values[i] == null) continue;
if (values[i] instanceof String) continue;
if (values[i] instanceof Text) continue;
if (values[i] instanceof Long) continue;
if (values[i] instanceof Integer) continue;
if (values[i] instanceof Short) continue;
if (values[i] instanceof Byte) continue;
if (values[i] instanceof Double) continue;
if (values[i] instanceof Float) continue;
if (values[i] instanceof Boolean) continue;
if (values[i] instanceof BigInteger) continue;
throw new IllegalArgumentException("Can't handle " + SEARCH_AFTER + " field value of type [" + values[i].getClass() + "]");
}
sortValues = new Object[values.length];
System.arraycopy(values, 0, sortValues, 0, values.length);
return this;
}
public Object[] getSortValues() {
return Arrays.copyOf(sortValues, sortValues.length);
}
public static FieldDoc buildFieldDoc(SortAndFormats sort, Object[] values, @Nullable String collapseField) {
if (sort == null || sort.sort.getSort() == null || sort.sort.getSort().length == 0) {
throw new IllegalArgumentException("Sort must contain at least one field.");
}
SortField[] sortFields = sort.sort.getSort();
if (sortFields.length != values.length) {
throw new IllegalArgumentException(
SEARCH_AFTER.getPreferredName() + " has " + values.length + " value(s) but sort has " + sort.sort.getSort().length + "."
);
}
if (collapseField != null && (sortFields.length > 1 || sortFields[0].getField().equals(collapseField) == false)) {
throw new IllegalArgumentException(
"Cannot use [collapse] in conjunction with ["
+ SEARCH_AFTER.getPreferredName()
+ "] unless the search is sorted on the same field. Multiple sort fields are not allowed."
);
}
Object[] fieldValues = new Object[sortFields.length];
for (int i = 0; i < sortFields.length; i++) {
SortField sortField = sortFields[i];
DocValueFormat format = sort.formats[i];
if (values[i] != null) {
fieldValues[i] = convertValueFromSortField(values[i], sortField, format);
} else {
fieldValues[i] = null;
}
}
/*
* We set the doc id to Integer.MAX_VALUE in order to make sure that the search starts "after" the first document that is equal to
* the field values.
*/
return new FieldDoc(Integer.MAX_VALUE, 0, fieldValues);
}
/**
* Returns the inner {@link SortField.Type} expected for this sort field.
*/
static SortField.Type extractSortType(SortField sortField) {
if (sortField.getComparatorSource() instanceof IndexFieldData.XFieldComparatorSource) {
return ((IndexFieldData.XFieldComparatorSource) sortField.getComparatorSource()).reducedType();
} else if (sortField instanceof SortedSetSortField) {
return SortField.Type.STRING;
} else if (sortField instanceof SortedNumericSortField) {
return ((SortedNumericSortField) sortField).getNumericType();
} else if ("LatLonPointSortField".equals(sortField.getClass().getSimpleName())) {
// for geo distance sorting
return SortField.Type.DOUBLE;
} else {
return sortField.getType();
}
}
static Object convertValueFromSortField(Object value, SortField sortField, DocValueFormat format) {
SortField.Type sortType = extractSortType(sortField);
return convertValueFromSortType(sortField.getField(), sortType, value, format);
}
private static Object convertValueFromSortType(String fieldName, SortField.Type sortType, Object value, DocValueFormat format) {
try {
switch (sortType) {
case DOC:
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.parseInt(value.toString());
case SCORE:
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.parseFloat(value.toString());
case INT:
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.parseInt(value.toString());
case DOUBLE:
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return Double.parseDouble(value.toString());
case LONG:
// for unsigned_long field type we want to pass search_after value through formatting
if (value instanceof Number && format != DocValueFormat.UNSIGNED_LONG_SHIFTED) {
return ((Number) value).longValue();
}
return format.parseLong(
value.toString(),
false,
() -> { throw new IllegalStateException("now() is not allowed in [search_after] key"); }
);
case FLOAT:
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.parseFloat(value.toString());
case STRING_VAL:
case STRING:
return format.parseBytesRef(value.toString());
default:
throw new IllegalArgumentException(
"Comparator type [" + sortType.name() + "] for field [" + fieldName + "] is not supported."
);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Failed to parse " + SEARCH_AFTER.getPreferredName() + " value for field [" + fieldName + "].",
e
);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
innerToXContent(builder);
builder.endObject();
return builder;
}
void innerToXContent(XContentBuilder builder) throws IOException {
builder.array(SEARCH_AFTER.getPreferredName(), sortValues);
}
public static SearchAfterBuilder fromXContent(XContentParser parser) throws IOException {
SearchAfterBuilder builder = new SearchAfterBuilder();
XContentParser.Token token = parser.currentToken();
List