diff --git a/src/main/java/org/opensearch/knn/common/KNNConstants.java b/src/main/java/org/opensearch/knn/common/KNNConstants.java index ce6095fd05..66b4893d94 100644 --- a/src/main/java/org/opensearch/knn/common/KNNConstants.java +++ b/src/main/java/org/opensearch/knn/common/KNNConstants.java @@ -35,6 +35,8 @@ public class KNNConstants { public static final String METHOD_PARAMETER_SPACE_TYPE = "space_type"; // used for mapping parameter // used for defining toplevel parameter public static final String TOP_LEVEL_PARAMETER_SPACE_TYPE = METHOD_PARAMETER_SPACE_TYPE; + + public static final String SEARCH_MODE_PARAMETER = "search_mode"; public static final String COMPOUND_EXTENSION = "c"; public static final String MODEL = "model"; public static final String MODELS = "models"; diff --git a/src/main/java/org/opensearch/knn/index/SearchMode.java b/src/main/java/org/opensearch/knn/index/SearchMode.java new file mode 100644 index 0000000000..d23975563b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/SearchMode.java @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import java.util.Arrays; +import java.util.Locale; + +public enum SearchMode { + ANN("ann") { + + }, + EXACT("exact") { + + }; + + private final String value; + + SearchMode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static SearchMode getSearchMode(String searchModeName) { + for (SearchMode currentSearchMode : SearchMode.values()) { + if (currentSearchMode.getValue().equalsIgnoreCase(searchModeName)) { + return currentSearchMode; + } + } + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Unable to find search mode: %s . Valid values are: %s", + searchModeName, + Arrays.toString(SearchMode.values()) + ) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java index 443b12b9c4..26491484a0 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java @@ -7,6 +7,8 @@ import lombok.extern.log4j.Log4j2; import org.opensearch.common.StopWatch; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SearchMode; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.vectorvalues.KNNVectorValues; @@ -90,6 +92,11 @@ public void merge(MergeState mergeState) { assert mergeState != null; assert mergeState.mergeFieldInfos != null; for (FieldInfo fieldInfo : mergeState.mergeFieldInfos) { + if (fieldInfo.attributes().containsKey(KNNConstants.SEARCH_MODE_PARAMETER) + && SearchMode.EXACT.equals(fieldInfo.attributes().get(KNNConstants.SEARCH_MODE_PARAMETER))) { + logger.debug("Field with exact search mode, skipping graph creation during merge."); + continue; + } DocValuesType type = fieldInfo.getDocValuesType(); if (type == DocValuesType.BINARY && fieldInfo.attributes().containsKey(KNNVectorFieldMapper.KNN_FIELD)) { StopWatch stopWatch = new StopWatch(); diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java index 67f3efa5bb..6c9e151512 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java @@ -39,13 +39,10 @@ import org.opensearch.index.mapper.ParametrizedFieldMapper; import org.opensearch.index.mapper.ParseContext; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.*; import org.opensearch.knn.index.engine.EngineResolver; import org.opensearch.knn.index.engine.KNNMethodConfigContext; import org.opensearch.knn.index.engine.KNNMethodContext; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.VectorField; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.engine.ResolvedMethodContext; import org.opensearch.knn.index.engine.SpaceTypeResolver; @@ -184,6 +181,13 @@ public static class Builder extends ParametrizedFieldMapper.Builder { SpaceType.UNDEFINED.getValue() ).setValidator(SpaceType::getSpace); + protected final Parameter searchMode = Parameter.stringParam( + KNNConstants.SEARCH_MODE_PARAMETER, + false, + m -> toType(m).originalMappingParameters.getSearchMode(), + SearchMode.ANN.getValue() + ).setValidator(SearchMode::getSearchMode); + protected final Parameter> meta = Parameter.metaParam(); protected ModelDao modelDao; @@ -221,7 +225,8 @@ protected List> getParameters() { modelId, mode, compressionLevel, - topLevelSpaceType + topLevelSpaceType, + searchMode ); } @@ -378,6 +383,9 @@ public Mapper.Builder parse(String name, Map node, ParserCont ); } + // TODO: validate and make sure this would work even if knn setting is disabled. + validateSearchMode(builder); + // Check for flat configuration and validate only if index is created after 2.17 if (isKNNDisabled(parserContext.getSettings()) && parserContext.indexVersionCreated().onOrAfter(Version.V_2_17_0)) { validateFromFlat(builder); @@ -404,6 +412,17 @@ public Mapper.Builder parse(String name, Map node, ParserCont return builder; } + private void validateSearchMode(KNNVectorFieldMapper.Builder builder) { + final KNNMethodContext knnMethodContext = builder.knnMethodContext.get(); + final SpaceType spaceType = SpaceType.getSpace(builder.topLevelSpaceType.get()); + final SearchMode searchMode = SearchMode.getSearchMode(builder.searchMode.get()); + if (SearchMode.EXACT.equals(searchMode) && (knnMethodContext != null || spaceType != SpaceType.UNDEFINED)) { + throw new MapperParsingException( + "knnMethodContext or space type is not expected to be passed if the" + " field have exact search mode." + ); + } + } + private void validateSpaceType(KNNVectorFieldMapper.Builder builder) { final KNNMethodContext knnMethodContext = builder.knnMethodContext.get(); // if context is defined diff --git a/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java index 340c450ee3..8e5cf68191 100644 --- a/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java +++ b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java @@ -45,6 +45,7 @@ public final class OriginalMappingParameters { private final String compressionLevel; private final String modelId; private final String topLevelSpaceType; + private final String searchMode; /** * Initialize the parameters from the builder @@ -60,6 +61,7 @@ public OriginalMappingParameters(KNNVectorFieldMapper.Builder builder) { this.compressionLevel = builder.compressionLevel.get(); this.modelId = builder.modelId.get(); this.topLevelSpaceType = builder.topLevelSpaceType.get(); + this.searchMode = builder.searchMode.get(); } /** diff --git a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java index 9e637be9b6..01b1c1cb19 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java @@ -39,10 +39,7 @@ import org.opensearch.index.mapper.ParseContext; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.VectorDataType; -import org.opensearch.knn.index.VectorField; +import org.opensearch.knn.index.*; import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.engine.KNNMethodConfigContext; @@ -123,7 +120,16 @@ public void testBuilder_getParameters() { modelDao, CURRENT, null, - new OriginalMappingParameters(VectorDataType.DEFAULT, TEST_DIMENSION, null, null, null, null, SpaceType.UNDEFINED.getValue()) + new OriginalMappingParameters( + VectorDataType.DEFAULT, + TEST_DIMENSION, + null, + null, + null, + null, + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() + ) ); assertEquals(10, builder.getParameters().size()); @@ -1125,7 +1131,8 @@ public void testMethodFieldMapperParseCreateField_validInput_thenDifferentFieldT Mode.NOT_CONFIGURED.getName(), CompressionLevel.NOT_CONFIGURED.getName(), null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ); originalMappingParameters.setResolvedKnnMethodContext(knnMethodContext); MethodFieldMapper methodFieldMapper = MethodFieldMapper.createFieldMapper( @@ -1233,7 +1240,8 @@ public void testModelFieldMapperParseCreateField_validInput_thenDifferentFieldTy Mode.NOT_CONFIGURED.getName(), CompressionLevel.NOT_CONFIGURED.getName(), MODEL_ID, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ); ModelFieldMapper modelFieldMapper = ModelFieldMapper.createFieldMapper( @@ -1328,7 +1336,8 @@ public void testLuceneFieldMapper_parseCreateField_docValues_withFloats() { Mode.NOT_CONFIGURED.getName(), CompressionLevel.NOT_CONFIGURED.getName(), null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ); originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); @@ -1388,7 +1397,8 @@ public void testLuceneFieldMapper_parseCreateField_docValues_withFloats() { Mode.NOT_CONFIGURED.getName(), CompressionLevel.NOT_CONFIGURED.getName(), null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ); originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); luceneFieldMapper = LuceneFieldMapper.createFieldMapper( @@ -1429,7 +1439,8 @@ public void testLuceneFieldMapper_parseCreateField_docValues_withBytes() { Mode.NOT_CONFIGURED.getName(), CompressionLevel.NOT_CONFIGURED.getName(), null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ); originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); diff --git a/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java b/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java index 4b089b149e..6792394825 100644 --- a/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java +++ b/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java @@ -6,6 +6,7 @@ package org.opensearch.knn.index.mapper; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SearchMode; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.engine.KNNEngine; @@ -18,12 +19,28 @@ public class OriginalMappingParametersTests extends KNNTestCase { public void testIsLegacy() { assertTrue( - new OriginalMappingParameters(VectorDataType.DEFAULT, 123, null, null, null, null, SpaceType.UNDEFINED.getValue()) - .isLegacyMapping() + new OriginalMappingParameters( + VectorDataType.DEFAULT, + 123, + null, + null, + null, + null, + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() + ).isLegacyMapping() ); assertFalse( - new OriginalMappingParameters(VectorDataType.DEFAULT, 123, null, null, null, "model-id", SpaceType.UNDEFINED.getValue()) - .isLegacyMapping() + new OriginalMappingParameters( + VectorDataType.DEFAULT, + 123, + null, + null, + null, + "model-id", + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() + ).isLegacyMapping() ); assertFalse( new OriginalMappingParameters( @@ -33,7 +50,8 @@ public void testIsLegacy() { Mode.ON_DISK.getName(), null, null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ).isLegacyMapping() ); assertFalse( @@ -44,7 +62,8 @@ public void testIsLegacy() { null, CompressionLevel.x2.getName(), null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ).isLegacyMapping() ); assertFalse( @@ -55,7 +74,8 @@ public void testIsLegacy() { null, null, null, - SpaceType.UNDEFINED.getValue() + SpaceType.UNDEFINED.getValue(), + SearchMode.ANN.getValue() ).isLegacyMapping() ); }