org.opensearch.index.IndexSortConfig Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opensearch Show documentation
Show all versions of opensearch Show documentation
OpenSearch subproject :server
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.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.opensearch.common.annotation.PublicApi;
import org.opensearch.common.settings.Setting;
import org.opensearch.common.settings.Settings;
import org.opensearch.index.fielddata.IndexFieldData;
import org.opensearch.index.mapper.MappedFieldType;
import org.opensearch.search.MultiValueMode;
import org.opensearch.search.lookup.SearchLookup;
import org.opensearch.search.sort.SortOrder;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
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`
*
*
*
*
* @opensearch.api
*/
@PublicApi(since = "1.0.0")
public final class IndexSortConfig {
/**
* 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;
public IndexSortConfig(IndexSettings indexSettings) {
final Settings settings = indexSettings.getSettings();
List fields = INDEX_SORT_FIELD_SETTING.get(settings);
this.sortSpecs = fields.stream().map((name) -> new FieldSortSpec(name)).toArray(FieldSortSpec[]::new);
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(
boolean shouldWidenIndexSortType,
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 + "]");
}
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 + "]");
}
if (shouldWidenIndexSortType == true) {
sortFields[i] = fieldData.wideSortField(sortSpec.missingValue, mode, null, reverse);
} else {
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() + "]");
}
}
/**
* Field sort specification
*
* @opensearch.internal
*/
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();
}
}
}