
org.elasticsearch.index.IndexSortConfig Maven / Gradle / Ivy
/*
* 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.index;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.SortedSetSortField;
import org.elasticsearch.Version;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.search.sort.SortOrder;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Holds all the information that is used to build the sort order of an index.
*
* The index sort settings are final and can be defined only at index creation.
* These settings are divided in four lists that are merged during the initialization of this class:
*
* - `index.sort.field`: the field or a list of field to use for the sort
* - `index.sort.order` the {@link SortOrder} to use for the field or a list of {@link SortOrder}
* for each field defined in `index.sort.field`.
*
* - `index.sort.mode`: the {@link MultiValueMode} to use for the field or a list of orders
* for each field defined in `index.sort.field`.
*
* - `index.sort.missing`: the missing value to use for the field or a list of missing values
* for each field defined in `index.sort.field`
*
*
*
**/
public final class IndexSortConfig {
private static final DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(IndexSortConfig.class);
/**
* The list of field names
*/
public static final Setting> INDEX_SORT_FIELD_SETTING = Setting.listSetting(
"index.sort.field",
Collections.emptyList(),
Function.identity(),
Setting.Property.IndexScope,
Setting.Property.Final
);
/**
* The {@link SortOrder} for each specified sort field (ie. asc or desc).
*/
public static final Setting> INDEX_SORT_ORDER_SETTING = Setting.listSetting(
"index.sort.order",
Collections.emptyList(),
IndexSortConfig::parseOrderMode,
Setting.Property.IndexScope,
Setting.Property.Final
);
/**
* The {@link MultiValueMode} for each specified sort field (ie. max or min).
*/
public static final Setting> INDEX_SORT_MODE_SETTING = Setting.listSetting(
"index.sort.mode",
Collections.emptyList(),
IndexSortConfig::parseMultiValueMode,
Setting.Property.IndexScope,
Setting.Property.Final
);
/**
* The missing value for each specified sort field (ie. _first or _last)
*/
public static final Setting> INDEX_SORT_MISSING_SETTING = Setting.listSetting(
"index.sort.missing",
Collections.emptyList(),
IndexSortConfig::validateMissingValue,
Setting.Property.IndexScope,
Setting.Property.Final
);
private static String validateMissingValue(String missing) {
if ("_last".equals(missing) == false && "_first".equals(missing) == false) {
throw new IllegalArgumentException("Illegal missing value:[" + missing + "], " + "must be one of [_last, _first]");
}
return missing;
}
private static SortOrder parseOrderMode(String value) {
try {
return SortOrder.fromString(value);
} catch (Exception e) {
throw new IllegalArgumentException("Illegal sort order:" + value);
}
}
private static MultiValueMode parseMultiValueMode(String value) {
MultiValueMode mode = MultiValueMode.fromString(value);
if (mode != MultiValueMode.MAX && mode != MultiValueMode.MIN) {
throw new IllegalArgumentException(
"Illegal index sort mode:[" + mode + "], " + "must be one of [" + MultiValueMode.MAX + ", " + MultiValueMode.MIN + "]"
);
}
return mode;
}
// visible for tests
final FieldSortSpec[] sortSpecs;
private final Version indexCreatedVersion;
private final String indexName;
public IndexSortConfig(IndexSettings indexSettings) {
final Settings settings = indexSettings.getSettings();
this.indexCreatedVersion = indexSettings.getIndexVersionCreated();
this.indexName = indexSettings.getIndex().getName();
List fields = INDEX_SORT_FIELD_SETTING.get(settings);
this.sortSpecs = fields.stream().map((name) -> new FieldSortSpec(name)).toArray(FieldSortSpec[]::new);
if (sortSpecs.length > 0 && indexSettings.getIndexVersionCreated().before(Version.V_6_0_0_alpha1)) {
/**
* This index might be assigned to a node where the index sorting feature is not available
* (ie. versions prior to {@link Version.V_6_0_0_alpha1_UNRELEASED}) so we must fail here rather than later.
*/
throw new IllegalArgumentException(
"unsupported index.version.created:"
+ indexSettings.getIndexVersionCreated()
+ ", can't set index.sort on versions prior to "
+ Version.V_6_0_0_alpha1
);
}
if (INDEX_SORT_ORDER_SETTING.exists(settings)) {
List orders = INDEX_SORT_ORDER_SETTING.get(settings);
if (orders.size() != sortSpecs.length) {
throw new IllegalArgumentException(
"index.sort.field:" + fields + " index.sort.order:" + orders.toString() + ", size mismatch"
);
}
for (int i = 0; i < sortSpecs.length; i++) {
sortSpecs[i].order = orders.get(i);
}
}
if (INDEX_SORT_MODE_SETTING.exists(settings)) {
List modes = INDEX_SORT_MODE_SETTING.get(settings);
if (modes.size() != sortSpecs.length) {
throw new IllegalArgumentException("index.sort.field:" + fields + " index.sort.mode:" + modes + ", size mismatch");
}
for (int i = 0; i < sortSpecs.length; i++) {
sortSpecs[i].mode = modes.get(i);
}
}
if (INDEX_SORT_MISSING_SETTING.exists(settings)) {
List missingValues = INDEX_SORT_MISSING_SETTING.get(settings);
if (missingValues.size() != sortSpecs.length) {
throw new IllegalArgumentException(
"index.sort.field:" + fields + " index.sort.missing:" + missingValues + ", size mismatch"
);
}
for (int i = 0; i < sortSpecs.length; i++) {
sortSpecs[i].missingValue = missingValues.get(i);
}
}
}
/**
* Returns true if the index should be sorted
*/
public boolean hasIndexSort() {
return sortSpecs.length > 0;
}
public boolean hasPrimarySortOnField(String field) {
return sortSpecs.length > 0 && sortSpecs[0].field.equals(field);
}
/**
* Builds the {@link Sort} order from the settings for this index
* or returns null if this index has no sort.
*/
public Sort buildIndexSort(
Function fieldTypeLookup,
BiFunction, IndexFieldData>> fieldDataLookup
) {
if (hasIndexSort() == false) {
return null;
}
final SortField[] sortFields = new SortField[sortSpecs.length];
for (int i = 0; i < sortSpecs.length; i++) {
FieldSortSpec sortSpec = sortSpecs[i];
final MappedFieldType ft = fieldTypeLookup.apply(sortSpec.field);
if (ft == null) {
throw new IllegalArgumentException("unknown index sort field:[" + sortSpec.field + "]");
}
if (Objects.equals(ft.name(), sortSpec.field) == false) {
if (this.indexCreatedVersion.onOrAfter(Version.V_7_13_0)) {
throw new IllegalArgumentException("Cannot use alias [" + sortSpec.field + "] as an index sort field");
} else {
DEPRECATION_LOGGER.warn(
DeprecationCategory.MAPPINGS,
"index-sort-aliases",
"Index sort for index ["
+ indexName
+ "] defined on field ["
+ sortSpec.field
+ "] which resolves to field ["
+ ft.name()
+ "]. "
+ "You will not be able to define an index sort over aliased fields in new indexes"
);
}
}
boolean reverse = sortSpec.order == null ? false : (sortSpec.order == SortOrder.DESC);
MultiValueMode mode = sortSpec.mode;
if (mode == null) {
mode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN;
}
IndexFieldData> fieldData;
try {
fieldData = fieldDataLookup.apply(
ft,
() -> { throw new UnsupportedOperationException("index sorting not supported on runtime field [" + ft.name() + "]"); }
);
} catch (Exception e) {
throw new IllegalArgumentException("docvalues not found for index sort field:[" + sortSpec.field + "]", e);
}
if (fieldData == null) {
throw new IllegalArgumentException("docvalues not found for index sort field:[" + sortSpec.field + "]");
}
sortFields[i] = fieldData.sortField(sortSpec.missingValue, mode, null, reverse);
validateIndexSortField(sortFields[i]);
}
return new Sort(sortFields);
}
private void validateIndexSortField(SortField sortField) {
SortField.Type type = getSortFieldType(sortField);
if (ALLOWED_INDEX_SORT_TYPES.contains(type) == false) {
throw new IllegalArgumentException("invalid index sort field:[" + sortField.getField() + "]");
}
}
static class FieldSortSpec {
final String field;
SortOrder order;
MultiValueMode mode;
String missingValue;
FieldSortSpec(String field) {
this.field = field;
}
}
/** We only allow index sorting on these types */
private static final EnumSet ALLOWED_INDEX_SORT_TYPES = EnumSet.of(
SortField.Type.STRING,
SortField.Type.LONG,
SortField.Type.INT,
SortField.Type.DOUBLE,
SortField.Type.FLOAT
);
public static SortField.Type getSortFieldType(SortField sortField) {
if (sortField instanceof SortedSetSortField) {
return SortField.Type.STRING;
} else if (sortField instanceof SortedNumericSortField) {
return ((SortedNumericSortField) sortField).getNumericType();
} else {
return sortField.getType();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy