diff --git a/CHANGELOG.md b/CHANGELOG.md index 5171b9289b81b..9f381c533b5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add cluster defaults for merge autoThrottle, maxMergeThreads, and maxMergeCount; Add segment size filter to the merged segment warmer ([#19629](https://github.com/opensearch-project/OpenSearch/pull/19629)) - Add build-tooling to run in FIPS environment ([#18921](https://github.com/opensearch-project/OpenSearch/pull/18921)) - Add SMILE/CBOR/YAML document format support to Bulk GRPC endpoint ([#19744](https://github.com/opensearch-project/OpenSearch/pull/19744)) +- Implement GRPC ConstantScoreQuery, FuzzyQuery, MatchBoolPrefixQuery, MatchPhrasePrefix, PrefixQuery, MatchQuery ([#19854](https://github.com/opensearch-project/OpenSearch/pull/19854)) ### Changed - Faster `terms` query creation for `keyword` field with index and docValues enabled ([#19350](https://github.com/opensearch-project/OpenSearch/pull/19350)) diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/OperatorProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/OperatorProtoUtils.java index 3482503aff578..d45adbb0b81f2 100644 --- a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/OperatorProtoUtils.java +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/OperatorProtoUtils.java @@ -26,7 +26,7 @@ private OperatorProtoUtils() { * @param op * @return */ - protected static Operator fromEnum(org.opensearch.protobufs.Operator op) { + public static Operator fromEnum(org.opensearch.protobufs.Operator op) { switch (op) { case OPERATOR_AND: return Operator.AND; diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..792bc0c040f71 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverter.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverterRegistry; + +/** + * Converter for ConstantScore queries. + * This class implements the QueryBuilderProtoConverter interface to provide ConstantScore query support + * for the gRPC transport module. + */ +public class ConstantScoreQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + + /** + * Default constructor for ConstantScoreQueryBuilderProtoConverter. + */ + public ConstantScoreQueryBuilderProtoConverter() {} + + private QueryBuilderProtoConverterRegistry registry; + + @Override + public void setRegistry(QueryBuilderProtoConverterRegistry registry) { + this.registry = registry; + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.CONSTANT_SCORE; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || queryContainer.getQueryContainerCase() != QueryContainer.QueryContainerCase.CONSTANT_SCORE) { + throw new IllegalArgumentException("QueryContainer does not contain a ConstantScore query"); + } + + return ConstantScoreQueryBuilderProtoUtils.fromProto(queryContainer.getConstantScore(), registry); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..00562b39d0dda --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtils.java @@ -0,0 +1,66 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.ConstantScoreQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.ConstantScoreQuery; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverterRegistry; + +/** + * Utility class for converting ConstantScoreQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of constant score queries + * into their corresponding OpenSearch ConstantScoreQueryBuilder implementations for search operations. + */ +class ConstantScoreQueryBuilderProtoUtils { + + private ConstantScoreQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer ConstantScoreQuery to an OpenSearch ConstantScoreQueryBuilder. + * Similar to {@link ConstantScoreQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * ConstantScoreQueryBuilder with the appropriate filter query, boost, and query name. + * + * @param constantScoreQueryProto The Protocol Buffer ConstantScoreQuery object + * @param registry The registry to use for converting the nested filter query + * @return A configured ConstantScoreQueryBuilder instance + */ + static ConstantScoreQueryBuilder fromProto(ConstantScoreQuery constantScoreQueryProto, QueryBuilderProtoConverterRegistry registry) { + if (registry == null) { + throw new IllegalArgumentException("QueryBuilderProtoConverterRegistry cannot be null"); + } + String queryName = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + + if (!constantScoreQueryProto.hasFilter()) { + throw new IllegalArgumentException("ConstantScore query must have a filter query"); + } + + QueryBuilder query = registry.fromProto(constantScoreQueryProto.getFilter()); + if (query == null) { + throw new IllegalArgumentException("Filter query cannot be null for constant_score query"); + } + + if (constantScoreQueryProto.hasBoost()) { + boost = constantScoreQueryProto.getBoost(); + } + + if (constantScoreQueryProto.hasXName()) { + queryName = constantScoreQueryProto.getXName(); + } + + ConstantScoreQueryBuilder constantScoreBuilder = new ConstantScoreQueryBuilder(query); + constantScoreBuilder.boost(boost); + constantScoreBuilder.queryName(queryName); + return constantScoreBuilder; + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..70df6f9934a2d --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverter.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; + +/** + * Converter for Fuzzy queries. + * This class implements the QueryBuilderProtoConverter interface to provide Fuzzy query support + * for the gRPC transport module. + */ +public class FuzzyQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + + /** + * Constructs a new FuzzyQueryBuilderProtoConverter. + */ + public FuzzyQueryBuilderProtoConverter() { + // Default constructor + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.FUZZY; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || !queryContainer.hasFuzzy()) { + throw new IllegalArgumentException("QueryContainer does not contain a Fuzzy query"); + } + + return FuzzyQueryBuilderProtoUtils.fromProto(queryContainer.getFuzzy()); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..a104c16193f9e --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtils.java @@ -0,0 +1,95 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.FuzzyQueryBuilder; +import org.opensearch.protobufs.FuzzyQuery; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.transport.grpc.proto.response.common.FieldValueProtoUtils; +import org.opensearch.transport.grpc.util.ProtobufEnumUtils; + +/** + * Utility class for converting FuzzyQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of fuzzy queries + * into their corresponding OpenSearch FuzzyQueryBuilder implementations for search operations. + */ +class FuzzyQueryBuilderProtoUtils { + + private FuzzyQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer FuzzyQuery to an OpenSearch FuzzyQueryBuilder. + * Similar to {@link FuzzyQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * FuzzyQueryBuilder with the appropriate field name, value, fuzziness, prefix length, + * max expansions, transpositions, rewrite method, boost, and query name. + * + * @param fuzzyQueryProto The Protocol Buffer FuzzyQuery object + * @return A configured FuzzyQueryBuilder instance + * @throws IllegalArgumentException if the field name or value is null or empty + */ + static FuzzyQueryBuilder fromProto(FuzzyQuery fuzzyQueryProto) { + String fieldName = fuzzyQueryProto.getField(); + Object value = FieldValueProtoUtils.fromProto(fuzzyQueryProto.getValue(), false); + Fuzziness fuzziness = FuzzyQueryBuilder.DEFAULT_FUZZINESS; + int prefixLength = FuzzyQueryBuilder.DEFAULT_PREFIX_LENGTH; + int maxExpansions = FuzzyQueryBuilder.DEFAULT_MAX_EXPANSIONS; + boolean transpositions = FuzzyQueryBuilder.DEFAULT_TRANSPOSITIONS; + String rewrite = null; + String queryName = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + + if (fuzzyQueryProto.hasBoost()) { + boost = fuzzyQueryProto.getBoost(); + } + + if (fuzzyQueryProto.hasFuzziness()) { + org.opensearch.protobufs.Fuzziness fuzzinessProto = fuzzyQueryProto.getFuzziness(); + if (fuzzinessProto.hasString()) { + fuzziness = Fuzziness.build(fuzzinessProto.getString()); + } else if (fuzzinessProto.hasInt32()) { + fuzziness = Fuzziness.fromEdits(fuzzinessProto.getInt32()); + } + } + + if (fuzzyQueryProto.hasPrefixLength()) { + prefixLength = fuzzyQueryProto.getPrefixLength(); + } + + if (fuzzyQueryProto.hasMaxExpansions()) { + maxExpansions = fuzzyQueryProto.getMaxExpansions(); + } + + if (fuzzyQueryProto.hasTranspositions()) { + transpositions = fuzzyQueryProto.getTranspositions(); + } + + if (fuzzyQueryProto.hasRewrite()) { + MultiTermQueryRewrite rewriteEnum = fuzzyQueryProto.getRewrite(); + if (rewriteEnum != MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) { + rewrite = ProtobufEnumUtils.convertToString(rewriteEnum); + } + } + + if (fuzzyQueryProto.hasXName()) { + queryName = fuzzyQueryProto.getXName(); + } + + return new FuzzyQueryBuilder(fieldName, value).fuzziness(fuzziness) + .prefixLength(prefixLength) + .maxExpansions(maxExpansions) + .transpositions(transpositions) + .rewrite(rewrite) + .boost(boost) + .queryName(queryName); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..d56fbf8180819 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverter.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; + +/** + * Converter for MatchBoolPrefix queries. + * This class implements the QueryBuilderProtoConverter interface to provide MatchBoolPrefix query support + * for the gRPC transport module. + */ +public class MatchBoolPrefixQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + + /** + * Constructs a new MatchBoolPrefixQueryBuilderProtoConverter. + */ + public MatchBoolPrefixQueryBuilderProtoConverter() { + // Default constructor + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.MATCH_BOOL_PREFIX; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || !queryContainer.hasMatchBoolPrefix()) { + throw new IllegalArgumentException("QueryContainer does not contain a MatchBoolPrefix query"); + } + + return MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryContainer.getMatchBoolPrefix()); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..6c6aeb2106866 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtils.java @@ -0,0 +1,120 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.MatchBoolPrefixQueryBuilder; +import org.opensearch.index.query.Operator; +import org.opensearch.protobufs.MatchBoolPrefixQuery; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.transport.grpc.proto.request.search.OperatorProtoUtils; +import org.opensearch.transport.grpc.util.ProtobufEnumUtils; + +/** + * Utility class for converting MatchBoolPrefixQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of match_bool_prefix queries + * into their corresponding OpenSearch MatchBoolPrefixQueryBuilder implementations for search operations. + */ +class MatchBoolPrefixQueryBuilderProtoUtils { + + private MatchBoolPrefixQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer MatchBoolPrefixQuery to an OpenSearch MatchBoolPrefixQueryBuilder. + * Similar to {@link MatchBoolPrefixQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * MatchBoolPrefixQueryBuilder with the appropriate field name, value, operator, analyzer, + * minimum_should_match, fuzziness, boost, and query name. + * + * @param matchBoolPrefixQueryProto The Protocol Buffer MatchBoolPrefixQuery object + * @return A configured MatchBoolPrefixQueryBuilder instance + * @throws IllegalArgumentException if the field name or value is null or empty + */ + static MatchBoolPrefixQueryBuilder fromProto(MatchBoolPrefixQuery matchBoolPrefixQueryProto) { + String fieldName = matchBoolPrefixQueryProto.getField(); + Object value = matchBoolPrefixQueryProto.getQuery(); + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + String analyzer = null; + Operator operator = Operator.OR; + String minimumShouldMatch = null; + Fuzziness fuzziness = null; + int prefixLength = FuzzyQuery.defaultPrefixLength; + int maxExpansion = FuzzyQuery.defaultMaxExpansions; + boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions; + String fuzzyRewrite = null; + String queryName = null; + + if (matchBoolPrefixQueryProto.hasAnalyzer()) { + analyzer = matchBoolPrefixQueryProto.getAnalyzer(); + } + + if (matchBoolPrefixQueryProto.getOperator() != org.opensearch.protobufs.Operator.OPERATOR_UNSPECIFIED) { + operator = OperatorProtoUtils.fromEnum(matchBoolPrefixQueryProto.getOperator()); + } + + if (matchBoolPrefixQueryProto.hasMinimumShouldMatch()) { + org.opensearch.protobufs.MinimumShouldMatch minimumShouldMatchProto = matchBoolPrefixQueryProto.getMinimumShouldMatch(); + if (minimumShouldMatchProto.hasString()) { + minimumShouldMatch = minimumShouldMatchProto.getString(); + } else if (minimumShouldMatchProto.hasInt32()) { + minimumShouldMatch = String.valueOf(minimumShouldMatchProto.getInt32()); + } + } + + if (matchBoolPrefixQueryProto.hasFuzziness()) { + org.opensearch.protobufs.Fuzziness fuzzinessProto = matchBoolPrefixQueryProto.getFuzziness(); + if (fuzzinessProto.hasString()) { + fuzziness = Fuzziness.build(fuzzinessProto.getString()); + } else if (fuzzinessProto.hasInt32()) { + fuzziness = Fuzziness.fromEdits(fuzzinessProto.getInt32()); + } + } + + if (matchBoolPrefixQueryProto.hasPrefixLength()) { + prefixLength = matchBoolPrefixQueryProto.getPrefixLength(); + } + + if (matchBoolPrefixQueryProto.hasMaxExpansions()) { + maxExpansion = matchBoolPrefixQueryProto.getMaxExpansions(); + } + + if (matchBoolPrefixQueryProto.hasFuzzyTranspositions()) { + fuzzyTranspositions = matchBoolPrefixQueryProto.getFuzzyTranspositions(); + } + + if (matchBoolPrefixQueryProto.hasFuzzyRewrite()) { + MultiTermQueryRewrite rewriteEnum = matchBoolPrefixQueryProto.getFuzzyRewrite(); + if (rewriteEnum != MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) { + fuzzyRewrite = ProtobufEnumUtils.convertToString(rewriteEnum); + } + } + + if (matchBoolPrefixQueryProto.hasBoost()) { + boost = matchBoolPrefixQueryProto.getBoost(); + } + + if (matchBoolPrefixQueryProto.hasXName()) { + queryName = matchBoolPrefixQueryProto.getXName(); + } + + return new MatchBoolPrefixQueryBuilder(fieldName, value).analyzer(analyzer) + .operator(operator) + .minimumShouldMatch(minimumShouldMatch) + .boost(boost) + .queryName(queryName) + .fuzziness(fuzziness) + .prefixLength(prefixLength) + .maxExpansions(maxExpansion) + .fuzzyTranspositions(fuzzyTranspositions) + .fuzzyRewrite(fuzzyRewrite); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..1d3327678e49b --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverter.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; + +/** + * Converter for handling MatchPhrasePrefixQuery Protocol Buffer messages. + * This class converts a QueryContainer containing a match_phrase_prefix query from Protocol Buffer format + * into an OpenSearch QueryBuilder object for search operations. + */ +public class MatchPhrasePrefixQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + /** + * Constructs a new MatchPhrasePrefixQueryBuilderProtoConverter. + */ + public MatchPhrasePrefixQueryBuilderProtoConverter() { + // Default constructor + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.MATCH_PHRASE_PREFIX; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || !queryContainer.hasMatchPhrasePrefix()) { + throw new IllegalArgumentException("QueryContainer does not contain a MatchPhrasePrefix query"); + } + + return MatchPhrasePrefixQueryBuilderProtoUtils.fromProto(queryContainer.getMatchPhrasePrefix()); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..56d2a8157aa7d --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtils.java @@ -0,0 +1,87 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.MatchPhrasePrefixQueryBuilder; +import org.opensearch.index.search.MatchQuery; +import org.opensearch.index.search.MatchQuery.ZeroTermsQuery; +import org.opensearch.protobufs.MatchPhrasePrefixQuery; + +/** + * Utility class for converting MatchPhrasePrefixQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of match_phrase_prefix queries + * into their corresponding OpenSearch MatchPhrasePrefixQueryBuilder implementations for search operations. + */ +public class MatchPhrasePrefixQueryBuilderProtoUtils { + + private MatchPhrasePrefixQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer MatchPhrasePrefixQuery to an OpenSearch MatchPhrasePrefixQueryBuilder. + * Similar to {@link MatchPhrasePrefixQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * MatchPhrasePrefixQueryBuilder with the appropriate field name, value, analyzer, slop, max_expansions, + * zero_terms_query, boost, and query name. + * + * @param matchPhrasePrefixQueryProto The Protocol Buffer MatchPhrasePrefixQuery object + * @return A configured MatchPhrasePrefixQueryBuilder instance + * @throws IllegalArgumentException if the field name or value is null or empty + */ + protected static MatchPhrasePrefixQueryBuilder fromProto(MatchPhrasePrefixQuery matchPhrasePrefixQueryProto) { + String fieldName = matchPhrasePrefixQueryProto.getField(); + Object value = matchPhrasePrefixQueryProto.getQuery(); + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + String analyzer = null; + int slop = MatchQuery.DEFAULT_PHRASE_SLOP; + int maxExpansion = FuzzyQuery.defaultMaxExpansions; + String queryName = null; + ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY; + + if (matchPhrasePrefixQueryProto.hasAnalyzer()) { + analyzer = matchPhrasePrefixQueryProto.getAnalyzer(); + } + + if (matchPhrasePrefixQueryProto.hasBoost()) { + boost = matchPhrasePrefixQueryProto.getBoost(); + } + + if (matchPhrasePrefixQueryProto.hasSlop()) { + slop = matchPhrasePrefixQueryProto.getSlop(); + } + + if (matchPhrasePrefixQueryProto.hasMaxExpansions()) { + maxExpansion = matchPhrasePrefixQueryProto.getMaxExpansions(); + } + + if (matchPhrasePrefixQueryProto.hasXName()) { + queryName = matchPhrasePrefixQueryProto.getXName(); + } + + if (matchPhrasePrefixQueryProto.hasZeroTermsQuery() + && matchPhrasePrefixQueryProto.getZeroTermsQuery() != org.opensearch.protobufs.ZeroTermsQuery.ZERO_TERMS_QUERY_UNSPECIFIED) { + if (matchPhrasePrefixQueryProto.getZeroTermsQuery() == org.opensearch.protobufs.ZeroTermsQuery.ZERO_TERMS_QUERY_ALL) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL; + } else if (matchPhrasePrefixQueryProto.getZeroTermsQuery() == org.opensearch.protobufs.ZeroTermsQuery.ZERO_TERMS_QUERY_NONE) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE; + } + } + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = new MatchPhrasePrefixQueryBuilder(fieldName, value); + matchPhrasePrefixQueryBuilder.analyzer(analyzer); + matchPhrasePrefixQueryBuilder.slop(slop); + matchPhrasePrefixQueryBuilder.maxExpansions(maxExpansion); + matchPhrasePrefixQueryBuilder.queryName(queryName); + matchPhrasePrefixQueryBuilder.boost(boost); + matchPhrasePrefixQueryBuilder.zeroTermsQuery(zeroTermsQuery); + return matchPhrasePrefixQueryBuilder; + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..6dc114c3693c9 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverter.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; + +/** + * Converter for Match queries. + * This class implements the QueryBuilderProtoConverter interface to provide Match query support + * for the gRPC transport module. + */ +public class MatchQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + + /** + * Constructs a new MatchQueryBuilderProtoConverter. + */ + public MatchQueryBuilderProtoConverter() { + // Default constructor + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.MATCH; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || !queryContainer.hasMatch()) { + throw new IllegalArgumentException("QueryContainer does not contain a Match query"); + } + + return MatchQueryBuilderProtoUtils.fromProto(queryContainer.getMatch()); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..bb2be517a69b0 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtils.java @@ -0,0 +1,150 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.MatchQueryBuilder; +import org.opensearch.index.query.Operator; +import org.opensearch.index.search.MatchQuery; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.protobufs.ZeroTermsQuery; +import org.opensearch.transport.grpc.proto.request.search.OperatorProtoUtils; +import org.opensearch.transport.grpc.proto.response.common.FieldValueProtoUtils; +import org.opensearch.transport.grpc.util.ProtobufEnumUtils; + +/** + * Utility class for converting MatchQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of match queries + * into their corresponding OpenSearch MatchQueryBuilder implementations for search operations. + */ +class MatchQueryBuilderProtoUtils { + + private MatchQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer MatchQuery to an OpenSearch MatchQueryBuilder. + * Similar to {@link MatchQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * MatchQueryBuilder with the appropriate field name, value, operator, analyzer, fuzziness, + * prefix length, max expansions, fuzzy rewrite, fuzzy transpositions, lenient, + * zero terms query, boost, and query name. + * + * @param matchQueryProto The Protocol Buffer MatchQuery object + * @return A configured MatchQueryBuilder instance + * @throws IllegalArgumentException if the field name or value is null or empty + */ + static MatchQueryBuilder fromProto(org.opensearch.protobufs.MatchQuery matchQueryProto) { + if (matchQueryProto == null) { + throw new IllegalArgumentException("MatchQuery cannot be null"); + } + + String fieldName = matchQueryProto.getField(); + Object value = FieldValueProtoUtils.fromProto(matchQueryProto.getQuery(), false); + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + String minimumShouldMatch = null; + String analyzer = null; + Operator operator = MatchQueryBuilder.DEFAULT_OPERATOR; + Fuzziness fuzziness = null; + int prefixLength = FuzzyQuery.defaultPrefixLength; + int maxExpansions = FuzzyQuery.defaultMaxExpansions; + boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions; + String fuzzyRewrite = null; + boolean lenient = MatchQuery.DEFAULT_LENIENCY; + MatchQuery.ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY; + boolean autoGenerateSynonymsPhraseQuery = true; + String queryName = null; + + if (matchQueryProto.hasAnalyzer()) { + analyzer = matchQueryProto.getAnalyzer(); + } + + if (matchQueryProto.hasBoost()) { + boost = matchQueryProto.getBoost(); + } + if (matchQueryProto.hasFuzziness()) { + org.opensearch.protobufs.Fuzziness fuzzinessProto = matchQueryProto.getFuzziness(); + if (fuzzinessProto.hasString()) { + fuzziness = Fuzziness.build(fuzzinessProto.getString()); + } else if (fuzzinessProto.hasInt32()) { + fuzziness = Fuzziness.fromEdits(fuzzinessProto.getInt32()); + } + } + + if (matchQueryProto.hasPrefixLength()) { + prefixLength = matchQueryProto.getPrefixLength(); + } + + if (matchQueryProto.hasMaxExpansions()) { + maxExpansions = matchQueryProto.getMaxExpansions(); + } + + if (matchQueryProto.getOperator() != org.opensearch.protobufs.Operator.OPERATOR_UNSPECIFIED) { + operator = OperatorProtoUtils.fromEnum(matchQueryProto.getOperator()); + } + + if (matchQueryProto.hasMinimumShouldMatch()) { + org.opensearch.protobufs.MinimumShouldMatch minimumShouldMatchProto = matchQueryProto.getMinimumShouldMatch(); + if (minimumShouldMatchProto.hasString()) { + minimumShouldMatch = minimumShouldMatchProto.getString(); + } else if (minimumShouldMatchProto.hasInt32()) { + minimumShouldMatch = String.valueOf(minimumShouldMatchProto.getInt32()); + } + } + + if (matchQueryProto.hasFuzzyRewrite()) { + MultiTermQueryRewrite rewriteEnum = matchQueryProto.getFuzzyRewrite(); + if (rewriteEnum != MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) { + fuzzyRewrite = ProtobufEnumUtils.convertToString(rewriteEnum); + } + } + + if (matchQueryProto.hasFuzzyTranspositions()) { + fuzzyTranspositions = matchQueryProto.getFuzzyTranspositions(); + } + + if (matchQueryProto.hasLenient()) { + lenient = matchQueryProto.getLenient(); + } + + if (matchQueryProto.hasZeroTermsQuery() && matchQueryProto.getZeroTermsQuery() != ZeroTermsQuery.ZERO_TERMS_QUERY_UNSPECIFIED) { + if (matchQueryProto.getZeroTermsQuery() == org.opensearch.protobufs.ZeroTermsQuery.ZERO_TERMS_QUERY_ALL) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL; + } else if (matchQueryProto.getZeroTermsQuery() == org.opensearch.protobufs.ZeroTermsQuery.ZERO_TERMS_QUERY_NONE) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE; + } + } + + if (matchQueryProto.hasXName()) { + queryName = matchQueryProto.getXName(); + } + + if (matchQueryProto.hasAutoGenerateSynonymsPhraseQuery()) { + autoGenerateSynonymsPhraseQuery = matchQueryProto.getAutoGenerateSynonymsPhraseQuery(); + } + + MatchQueryBuilder matchQuery = new MatchQueryBuilder(fieldName, value); + matchQuery.operator(operator); + matchQuery.analyzer(analyzer); + matchQuery.minimumShouldMatch(minimumShouldMatch); + matchQuery.fuzziness(fuzziness); + matchQuery.fuzzyRewrite(fuzzyRewrite); + matchQuery.prefixLength(prefixLength); + matchQuery.fuzzyTranspositions(fuzzyTranspositions); + matchQuery.maxExpansions(maxExpansions); + matchQuery.lenient(lenient); + matchQuery.zeroTermsQuery(zeroTermsQuery); + matchQuery.autoGenerateSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery); + matchQuery.queryName(queryName); + matchQuery.boost(boost); + return matchQuery; + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtils.java index aa74fdf97a7b9..3dcc6605b205a 100644 --- a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtils.java +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtils.java @@ -7,11 +7,13 @@ */ package org.opensearch.transport.grpc.proto.request.search.query; +import org.opensearch.common.unit.Fuzziness; import org.opensearch.index.query.AbstractQueryBuilder; import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.Operator; import org.opensearch.index.search.MatchQuery; import org.opensearch.protobufs.MultiMatchQuery; +import org.opensearch.transport.grpc.proto.request.search.OperatorProtoUtils; import java.util.HashMap; import java.util.Map; @@ -43,6 +45,7 @@ static MultiMatchQueryBuilder fromProto(MultiMatchQuery multiMatchQueryProto) { MultiMatchQueryBuilder.Type type = MultiMatchQueryBuilder.DEFAULT_TYPE; String analyzer = null; int slop = MultiMatchQueryBuilder.DEFAULT_PHRASE_SLOP; + Fuzziness fuzziness = null; int prefixLength = MultiMatchQueryBuilder.DEFAULT_PREFIX_LENGTH; int maxExpansions = MultiMatchQueryBuilder.DEFAULT_MAX_EXPANSIONS; Operator operator = MultiMatchQueryBuilder.DEFAULT_OPERATOR; @@ -100,6 +103,15 @@ static MultiMatchQueryBuilder fromProto(MultiMatchQuery multiMatchQueryProto) { slop = multiMatchQueryProto.getSlop(); } + if (multiMatchQueryProto.hasFuzziness()) { + org.opensearch.protobufs.Fuzziness fuzzinessProto = multiMatchQueryProto.getFuzziness(); + if (fuzzinessProto.hasString()) { + fuzziness = Fuzziness.build(fuzzinessProto.getString()); + } else if (fuzzinessProto.hasInt32()) { + fuzziness = Fuzziness.fromEdits(fuzzinessProto.getInt32()); + } + } + if (multiMatchQueryProto.hasPrefixLength()) { prefixLength = multiMatchQueryProto.getPrefixLength(); } @@ -108,17 +120,9 @@ static MultiMatchQueryBuilder fromProto(MultiMatchQuery multiMatchQueryProto) { maxExpansions = multiMatchQueryProto.getMaxExpansions(); } - if (multiMatchQueryProto.hasOperator()) { - switch (multiMatchQueryProto.getOperator()) { - case OPERATOR_AND: - operator = Operator.AND; - break; - case OPERATOR_OR: - operator = Operator.OR; - break; - default: - // Keep default - } + if (multiMatchQueryProto.hasOperator() + && multiMatchQueryProto.getOperator() != org.opensearch.protobufs.Operator.OPERATOR_UNSPECIFIED) { + operator = OperatorProtoUtils.fromEnum(multiMatchQueryProto.getOperator()); } if (multiMatchQueryProto.hasMinimumShouldMatch()) { @@ -177,6 +181,7 @@ static MultiMatchQueryBuilder fromProto(MultiMatchQuery multiMatchQueryProto) { MultiMatchQueryBuilder builder = new MultiMatchQueryBuilder(value).fields(fieldsBoosts) .type(type) .analyzer(analyzer) + .fuzziness(fuzziness) .fuzzyRewrite(fuzzyRewrite) .maxExpansions(maxExpansions) .minimumShouldMatch(minimumShouldMatch) diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverter.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverter.java new file mode 100644 index 0000000000000..db1a541ca3c83 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverter.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverter; + +/** + * Converter for Prefix queries. + * This class implements the QueryBuilderProtoConverter interface to provide Prefix query support + * for the gRPC transport module. + */ +public class PrefixQueryBuilderProtoConverter implements QueryBuilderProtoConverter { + + /** + * Constructs a new PrefixQueryBuilderProtoConverter. + */ + public PrefixQueryBuilderProtoConverter() { + // Default constructor + } + + @Override + public QueryContainer.QueryContainerCase getHandledQueryCase() { + return QueryContainer.QueryContainerCase.PREFIX; + } + + @Override + public QueryBuilder fromProto(QueryContainer queryContainer) { + if (queryContainer == null || !queryContainer.hasPrefix()) { + throw new IllegalArgumentException("QueryContainer does not contain a Prefix query"); + } + + return PrefixQueryBuilderProtoUtils.fromProto(queryContainer.getPrefix()); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtils.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtils.java new file mode 100644 index 0000000000000..f5a3e575a2c15 --- /dev/null +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtils.java @@ -0,0 +1,66 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.PrefixQueryBuilder; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.protobufs.PrefixQuery; +import org.opensearch.transport.grpc.util.ProtobufEnumUtils; + +/** + * Utility class for converting PrefixQuery Protocol Buffers to OpenSearch objects. + * This class provides methods to transform Protocol Buffer representations of prefix queries + * into their corresponding OpenSearch PrefixQueryBuilder implementations for search operations. + */ +class PrefixQueryBuilderProtoUtils { + + private PrefixQueryBuilderProtoUtils() { + // Utility class, no instances + } + + /** + * Converts a Protocol Buffer PrefixQuery to an OpenSearch PrefixQueryBuilder. + * Similar to {@link PrefixQueryBuilder#fromXContent(org.opensearch.core.xcontent.XContentParser)}, this method + * parses the Protocol Buffer representation and creates a properly configured + * PrefixQueryBuilder with the appropriate field name, value, rewrite method, case insensitivity, + * boost, and query name. + * + * @param prefixQueryProto The Protocol Buffer PrefixQuery object + * @return A configured PrefixQueryBuilder instance + * @throws IllegalArgumentException if the field name or value is null or empty + */ + static PrefixQueryBuilder fromProto(PrefixQuery prefixQueryProto) { + String fieldName = prefixQueryProto.getField(); + String value = prefixQueryProto.getValue(); + String rewrite = null; + String queryName = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + boolean caseInsensitive = PrefixQueryBuilder.DEFAULT_CASE_INSENSITIVITY; + + if (prefixQueryProto.hasXName()) { + queryName = prefixQueryProto.getXName(); + } + + if (prefixQueryProto.hasBoost()) { + boost = prefixQueryProto.getBoost(); + } + + if (prefixQueryProto.hasRewrite()) { + MultiTermQueryRewrite rewriteEnum = prefixQueryProto.getRewrite(); + if (rewriteEnum != MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) { + rewrite = ProtobufEnumUtils.convertToString(rewriteEnum); + } + } + if (prefixQueryProto.hasCaseInsensitive()) { + caseInsensitive = prefixQueryProto.getCaseInsensitive(); + } + + return new PrefixQueryBuilder(fieldName, value).rewrite(rewrite).boost(boost).queryName(queryName).caseInsensitive(caseInsensitive); + } +} diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryImpl.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryImpl.java index 1eea70fb41cbc..6d11a964fb177 100644 --- a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryImpl.java +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryImpl.java @@ -61,6 +61,12 @@ protected void registerBuiltInConverters() { delegate.registerConverter(new IdsQueryBuilderProtoConverter()); delegate.registerConverter(new RangeQueryBuilderProtoConverter()); delegate.registerConverter(new TermsSetQueryBuilderProtoConverter()); + delegate.registerConverter(new ConstantScoreQueryBuilderProtoConverter()); + delegate.registerConverter(new FuzzyQueryBuilderProtoConverter()); + delegate.registerConverter(new PrefixQueryBuilderProtoConverter()); + delegate.registerConverter(new MatchQueryBuilderProtoConverter()); + delegate.registerConverter(new MatchBoolPrefixQueryBuilderProtoConverter()); + delegate.registerConverter(new MatchPhrasePrefixQueryBuilderProtoConverter()); // Set the registry on all converters so they can access each other delegate.setRegistryOnAllConverters(this); diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..fddf077a89fe8 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoConverterTests.java @@ -0,0 +1,123 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.ConstantScoreQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.protobufs.ConstantScoreQuery; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.protobufs.TermQuery; +import org.opensearch.test.OpenSearchTestCase; + +public class ConstantScoreQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private ConstantScoreQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new ConstantScoreQueryBuilderProtoConverter(); + QueryBuilderProtoConverterRegistryImpl registry = new QueryBuilderProtoConverterRegistryImpl(); + converter.setRegistry(registry); + } + + public void testGetHandledQueryCase() { + assertEquals( + "Converter should handle CONSTANT_SCORE case", + QueryContainer.QueryContainerCase.CONSTANT_SCORE, + converter.getHandledQueryCase() + ); + } + + public void testFromProto() { + FieldValue fieldValue = FieldValue.newBuilder().setString("test-value").build(); + TermQuery termQuery = TermQuery.newBuilder().setField("test-field").setValue(fieldValue).build(); + QueryContainer filterContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder() + .setFilter(filterContainer) + .setBoost(2.0f) + .setXName("test_query") + .build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setConstantScore(constantScoreQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a ConstantScoreQueryBuilder", queryBuilder instanceof ConstantScoreQueryBuilder); + + ConstantScoreQueryBuilder constantScoreQueryBuilder = (ConstantScoreQueryBuilder) queryBuilder; + assertEquals("Boost should match", 2.0f, constantScoreQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", constantScoreQueryBuilder.queryName()); + + QueryBuilder innerQuery = constantScoreQueryBuilder.innerQuery(); + assertNotNull("Inner query should not be null", innerQuery); + assertTrue("Inner query should be a TermQueryBuilder", innerQuery instanceof TermQueryBuilder); + + TermQueryBuilder termQueryBuilder = (TermQueryBuilder) innerQuery; + assertEquals("Field name should match", "test-field", termQueryBuilder.fieldName()); + assertEquals("Value should match", "test-value", termQueryBuilder.value()); + } + + public void testFromProtoWithOnlyFilter() { + FieldValue fieldValue = FieldValue.newBuilder().setString("test").build(); + TermQuery termQuery = TermQuery.newBuilder().setField("field").setValue(fieldValue).build(); + QueryContainer filterContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().setFilter(filterContainer).build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setConstantScore(constantScoreQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a ConstantScoreQueryBuilder", queryBuilder instanceof ConstantScoreQueryBuilder); + + ConstantScoreQueryBuilder constantScoreQueryBuilder = (ConstantScoreQueryBuilder) queryBuilder; + assertEquals("Default boost should be 1.0", 1.0f, constantScoreQueryBuilder.boost(), 0.0f); + assertNull("Query name should be null", constantScoreQueryBuilder.queryName()); + + // Verify the inner filter query + QueryBuilder innerQuery = constantScoreQueryBuilder.innerQuery(); + assertNotNull("Inner query should not be null", innerQuery); + assertTrue("Inner query should be a TermQueryBuilder", innerQuery instanceof TermQueryBuilder); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should indicate missing ConstantScore query", + exception.getMessage().contains("does not contain a ConstantScore query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should indicate missing ConstantScore query", + exception.getMessage().contains("does not contain a ConstantScore query") + ); + } + + public void testFromProtoWithMissingFilter() { + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().setBoost(2.0f).build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setConstantScore(constantScoreQuery).build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(queryContainer)); + + assertTrue("Exception message should indicate missing filter", exception.getMessage().contains("must have a filter query")); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..f120bef1adffb --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/ConstantScoreQueryBuilderProtoUtilsTests.java @@ -0,0 +1,114 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.ConstantScoreQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.protobufs.ConstantScoreQuery; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.protobufs.TermQuery; +import org.opensearch.test.OpenSearchTestCase; + +public class ConstantScoreQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + private QueryBuilderProtoConverterRegistryImpl registry; + + @Override + public void setUp() throws Exception { + super.setUp(); + registry = new QueryBuilderProtoConverterRegistryImpl(); + } + + public void testFromProtoWithBasicConstantScoreQuery() { + // Create a simple term query to wrap + TermQuery termQuery = TermQuery.newBuilder() + .setField("status") + .setValue(FieldValue.newBuilder().setString("active").build()) + .build(); + QueryContainer innerQueryContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().setFilter(innerQueryContainer).build(); + + ConstantScoreQueryBuilder constantScoreQueryBuilder = ConstantScoreQueryBuilderProtoUtils.fromProto(constantScoreQuery, registry); + + assertNotNull("ConstantScoreQueryBuilder should not be null", constantScoreQueryBuilder); + QueryBuilder innerQuery = constantScoreQueryBuilder.innerQuery(); + assertNotNull("Inner query should not be null", innerQuery); + assertTrue("Inner query should be a TermQueryBuilder", innerQuery instanceof TermQueryBuilder); + + TermQueryBuilder termQueryBuilder = (TermQueryBuilder) innerQuery; + assertEquals("Inner query field should match", "status", termQueryBuilder.fieldName()); + assertEquals("Inner query value should match", "active", termQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, constantScoreQueryBuilder.boost(), 0.0f); + } + + public void testFromProtoWithAllParameters() { + TermQuery termQuery = TermQuery.newBuilder() + .setField("status") + .setValue(FieldValue.newBuilder().setString("active").build()) + .build(); + QueryContainer innerQueryContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder() + .setFilter(innerQueryContainer) + .setBoost(2.5f) + .setXName("test_constant_score") + .build(); + + ConstantScoreQueryBuilder constantScoreQueryBuilder = ConstantScoreQueryBuilderProtoUtils.fromProto(constantScoreQuery, registry); + + assertNotNull("ConstantScoreQueryBuilder should not be null", constantScoreQueryBuilder); + QueryBuilder innerQuery = constantScoreQueryBuilder.innerQuery(); + assertNotNull("Inner query should not be null", innerQuery); + assertTrue("Inner query should be a TermQueryBuilder", innerQuery instanceof TermQueryBuilder); + assertEquals("Boost should match", 2.5f, constantScoreQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_constant_score", constantScoreQueryBuilder.queryName()); + } + + public void testFromProtoWithMinimalFields() { + TermQuery termQuery = TermQuery.newBuilder().setField("active").setValue(FieldValue.newBuilder().setBool(true).build()).build(); + QueryContainer innerQueryContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().setFilter(innerQueryContainer).build(); + + ConstantScoreQueryBuilder constantScoreQueryBuilder = ConstantScoreQueryBuilderProtoUtils.fromProto(constantScoreQuery, registry); + + assertNotNull("ConstantScoreQueryBuilder should not be null", constantScoreQueryBuilder); + assertNotNull("Inner query should not be null", constantScoreQueryBuilder.innerQuery()); + assertNull("Query name should be null", constantScoreQueryBuilder.queryName()); + } + + public void testFromProtoWithNullRegistry() { + TermQuery termQuery = TermQuery.newBuilder() + .setField("status") + .setValue(FieldValue.newBuilder().setString("active").build()) + .build(); + QueryContainer innerQueryContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().setFilter(innerQueryContainer).build(); + + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> ConstantScoreQueryBuilderProtoUtils.fromProto(constantScoreQuery, null) + ); + + assertEquals("QueryBuilderProtoConverterRegistry cannot be null", exception.getMessage()); + } + + public void testFromProtoWithMissingFilter() { + ConstantScoreQuery constantScoreQuery = ConstantScoreQuery.newBuilder().build(); + + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> ConstantScoreQueryBuilderProtoUtils.fromProto(constantScoreQuery, registry) + ); + + assertEquals("ConstantScore query must have a filter query", exception.getMessage()); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..95acf4e667cc4 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoConverterTests.java @@ -0,0 +1,76 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.FuzzyQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.FuzzyQuery; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.test.OpenSearchTestCase; + +public class FuzzyQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private FuzzyQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new FuzzyQueryBuilderProtoConverter(); + } + + public void testGetHandledQueryCase() { + assertEquals("Converter should handle FUZZY case", QueryContainer.QueryContainerCase.FUZZY, converter.getHandledQueryCase()); + } + + public void testFromProto() { + FieldValue fieldValue = FieldValue.newBuilder().setString("test-value").build(); + FuzzyQuery fuzzyQuery = FuzzyQuery.newBuilder() + .setField("test-field") + .setValue(fieldValue) + .setBoost(2.0f) + .setXName("test_query") + .setFuzziness(org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build()) + .setPrefixLength(2) + .setMaxExpansions(100) + .setTranspositions(true) + .setRewrite(org.opensearch.protobufs.MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .build(); + QueryContainer queryContainer = QueryContainer.newBuilder().setFuzzy(fuzzyQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a FuzzyQueryBuilder", queryBuilder instanceof FuzzyQueryBuilder); + FuzzyQueryBuilder fuzzyQueryBuilder = (FuzzyQueryBuilder) queryBuilder; + assertEquals("Field name should match", "test-field", fuzzyQueryBuilder.fieldName()); + assertEquals("Value should match", "test-value", fuzzyQueryBuilder.value()); + assertEquals("Boost should match", 2.0f, fuzzyQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", fuzzyQueryBuilder.queryName()); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should mention 'does not contain a Fuzzy query'", + exception.getMessage().contains("does not contain a Fuzzy query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should mention 'does not contain a Fuzzy query'", + exception.getMessage().contains("does not contain a Fuzzy query") + ); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..a6570acc33d75 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/FuzzyQueryBuilderProtoUtilsTests.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.FuzzyQueryBuilder; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.test.OpenSearchTestCase; + +public class FuzzyQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + public void testFromProtoWithBasicFuzzyQuery() { + FieldValue value = FieldValue.newBuilder().setString("jonh").build(); + org.opensearch.protobufs.FuzzyQuery fuzzyQuery = org.opensearch.protobufs.FuzzyQuery.newBuilder() + .setField("name") + .setValue(value) + .build(); + + FuzzyQueryBuilder fuzzyQueryBuilder = FuzzyQueryBuilderProtoUtils.fromProto(fuzzyQuery); + + assertNotNull("FuzzyQueryBuilder should not be null", fuzzyQueryBuilder); + assertEquals("Field name should match", "name", fuzzyQueryBuilder.fieldName()); + assertEquals("Value should match", "jonh", fuzzyQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, fuzzyQueryBuilder.boost(), 0.0f); + assertEquals("Default fuzziness should match", FuzzyQueryBuilder.DEFAULT_FUZZINESS, fuzzyQueryBuilder.fuzziness()); + assertEquals("Default prefix length should match", FuzzyQuery.defaultPrefixLength, fuzzyQueryBuilder.prefixLength()); + assertEquals("Default max expansions should match", FuzzyQuery.defaultMaxExpansions, fuzzyQueryBuilder.maxExpansions()); + assertEquals("Default transpositions should match", FuzzyQuery.defaultTranspositions, fuzzyQueryBuilder.transpositions()); + } + + public void testFromProtoWithAllParameters() { + FieldValue value = FieldValue.newBuilder().setString("jonh").build(); + org.opensearch.protobufs.Fuzziness fuzziness = org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build(); + + org.opensearch.protobufs.FuzzyQuery fuzzyQuery = org.opensearch.protobufs.FuzzyQuery.newBuilder() + .setField("name") + .setValue(value) + .setFuzziness(fuzziness) + .setPrefixLength(2) + .setMaxExpansions(100) + .setTranspositions(false) + .setRewrite(MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .setBoost(1.5f) + .setXName("test_query") + .build(); + + FuzzyQueryBuilder fuzzyQueryBuilder = FuzzyQueryBuilderProtoUtils.fromProto(fuzzyQuery); + + assertNotNull("FuzzyQueryBuilder should not be null", fuzzyQueryBuilder); + assertEquals("Field name should match", "name", fuzzyQueryBuilder.fieldName()); + assertEquals("Value should match", "jonh", fuzzyQueryBuilder.value()); + assertEquals("Fuzziness should match", Fuzziness.AUTO, fuzzyQueryBuilder.fuzziness()); + assertEquals("Prefix length should match", 2, fuzzyQueryBuilder.prefixLength()); + assertEquals("Max expansions should match", 100, fuzzyQueryBuilder.maxExpansions()); + assertEquals("Transpositions should match", false, fuzzyQueryBuilder.transpositions()); + assertEquals("Rewrite should match", "constant_score", fuzzyQueryBuilder.rewrite()); + assertEquals("Boost should match", 1.5f, fuzzyQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_query", fuzzyQueryBuilder.queryName()); + } + + public void testFromProtoWithFuzzinessInt() { + FieldValue value = FieldValue.newBuilder().setString("jonh").build(); + org.opensearch.protobufs.Fuzziness fuzziness = org.opensearch.protobufs.Fuzziness.newBuilder().setInt32(2).build(); + + org.opensearch.protobufs.FuzzyQuery fuzzyQuery = org.opensearch.protobufs.FuzzyQuery.newBuilder() + .setField("name") + .setValue(value) + .setFuzziness(fuzziness) + .build(); + + FuzzyQueryBuilder fuzzyQueryBuilder = FuzzyQueryBuilderProtoUtils.fromProto(fuzzyQuery); + + assertNotNull("FuzzyQueryBuilder should not be null", fuzzyQueryBuilder); + assertEquals("Fuzziness should match", Fuzziness.fromEdits(2), fuzzyQueryBuilder.fuzziness()); + } + + public void testFromProtoWithTranspositionsTrue() { + FieldValue value = FieldValue.newBuilder().setString("jonh").build(); + org.opensearch.protobufs.FuzzyQuery fuzzyQuery = org.opensearch.protobufs.FuzzyQuery.newBuilder() + .setField("name") + .setValue(value) + .setTranspositions(true) + .build(); + + FuzzyQueryBuilder fuzzyQueryBuilder = FuzzyQueryBuilderProtoUtils.fromProto(fuzzyQuery); + + assertNotNull("FuzzyQueryBuilder should not be null", fuzzyQueryBuilder); + assertEquals("Transpositions should match", true, fuzzyQueryBuilder.transpositions()); + } + + public void testFromProtoWithMinimalFields() { + FieldValue value = FieldValue.newBuilder().setString("text").build(); + org.opensearch.protobufs.FuzzyQuery fuzzyQuery = org.opensearch.protobufs.FuzzyQuery.newBuilder() + .setField("description") + .setValue(value) + .build(); + + FuzzyQueryBuilder fuzzyQueryBuilder = FuzzyQueryBuilderProtoUtils.fromProto(fuzzyQuery); + + assertNotNull("FuzzyQueryBuilder should not be null", fuzzyQueryBuilder); + assertEquals("Field name should match", "description", fuzzyQueryBuilder.fieldName()); + assertEquals("Value should match", "text", fuzzyQueryBuilder.value()); + assertNull("Query name should be null", fuzzyQueryBuilder.queryName()); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..415e7e34e1975 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoConverterTests.java @@ -0,0 +1,91 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.MatchBoolPrefixQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.MatchBoolPrefixQuery; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchBoolPrefixQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private MatchBoolPrefixQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new MatchBoolPrefixQueryBuilderProtoConverter(); + } + + public void testGetHandledQueryCase() { + assertEquals( + "Converter should handle MATCH_BOOL_PREFIX case", + QueryContainer.QueryContainerCase.MATCH_BOOL_PREFIX, + converter.getHandledQueryCase() + ); + } + + public void testFromProto() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("quick brown f") + .setBoost(2.0f) + .setXName("test_query") + .build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatchBoolPrefix(matchBoolPrefixQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchBoolPrefixQueryBuilder", queryBuilder instanceof MatchBoolPrefixQueryBuilder); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = (MatchBoolPrefixQueryBuilder) queryBuilder; + assertEquals("Field name should match", "message", matchBoolPrefixQueryBuilder.fieldName()); + assertEquals("Value should match", "quick brown f", matchBoolPrefixQueryBuilder.value()); + assertEquals("Boost should match", 2.0f, matchBoolPrefixQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", matchBoolPrefixQueryBuilder.queryName()); + } + + public void testFromProtoWithMinimalFields() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder().setField("title").setQuery("test").build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatchBoolPrefix(matchBoolPrefixQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchBoolPrefixQueryBuilder", queryBuilder instanceof MatchBoolPrefixQueryBuilder); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = (MatchBoolPrefixQueryBuilder) queryBuilder; + assertEquals("Field name should match", "title", matchBoolPrefixQueryBuilder.fieldName()); + assertEquals("Value should match", "test", matchBoolPrefixQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, matchBoolPrefixQueryBuilder.boost(), 0.0f); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should mention 'does not contain a MatchBoolPrefix query'", + exception.getMessage().contains("does not contain a MatchBoolPrefix query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should mention 'does not contain a MatchBoolPrefix query'", + exception.getMessage().contains("does not contain a MatchBoolPrefix query") + ); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..40f453449fda8 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchBoolPrefixQueryBuilderProtoUtilsTests.java @@ -0,0 +1,206 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.MatchBoolPrefixQueryBuilder; +import org.opensearch.index.query.Operator; +import org.opensearch.protobufs.MatchBoolPrefixQuery; +import org.opensearch.protobufs.MinimumShouldMatch; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchBoolPrefixQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + public void testFromProtoWithBasicMatchBoolPrefixQuery() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder().setField("message").setQuery("quick brown f").build(); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(matchBoolPrefixQuery); + + assertNotNull("MatchBoolPrefixQueryBuilder should not be null", matchBoolPrefixQueryBuilder); + assertEquals("Field name should match", "message", matchBoolPrefixQueryBuilder.fieldName()); + assertEquals("Query should match", "quick brown f", matchBoolPrefixQueryBuilder.value()); + assertEquals("Operator should be default OR", Operator.OR, matchBoolPrefixQueryBuilder.operator()); + } + + public void testFromProtoWithAllParameters() { + MinimumShouldMatch minimumShouldMatch = MinimumShouldMatch.newBuilder().setString("75%").build(); + org.opensearch.protobufs.Fuzziness fuzziness = org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build(); + + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("quick brown f") + .setAnalyzer("standard") + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_AND) + .setMinimumShouldMatch(minimumShouldMatch) + .setFuzziness(fuzziness) + .setPrefixLength(2) + .setMaxExpansions(100) + .setFuzzyTranspositions(false) + .setBoost(1.5f) + .setXName("test_query") + .build(); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(matchBoolPrefixQuery); + + assertNotNull("MatchBoolPrefixQueryBuilder should not be null", matchBoolPrefixQueryBuilder); + assertEquals("Field name should match", "message", matchBoolPrefixQueryBuilder.fieldName()); + assertEquals("Query should match", "quick brown f", matchBoolPrefixQueryBuilder.value()); + assertEquals("Analyzer should match", "standard", matchBoolPrefixQueryBuilder.analyzer()); + assertEquals("Operator should match", Operator.AND, matchBoolPrefixQueryBuilder.operator()); + assertEquals("Minimum should match", "75%", matchBoolPrefixQueryBuilder.minimumShouldMatch()); + assertEquals("Fuzziness should match", Fuzziness.AUTO, matchBoolPrefixQueryBuilder.fuzziness()); + assertEquals("Prefix length should match", 2, matchBoolPrefixQueryBuilder.prefixLength()); + assertEquals("Max expansions should match", 100, matchBoolPrefixQueryBuilder.maxExpansions()); + assertEquals("Fuzzy transpositions should match", false, matchBoolPrefixQueryBuilder.fuzzyTranspositions()); + assertEquals("Boost should match", 1.5f, matchBoolPrefixQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_query", matchBoolPrefixQueryBuilder.queryName()); + } + + public void testFromProtoWithOperatorOr() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_OR) + .build(); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(matchBoolPrefixQuery); + + assertNotNull("MatchBoolPrefixQueryBuilder should not be null", matchBoolPrefixQueryBuilder); + assertEquals("Operator should match OR", Operator.OR, matchBoolPrefixQueryBuilder.operator()); + } + + public void testFromProtoWithOperatorUnspecified() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_UNSPECIFIED) + .build(); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(matchBoolPrefixQuery); + + assertNotNull("MatchBoolPrefixQueryBuilder should not be null", matchBoolPrefixQueryBuilder); + assertEquals("Operator should be default OR", Operator.OR, matchBoolPrefixQueryBuilder.operator()); + } + + public void testFromProtoWithMinimumShouldMatch() { + // Test 1: MinimumShouldMatch with string value + MinimumShouldMatch minimumShouldMatchString = MinimumShouldMatch.newBuilder().setString("75%").build(); + MatchBoolPrefixQuery queryString = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setMinimumShouldMatch(minimumShouldMatchString) + .build(); + MatchBoolPrefixQueryBuilder builderString = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryString); + assertNotNull("Builder should not be null (STRING)", builderString); + assertEquals("Minimum should match (STRING)", "75%", builderString.minimumShouldMatch()); + + // Test 2: MinimumShouldMatch with int32 value + MinimumShouldMatch minimumShouldMatchInt = MinimumShouldMatch.newBuilder().setInt32(2).build(); + MatchBoolPrefixQuery queryInt = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setMinimumShouldMatch(minimumShouldMatchInt) + .build(); + MatchBoolPrefixQueryBuilder builderInt = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryInt); + assertNotNull("Builder should not be null (INT32)", builderInt); + assertEquals("Minimum should match (INT32)", "2", builderInt.minimumShouldMatch()); + + // Test 3: MinimumShouldMatch with empty/neither string nor int32 + MinimumShouldMatch minimumShouldMatchEmpty = MinimumShouldMatch.newBuilder().build(); + MatchBoolPrefixQuery queryEmpty = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setMinimumShouldMatch(minimumShouldMatchEmpty) + .build(); + MatchBoolPrefixQueryBuilder builderEmpty = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryEmpty); + assertNotNull("Builder should not be null (EMPTY)", builderEmpty); + assertNull("Minimum should match should be null (EMPTY)", builderEmpty.minimumShouldMatch()); + } + + public void testFromProtoWithFuzziness() { + // Test 1: Fuzziness with string value + org.opensearch.protobufs.Fuzziness fuzzinessString = org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build(); + MatchBoolPrefixQuery queryString = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setFuzziness(fuzzinessString) + .build(); + MatchBoolPrefixQueryBuilder builderString = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryString); + assertNotNull("Builder should not be null (STRING)", builderString); + assertEquals("Fuzziness should match (STRING)", Fuzziness.AUTO, builderString.fuzziness()); + + // Test 2: Fuzziness with int32 value + org.opensearch.protobufs.Fuzziness fuzzinessInt = org.opensearch.protobufs.Fuzziness.newBuilder().setInt32(2).build(); + MatchBoolPrefixQuery queryInt = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setFuzziness(fuzzinessInt) + .build(); + MatchBoolPrefixQueryBuilder builderInt = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryInt); + assertNotNull("Builder should not be null (INT32)", builderInt); + assertEquals("Fuzziness should match (INT32)", Fuzziness.fromEdits(2), builderInt.fuzziness()); + + // Test 3: Fuzziness with empty/neither string nor int32 + org.opensearch.protobufs.Fuzziness fuzzinessEmpty = org.opensearch.protobufs.Fuzziness.newBuilder().build(); + MatchBoolPrefixQuery queryEmpty = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setFuzziness(fuzzinessEmpty) + .build(); + MatchBoolPrefixQueryBuilder builderEmpty = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryEmpty); + assertNotNull("Builder should not be null (EMPTY)", builderEmpty); + assertNull("Fuzziness should be null (EMPTY)", builderEmpty.fuzziness()); + } + + public void testFromProtoWithDefaults() { + MatchBoolPrefixQuery matchBoolPrefixQuery = MatchBoolPrefixQuery.newBuilder().setField("message").setQuery("test").build(); + + MatchBoolPrefixQueryBuilder matchBoolPrefixQueryBuilder = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(matchBoolPrefixQuery); + + assertNotNull("MatchBoolPrefixQueryBuilder should not be null", matchBoolPrefixQueryBuilder); + assertEquals("Prefix length should be default", FuzzyQuery.defaultPrefixLength, matchBoolPrefixQueryBuilder.prefixLength()); + assertEquals("Max expansions should be default", FuzzyQuery.defaultMaxExpansions, matchBoolPrefixQueryBuilder.maxExpansions()); + assertEquals( + "Fuzzy transpositions should be default", + FuzzyQuery.defaultTranspositions, + matchBoolPrefixQueryBuilder.fuzzyTranspositions() + ); + } + + public void testFromProtoWithFuzzyRewrite() { + // Test 1: Fuzzy rewrite not set (null) + MatchBoolPrefixQuery queryNoRewrite = MatchBoolPrefixQuery.newBuilder().setField("message").setQuery("test").build(); + MatchBoolPrefixQueryBuilder builderNoRewrite = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryNoRewrite); + assertNotNull("Builder should not be null (no fuzzyRewrite)", builderNoRewrite); + assertNull("Fuzzy rewrite should be null (not set)", builderNoRewrite.fuzzyRewrite()); + + // Test 2: Fuzzy rewrite with CONSTANT_SCORE value + MatchBoolPrefixQuery queryWithRewrite = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setFuzzyRewrite(MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .build(); + MatchBoolPrefixQueryBuilder builderWithRewrite = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryWithRewrite); + assertNotNull("Builder should not be null (CONSTANT_SCORE)", builderWithRewrite); + assertNotNull("Fuzzy rewrite should not be null (CONSTANT_SCORE)", builderWithRewrite.fuzzyRewrite()); + assertEquals("Fuzzy rewrite should match (CONSTANT_SCORE)", "constant_score", builderWithRewrite.fuzzyRewrite()); + + // Test 3: Fuzzy rewrite with UNSPECIFIED value + MatchBoolPrefixQuery queryUnspecified = MatchBoolPrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setFuzzyRewrite(MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) + .build(); + MatchBoolPrefixQueryBuilder builderUnspecified = MatchBoolPrefixQueryBuilderProtoUtils.fromProto(queryUnspecified); + assertNotNull("Builder should not be null (UNSPECIFIED)", builderUnspecified); + assertNull("Fuzzy rewrite should be null (UNSPECIFIED)", builderUnspecified.fuzzyRewrite()); + } + +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..4eb309b96e57f --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoConverterTests.java @@ -0,0 +1,95 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.MatchPhrasePrefixQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.MatchPhrasePrefixQuery; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchPhrasePrefixQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private MatchPhrasePrefixQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new MatchPhrasePrefixQueryBuilderProtoConverter(); + } + + public void testGetHandledQueryCase() { + assertEquals( + "Converter should handle MATCH_PHRASE_PREFIX case", + QueryContainer.QueryContainerCase.MATCH_PHRASE_PREFIX, + converter.getHandledQueryCase() + ); + } + + public void testFromProto() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder() + .setField("message") + .setQuery("quick bro") + .setSlop(2) + .setMaxExpansions(10) + .setBoost(2.0f) + .setXName("test_query") + .build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatchPhrasePrefix(matchPhrasePrefixQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchPhrasePrefixQueryBuilder", queryBuilder instanceof MatchPhrasePrefixQueryBuilder); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = (MatchPhrasePrefixQueryBuilder) queryBuilder; + assertEquals("Field name should match", "message", matchPhrasePrefixQueryBuilder.fieldName()); + assertEquals("Value should match", "quick bro", matchPhrasePrefixQueryBuilder.value()); + assertEquals("Slop should match", 2, matchPhrasePrefixQueryBuilder.slop()); + assertEquals("Max expansions should match", 10, matchPhrasePrefixQueryBuilder.maxExpansions()); + assertEquals("Boost should match", 2.0f, matchPhrasePrefixQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", matchPhrasePrefixQueryBuilder.queryName()); + } + + public void testFromProtoWithMinimalFields() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder().setField("title").setQuery("test").build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatchPhrasePrefix(matchPhrasePrefixQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchPhrasePrefixQueryBuilder", queryBuilder instanceof MatchPhrasePrefixQueryBuilder); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = (MatchPhrasePrefixQueryBuilder) queryBuilder; + assertEquals("Field name should match", "title", matchPhrasePrefixQueryBuilder.fieldName()); + assertEquals("Value should match", "test", matchPhrasePrefixQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, matchPhrasePrefixQueryBuilder.boost(), 0.0f); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should mention 'does not contain a MatchPhrasePrefix query'", + exception.getMessage().contains("does not contain a MatchPhrasePrefix query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should mention 'does not contain a MatchPhrasePrefix query'", + exception.getMessage().contains("does not contain a MatchPhrasePrefix query") + ); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..4248df4ae03c4 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchPhrasePrefixQueryBuilderProtoUtilsTests.java @@ -0,0 +1,113 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.apache.lucene.search.FuzzyQuery; +import org.opensearch.index.query.MatchPhrasePrefixQueryBuilder; +import org.opensearch.index.search.MatchQuery; +import org.opensearch.protobufs.MatchPhrasePrefixQuery; +import org.opensearch.protobufs.ZeroTermsQuery; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchPhrasePrefixQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + public void testFromProtoWithBasicMatchPhrasePrefixQuery() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder() + .setField("message") + .setQuery("quick bro") + .build(); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = MatchPhrasePrefixQueryBuilderProtoUtils.fromProto( + matchPhrasePrefixQuery + ); + + assertNotNull("MatchPhrasePrefixQueryBuilder should not be null", matchPhrasePrefixQueryBuilder); + assertEquals("Field name should match", "message", matchPhrasePrefixQueryBuilder.fieldName()); + assertEquals("Query should match", "quick bro", matchPhrasePrefixQueryBuilder.value()); + assertEquals("Slop should be default", MatchQuery.DEFAULT_PHRASE_SLOP, matchPhrasePrefixQueryBuilder.slop()); + assertEquals("Max expansions should be default", FuzzyQuery.defaultMaxExpansions, matchPhrasePrefixQueryBuilder.maxExpansions()); + assertEquals( + "Zero terms query should be default", + MatchQuery.DEFAULT_ZERO_TERMS_QUERY, + matchPhrasePrefixQueryBuilder.zeroTermsQuery() + ); + } + + public void testFromProtoWithAllParameters() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder() + .setField("message") + .setQuery("quick bro") + .setAnalyzer("standard") + .setSlop(2) + .setMaxExpansions(10) + .setZeroTermsQuery(ZeroTermsQuery.ZERO_TERMS_QUERY_ALL) + .setBoost(1.5f) + .setXName("test_query") + .build(); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = MatchPhrasePrefixQueryBuilderProtoUtils.fromProto( + matchPhrasePrefixQuery + ); + + assertNotNull("MatchPhrasePrefixQueryBuilder should not be null", matchPhrasePrefixQueryBuilder); + assertEquals("Field name should match", "message", matchPhrasePrefixQueryBuilder.fieldName()); + assertEquals("Query should match", "quick bro", matchPhrasePrefixQueryBuilder.value()); + assertEquals("Analyzer should match", "standard", matchPhrasePrefixQueryBuilder.analyzer()); + assertEquals("Slop should match", 2, matchPhrasePrefixQueryBuilder.slop()); + assertEquals("Max expansions should match", 10, matchPhrasePrefixQueryBuilder.maxExpansions()); + assertEquals("Zero terms query should match", MatchQuery.ZeroTermsQuery.ALL, matchPhrasePrefixQueryBuilder.zeroTermsQuery()); + assertEquals("Boost should match", 1.5f, matchPhrasePrefixQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_query", matchPhrasePrefixQueryBuilder.queryName()); + } + + public void testFromProtoWithZeroTermsQueryNone() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setZeroTermsQuery(ZeroTermsQuery.ZERO_TERMS_QUERY_NONE) + .build(); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = MatchPhrasePrefixQueryBuilderProtoUtils.fromProto( + matchPhrasePrefixQuery + ); + + assertNotNull("MatchPhrasePrefixQueryBuilder should not be null", matchPhrasePrefixQueryBuilder); + assertEquals("Zero terms query should match", MatchQuery.ZeroTermsQuery.NONE, matchPhrasePrefixQueryBuilder.zeroTermsQuery()); + } + + public void testFromProtoWithZeroTermsQueryUnspecified() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder() + .setField("message") + .setQuery("test") + .setZeroTermsQuery(ZeroTermsQuery.ZERO_TERMS_QUERY_UNSPECIFIED) + .build(); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = MatchPhrasePrefixQueryBuilderProtoUtils.fromProto( + matchPhrasePrefixQuery + ); + + assertNotNull("MatchPhrasePrefixQueryBuilder should not be null", matchPhrasePrefixQueryBuilder); + assertEquals( + "Zero terms query should be default when UNSPECIFIED", + MatchQuery.DEFAULT_ZERO_TERMS_QUERY, + matchPhrasePrefixQueryBuilder.zeroTermsQuery() + ); + } + + public void testFromProtoWithIntegerQueryValue() { + MatchPhrasePrefixQuery matchPhrasePrefixQuery = MatchPhrasePrefixQuery.newBuilder().setField("age").setQuery("42").build(); + + MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = MatchPhrasePrefixQueryBuilderProtoUtils.fromProto( + matchPhrasePrefixQuery + ); + + assertNotNull("MatchPhrasePrefixQueryBuilder should not be null", matchPhrasePrefixQueryBuilder); + assertEquals("Field name should match", "age", matchPhrasePrefixQueryBuilder.fieldName()); + assertEquals("Query value should match", "42", matchPhrasePrefixQueryBuilder.value()); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..809ca3a065c1c --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoConverterTests.java @@ -0,0 +1,90 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.MatchQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.MatchQuery; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private MatchQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new MatchQueryBuilderProtoConverter(); + } + + public void testGetHandledQueryCase() { + assertEquals("Converter should handle MATCH case", QueryContainer.QueryContainerCase.MATCH, converter.getHandledQueryCase()); + } + + public void testFromProto() { + FieldValue queryValue = FieldValue.newBuilder().setString("search text").build(); + MatchQuery matchQuery = MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setBoost(2.0f) + .setXName("test_query") + .build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatch(matchQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchQueryBuilder", queryBuilder instanceof MatchQueryBuilder); + + MatchQueryBuilder matchQueryBuilder = (MatchQueryBuilder) queryBuilder; + assertEquals("Field name should match", "message", matchQueryBuilder.fieldName()); + assertEquals("Value should match", "search text", matchQueryBuilder.value()); + assertEquals("Boost should match", 2.0f, matchQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", matchQueryBuilder.queryName()); + } + + public void testFromProtoWithMinimalFields() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + MatchQuery matchQuery = MatchQuery.newBuilder().setField("title").setQuery(queryValue).build(); + + QueryContainer queryContainer = QueryContainer.newBuilder().setMatch(matchQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a MatchQueryBuilder", queryBuilder instanceof MatchQueryBuilder); + + MatchQueryBuilder matchQueryBuilder = (MatchQueryBuilder) queryBuilder; + assertEquals("Field name should match", "title", matchQueryBuilder.fieldName()); + assertEquals("Value should match", "test", matchQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, matchQueryBuilder.boost(), 0.0f); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should mention 'does not contain a Match query'", + exception.getMessage().contains("does not contain a Match query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should mention 'does not contain a Match query'", + exception.getMessage().contains("does not contain a Match query") + ); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..936c8196f9f24 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MatchQueryBuilderProtoUtilsTests.java @@ -0,0 +1,247 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.query.MatchQueryBuilder; +import org.opensearch.index.query.Operator; +import org.opensearch.index.search.MatchQuery; +import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.ZeroTermsQuery; +import org.opensearch.test.OpenSearchTestCase; + +public class MatchQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + public void testFromProtoWithBasicMatchQuery() { + FieldValue queryValue = FieldValue.newBuilder().setString("search text").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Field name should match", "message", matchQueryBuilder.fieldName()); + assertEquals("Query should match", "search text", matchQueryBuilder.value()); + assertEquals("Operator should be default OR", Operator.OR, matchQueryBuilder.operator()); + } + + public void testFromProtoWithAllParameters() { + FieldValue queryValue = FieldValue.newBuilder().setString("search text").build(); + org.opensearch.protobufs.Fuzziness fuzziness = org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build(); + + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setAnalyzer("standard") + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_AND) + .setFuzziness(fuzziness) + .setPrefixLength(2) + .setMaxExpansions(50) + .setFuzzyTranspositions(false) + .setLenient(true) + .setZeroTermsQuery(ZeroTermsQuery.ZERO_TERMS_QUERY_ALL) + .setAutoGenerateSynonymsPhraseQuery(false) + .setBoost(1.5f) + .setXName("test_query") + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Field name should match", "message", matchQueryBuilder.fieldName()); + assertEquals("Query should match", "search text", matchQueryBuilder.value()); + assertEquals("Analyzer should match", "standard", matchQueryBuilder.analyzer()); + assertEquals("Operator should match", Operator.AND, matchQueryBuilder.operator()); + assertEquals("Fuzziness should match", Fuzziness.AUTO, matchQueryBuilder.fuzziness()); + assertEquals("Prefix length should match", 2, matchQueryBuilder.prefixLength()); + assertEquals("Max expansions should match", 50, matchQueryBuilder.maxExpansions()); + assertEquals("Fuzzy transpositions should match", false, matchQueryBuilder.fuzzyTranspositions()); + assertEquals("Lenient should match", true, matchQueryBuilder.lenient()); + assertEquals("Zero terms query should match", MatchQuery.ZeroTermsQuery.ALL, matchQueryBuilder.zeroTermsQuery()); + assertEquals("Auto generate synonyms should match", false, matchQueryBuilder.autoGenerateSynonymsPhraseQuery()); + assertEquals("Boost should match", 1.5f, matchQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_query", matchQueryBuilder.queryName()); + } + + public void testFromProtoWithOperatorOr() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_OR) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Operator should match OR", Operator.OR, matchQueryBuilder.operator()); + } + + public void testFromProtoWithOperatorUnspecified() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setOperator(org.opensearch.protobufs.Operator.OPERATOR_UNSPECIFIED) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Operator should be default OR", Operator.OR, matchQueryBuilder.operator()); + } + + public void testFromProtoWithFuzziness() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + + // Test 1: Fuzziness with string value + org.opensearch.protobufs.Fuzziness fuzzinessString = org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build(); + org.opensearch.protobufs.MatchQuery queryString = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setFuzziness(fuzzinessString) + .build(); + MatchQueryBuilder builderString = MatchQueryBuilderProtoUtils.fromProto(queryString); + assertNotNull("Builder should not be null (STRING)", builderString); + assertEquals("Fuzziness should match (STRING)", Fuzziness.AUTO, builderString.fuzziness()); + + // Test 2: Fuzziness with int32 value + org.opensearch.protobufs.Fuzziness fuzzinessInt = org.opensearch.protobufs.Fuzziness.newBuilder().setInt32(2).build(); + org.opensearch.protobufs.MatchQuery queryInt = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setFuzziness(fuzzinessInt) + .build(); + MatchQueryBuilder builderInt = MatchQueryBuilderProtoUtils.fromProto(queryInt); + assertNotNull("Builder should not be null (INT32)", builderInt); + assertEquals("Fuzziness should match (INT32)", Fuzziness.fromEdits(2), builderInt.fuzziness()); + + // Test 3: Fuzziness with empty/neither string nor int32 + org.opensearch.protobufs.Fuzziness fuzzinessEmpty = org.opensearch.protobufs.Fuzziness.newBuilder().build(); + org.opensearch.protobufs.MatchQuery queryEmpty = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setFuzziness(fuzzinessEmpty) + .build(); + MatchQueryBuilder builderEmpty = MatchQueryBuilderProtoUtils.fromProto(queryEmpty); + assertNotNull("Builder should not be null (EMPTY)", builderEmpty); + assertNull("Fuzziness should be null (EMPTY)", builderEmpty.fuzziness()); + } + + public void testFromProtoWithMinimumShouldMatch() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + + // Test 1: MinimumShouldMatch with string value + org.opensearch.protobufs.MinimumShouldMatch minimumShouldMatchString = org.opensearch.protobufs.MinimumShouldMatch.newBuilder() + .setString("75%") + .build(); + org.opensearch.protobufs.MatchQuery queryString = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setMinimumShouldMatch(minimumShouldMatchString) + .build(); + MatchQueryBuilder builderString = MatchQueryBuilderProtoUtils.fromProto(queryString); + assertNotNull("Builder should not be null (STRING)", builderString); + assertEquals("Minimum should match (STRING)", "75%", builderString.minimumShouldMatch()); + + // Test 2: MinimumShouldMatch with int32 value + org.opensearch.protobufs.MinimumShouldMatch minimumShouldMatchInt = org.opensearch.protobufs.MinimumShouldMatch.newBuilder() + .setInt32(2) + .build(); + org.opensearch.protobufs.MatchQuery queryInt = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setMinimumShouldMatch(minimumShouldMatchInt) + .build(); + MatchQueryBuilder builderInt = MatchQueryBuilderProtoUtils.fromProto(queryInt); + assertNotNull("Builder should not be null (INT32)", builderInt); + assertEquals("Minimum should match (INT32)", "2", builderInt.minimumShouldMatch()); + + // Test 3: MinimumShouldMatch with empty/neither + org.opensearch.protobufs.MinimumShouldMatch minimumShouldMatchEmpty = org.opensearch.protobufs.MinimumShouldMatch.newBuilder() + .build(); + org.opensearch.protobufs.MatchQuery queryEmpty = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setMinimumShouldMatch(minimumShouldMatchEmpty) + .build(); + MatchQueryBuilder builderEmpty = MatchQueryBuilderProtoUtils.fromProto(queryEmpty); + assertNotNull("Builder should not be null (EMPTY)", builderEmpty); + assertNull("Minimum should match should be null (EMPTY)", builderEmpty.minimumShouldMatch()); + } + + public void testFromProtoWithFuzzyRewrite() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + + // Test 1: FuzzyRewrite with CONSTANT_SCORE value + org.opensearch.protobufs.MatchQuery queryWithRewrite = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setFuzzyRewrite(org.opensearch.protobufs.MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .build(); + MatchQueryBuilder builderWithRewrite = MatchQueryBuilderProtoUtils.fromProto(queryWithRewrite); + assertNotNull("Builder should not be null (CONSTANT_SCORE)", builderWithRewrite); + assertNotNull("Fuzzy rewrite should not be null (CONSTANT_SCORE)", builderWithRewrite.fuzzyRewrite()); + assertEquals("Fuzzy rewrite should match (CONSTANT_SCORE)", "constant_score", builderWithRewrite.fuzzyRewrite()); + + // Test 2: FuzzyRewrite with UNSPECIFIED value + org.opensearch.protobufs.MatchQuery queryUnspecified = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setFuzzyRewrite(org.opensearch.protobufs.MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_UNSPECIFIED) + .build(); + MatchQueryBuilder builderUnspecified = MatchQueryBuilderProtoUtils.fromProto(queryUnspecified); + assertNotNull("Builder should not be null (UNSPECIFIED)", builderUnspecified); + assertNull("Fuzzy rewrite should be null (UNSPECIFIED)", builderUnspecified.fuzzyRewrite()); + } + + public void testFromProtoWithZeroTermsQueryAll() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setZeroTermsQuery(ZeroTermsQuery.ZERO_TERMS_QUERY_ALL) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Zero terms query should match", MatchQuery.ZeroTermsQuery.ALL, matchQueryBuilder.zeroTermsQuery()); + } + + public void testFromProtoWithLenient() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setLenient(true) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Lenient should match", true, matchQueryBuilder.lenient()); + } + + public void testFromProtoWithAutoGenerateSynonymsPhraseQueryFalse() { + FieldValue queryValue = FieldValue.newBuilder().setString("test").build(); + org.opensearch.protobufs.MatchQuery matchQuery = org.opensearch.protobufs.MatchQuery.newBuilder() + .setField("message") + .setQuery(queryValue) + .setAutoGenerateSynonymsPhraseQuery(false) + .build(); + + MatchQueryBuilder matchQueryBuilder = MatchQueryBuilderProtoUtils.fromProto(matchQuery); + + assertNotNull("MatchQueryBuilder should not be null", matchQueryBuilder); + assertEquals("Auto generate synonyms should match", false, matchQueryBuilder.autoGenerateSynonymsPhraseQuery()); + } + +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtilsTests.java index 2ae51f3058f17..2e74e4a49f2ef 100644 --- a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtilsTests.java +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/MultiMatchQueryBuilderProtoUtilsTests.java @@ -8,6 +8,7 @@ package org.opensearch.transport.grpc.proto.request.search.query; +import org.opensearch.common.unit.Fuzziness; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.MultiMatchQueryBuilder; @@ -393,4 +394,37 @@ public void testFromProtoWithBoolPrefixAndDefaultSlop() { assertEquals(MultiMatchQueryBuilder.Type.BOOL_PREFIX, builder.type()); assertEquals(MultiMatchQueryBuilder.DEFAULT_PHRASE_SLOP, builder.slop()); } + + public void testFromProtoWithFuzziness() { + // Test 1: Fuzziness with string value + MultiMatchQuery protoString = MultiMatchQuery.newBuilder() + .setQuery("test query") + .addFields("field1") + .setFuzziness(org.opensearch.protobufs.Fuzziness.newBuilder().setString("AUTO").build()) + .build(); + MultiMatchQueryBuilder builderString = fromProto(protoString); + assertEquals("test query", builderString.value()); + assertNotNull("Fuzziness should not be null (STRING)", builderString.fuzziness()); + assertEquals(Fuzziness.build("AUTO"), builderString.fuzziness()); + + MultiMatchQuery protoInt = MultiMatchQuery.newBuilder() + .setQuery("test query") + .addFields("field1") + .setFuzziness(org.opensearch.protobufs.Fuzziness.newBuilder().setInt32(2).build()) + .build(); + MultiMatchQueryBuilder builderInt = fromProto(protoInt); + assertEquals("test query", builderInt.value()); + assertNotNull("Fuzziness should not be null (INT32)", builderInt.fuzziness()); + assertEquals(Fuzziness.fromEdits(2), builderInt.fuzziness()); + + // Test 3: Fuzziness with empty/neither string nor int32 + MultiMatchQuery protoEmpty = MultiMatchQuery.newBuilder() + .setQuery("test query") + .addFields("field1") + .setFuzziness(org.opensearch.protobufs.Fuzziness.newBuilder().build()) + .build(); + MultiMatchQueryBuilder builderEmpty = fromProto(protoEmpty); + assertEquals("test query", builderEmpty.value()); + assertNull("Fuzziness should be null (EMPTY)", builderEmpty.fuzziness()); + } } diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverterTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverterTests.java new file mode 100644 index 0000000000000..25fbebea61b31 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoConverterTests.java @@ -0,0 +1,71 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.PrefixQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.protobufs.PrefixQuery; +import org.opensearch.protobufs.QueryContainer; +import org.opensearch.test.OpenSearchTestCase; + +public class PrefixQueryBuilderProtoConverterTests extends OpenSearchTestCase { + + private PrefixQueryBuilderProtoConverter converter; + + @Override + public void setUp() throws Exception { + super.setUp(); + converter = new PrefixQueryBuilderProtoConverter(); + } + + public void testGetHandledQueryCase() { + assertEquals("Converter should handle PREFIX case", QueryContainer.QueryContainerCase.PREFIX, converter.getHandledQueryCase()); + } + + public void testFromProto() { + PrefixQuery prefixQuery = PrefixQuery.newBuilder() + .setField("test-field") + .setValue("test-value") + .setBoost(2.0f) + .setXName("test_query") + .setCaseInsensitive(true) + .setRewrite(org.opensearch.protobufs.MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .build(); + QueryContainer queryContainer = QueryContainer.newBuilder().setPrefix(prefixQuery).build(); + + QueryBuilder queryBuilder = converter.fromProto(queryContainer); + + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertTrue("QueryBuilder should be a PrefixQueryBuilder", queryBuilder instanceof PrefixQueryBuilder); + PrefixQueryBuilder prefixQueryBuilder = (PrefixQueryBuilder) queryBuilder; + assertEquals("Field name should match", "test-field", prefixQueryBuilder.fieldName()); + assertEquals("Value should match", "test-value", prefixQueryBuilder.value()); + assertEquals("Boost should match", 2.0f, prefixQueryBuilder.boost(), 0.0f); + assertEquals("Query name should match", "test_query", prefixQueryBuilder.queryName()); + } + + public void testFromProtoWithInvalidContainer() { + QueryContainer emptyContainer = QueryContainer.newBuilder().build(); + + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(emptyContainer)); + + assertTrue( + "Exception message should mention 'does not contain a Prefix query'", + exception.getMessage().contains("does not contain a Prefix query") + ); + } + + public void testFromProtoWithNullContainer() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null)); + + assertTrue( + "Exception message should mention 'does not contain a Prefix query'", + exception.getMessage().contains("does not contain a Prefix query") + ); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtilsTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtilsTests.java new file mode 100644 index 0000000000000..1f0cb6ab82f92 --- /dev/null +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/PrefixQueryBuilderProtoUtilsTests.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.opensearch.transport.grpc.proto.request.search.query; + +import org.opensearch.index.query.PrefixQueryBuilder; +import org.opensearch.protobufs.MultiTermQueryRewrite; +import org.opensearch.protobufs.PrefixQuery; +import org.opensearch.test.OpenSearchTestCase; + +public class PrefixQueryBuilderProtoUtilsTests extends OpenSearchTestCase { + + public void testFromProtoWithBasicPrefixQuery() { + PrefixQuery prefixQuery = PrefixQuery.newBuilder().setField("name").setValue("joh").build(); + + PrefixQueryBuilder prefixQueryBuilder = PrefixQueryBuilderProtoUtils.fromProto(prefixQuery); + + assertNotNull("PrefixQueryBuilder should not be null", prefixQueryBuilder); + assertEquals("Field name should match", "name", prefixQueryBuilder.fieldName()); + assertEquals("Value should match", "joh", prefixQueryBuilder.value()); + assertEquals("Default boost should be 1.0", 1.0f, prefixQueryBuilder.boost(), 0.0f); + assertEquals( + "Default case insensitive should match", + PrefixQueryBuilder.DEFAULT_CASE_INSENSITIVITY, + prefixQueryBuilder.caseInsensitive() + ); + } + + public void testFromProtoWithAllParameters() { + PrefixQuery prefixQuery = PrefixQuery.newBuilder() + .setField("name") + .setValue("joh") + .setRewrite(MultiTermQueryRewrite.MULTI_TERM_QUERY_REWRITE_CONSTANT_SCORE) + .setCaseInsensitive(true) + .setBoost(2.0f) + .setXName("test_query") + .build(); + + PrefixQueryBuilder prefixQueryBuilder = PrefixQueryBuilderProtoUtils.fromProto(prefixQuery); + + assertNotNull("PrefixQueryBuilder should not be null", prefixQueryBuilder); + assertEquals("Field name should match", "name", prefixQueryBuilder.fieldName()); + assertEquals("Value should match", "joh", prefixQueryBuilder.value()); + assertEquals("Rewrite should match", "constant_score", prefixQueryBuilder.rewrite()); + assertEquals("Case insensitive should match", true, prefixQueryBuilder.caseInsensitive()); + assertEquals("Boost should match", 2.0f, prefixQueryBuilder.boost(), 0.001f); + assertEquals("Query name should match", "test_query", prefixQueryBuilder.queryName()); + } + + public void testFromProtoWithCaseInsensitiveFalse() { + PrefixQuery prefixQuery = PrefixQuery.newBuilder().setField("name").setValue("joh").setCaseInsensitive(false).build(); + + PrefixQueryBuilder prefixQueryBuilder = PrefixQueryBuilderProtoUtils.fromProto(prefixQuery); + + assertNotNull("PrefixQueryBuilder should not be null", prefixQueryBuilder); + assertEquals("Case insensitive should match", false, prefixQueryBuilder.caseInsensitive()); + } + + public void testFromProtoWithMinimalFields() { + PrefixQuery prefixQuery = PrefixQuery.newBuilder().setField("title").setValue("ope").build(); + + PrefixQueryBuilder prefixQueryBuilder = PrefixQueryBuilderProtoUtils.fromProto(prefixQuery); + + assertNotNull("PrefixQueryBuilder should not be null", prefixQueryBuilder); + assertEquals("Field name should match", "title", prefixQueryBuilder.fieldName()); + assertEquals("Value should match", "ope", prefixQueryBuilder.value()); + assertNull("Query name should be null", prefixQueryBuilder.queryName()); + } +} diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryTests.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryTests.java index d717d6e226cb6..14eee9221a0a2 100644 --- a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryTests.java +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/proto/request/search/query/QueryBuilderProtoConverterRegistryTests.java @@ -10,10 +10,14 @@ import org.opensearch.index.query.QueryBuilder; import org.opensearch.protobufs.BoolQuery; import org.opensearch.protobufs.ChildScoreMode; +import org.opensearch.protobufs.ConstantScoreQuery; import org.opensearch.protobufs.CoordsGeoBounds; import org.opensearch.protobufs.DoubleArray; import org.opensearch.protobufs.ExistsQuery; import org.opensearch.protobufs.FieldValue; +import org.opensearch.protobufs.FieldValueArray; +import org.opensearch.protobufs.Fuzziness; +import org.opensearch.protobufs.FuzzyQuery; import org.opensearch.protobufs.GeoBoundingBoxQuery; import org.opensearch.protobufs.GeoBounds; import org.opensearch.protobufs.GeoDistanceQuery; @@ -22,19 +26,25 @@ import org.opensearch.protobufs.InlineScript; import org.opensearch.protobufs.LatLonGeoLocation; import org.opensearch.protobufs.MatchAllQuery; +import org.opensearch.protobufs.MatchBoolPrefixQuery; +import org.opensearch.protobufs.MatchPhrasePrefixQuery; import org.opensearch.protobufs.MatchPhraseQuery; +import org.opensearch.protobufs.MatchQuery; import org.opensearch.protobufs.MinimumShouldMatch; import org.opensearch.protobufs.MultiMatchQuery; import org.opensearch.protobufs.NestedQuery; import org.opensearch.protobufs.NumberRangeQuery; import org.opensearch.protobufs.NumberRangeQueryAllOfFrom; import org.opensearch.protobufs.NumberRangeQueryAllOfTo; +import org.opensearch.protobufs.Operator; +import org.opensearch.protobufs.PrefixQuery; import org.opensearch.protobufs.QueryContainer; import org.opensearch.protobufs.RangeQuery; import org.opensearch.protobufs.RegexpQuery; import org.opensearch.protobufs.Script; import org.opensearch.protobufs.ScriptQuery; import org.opensearch.protobufs.TermQuery; +import org.opensearch.protobufs.TermsQueryField; import org.opensearch.protobufs.TermsSetQuery; import org.opensearch.protobufs.TextQueryType; import org.opensearch.protobufs.WildcardQuery; @@ -571,4 +581,165 @@ public org.opensearch.index.query.QueryBuilder fromProto(QueryContainer queryCon assertNotNull("QueryBuilder should not be null after register and update operations", queryBuilder); assertEquals("Should be an ExistsQueryBuilder", "org.opensearch.index.query.ExistsQueryBuilder", queryBuilder.getClass().getName()); } + + public void testMatchNoneQueryConversion() { + QueryContainer queryContainer = QueryContainer.newBuilder() + .setMatchNone(org.opensearch.protobufs.MatchNoneQuery.newBuilder().build()) + .build(); + + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals( + "Should be a MatchNoneQueryBuilder", + "org.opensearch.index.query.MatchNoneQueryBuilder", + queryBuilder.getClass().getName() + ); + } + + public void testTermsQueryConversion() { + // Create a Terms query container + FieldValue fv1 = FieldValue.newBuilder().setString("electronics").build(); + FieldValue fv2 = FieldValue.newBuilder().setString("books").build(); + FieldValueArray fva = FieldValueArray.newBuilder().addFieldValueArray(fv1).addFieldValueArray(fv2).build(); + TermsQueryField termsQueryField = TermsQueryField.newBuilder().setFieldValueArray(fva).build(); + + QueryContainer queryContainer = QueryContainer.newBuilder() + .setTerms(org.opensearch.protobufs.TermsQuery.newBuilder().putTerms("category", termsQueryField).setBoost(1.5f).build()) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals("Should be a TermsQueryBuilder", "org.opensearch.index.query.TermsQueryBuilder", queryBuilder.getClass().getName()); + } + + public void testConstantScoreQueryConversion() { + // Create a ConstantScore query container with a term query inside + TermQuery termQuery = TermQuery.newBuilder() + .setField("status") + .setValue(FieldValue.newBuilder().setString("active").build()) + .build(); + + QueryContainer innerQueryContainer = QueryContainer.newBuilder().setTerm(termQuery).build(); + + QueryContainer queryContainer = QueryContainer.newBuilder() + .setConstantScore(ConstantScoreQuery.newBuilder().setFilter(innerQueryContainer).setBoost(2.0f).build()) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals( + "Should be a ConstantScoreQueryBuilder", + "org.opensearch.index.query.ConstantScoreQueryBuilder", + queryBuilder.getClass().getName() + ); + } + + public void testFuzzyQueryConversion() { + // Create a Fuzzy query container + QueryContainer queryContainer = QueryContainer.newBuilder() + .setFuzzy( + FuzzyQuery.newBuilder() + .setField("title") + .setValue(FieldValue.newBuilder().setString("opensearch").build()) + .setFuzziness(Fuzziness.newBuilder().setString("AUTO").build()) + .setBoost(1.2f) + .build() + ) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals("Should be a FuzzyQueryBuilder", "org.opensearch.index.query.FuzzyQueryBuilder", queryBuilder.getClass().getName()); + } + + public void testPrefixQueryConversion() { + // Create a Prefix query container + QueryContainer queryContainer = QueryContainer.newBuilder() + .setPrefix(PrefixQuery.newBuilder().setField("name").setValue("john").setBoost(1.1f).build()) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals("Should be a PrefixQueryBuilder", "org.opensearch.index.query.PrefixQueryBuilder", queryBuilder.getClass().getName()); + } + + public void testMatchQueryConversion() { + // Create a Match query container + QueryContainer queryContainer = QueryContainer.newBuilder() + .setMatch( + MatchQuery.newBuilder() + .setField("message") + .setQuery(FieldValue.newBuilder().setString("hello world").build()) + .setOperator(Operator.OPERATOR_AND) + .setBoost(1.5f) + .build() + ) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals("Should be a MatchQueryBuilder", "org.opensearch.index.query.MatchQueryBuilder", queryBuilder.getClass().getName()); + } + + public void testMatchBoolPrefixQueryConversion() { + // Create a MatchBoolPrefix query container + QueryContainer queryContainer = QueryContainer.newBuilder() + .setMatchBoolPrefix( + MatchBoolPrefixQuery.newBuilder() + .setField("title") + .setQuery("opensearch tutorial") + .setOperator(Operator.OPERATOR_OR) + .setBoost(1.3f) + .build() + ) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals( + "Should be a MatchBoolPrefixQueryBuilder", + "org.opensearch.index.query.MatchBoolPrefixQueryBuilder", + queryBuilder.getClass().getName() + ); + } + + public void testMatchPhrasePrefixQueryConversion() { + // Create a MatchPhrasePrefix query container + QueryContainer queryContainer = QueryContainer.newBuilder() + .setMatchPhrasePrefix( + MatchPhrasePrefixQuery.newBuilder().setField("title").setQuery("opensearch tuto").setSlop(2).setBoost(1.4f).build() + ) + .build(); + + // Convert using the registry + QueryBuilder queryBuilder = registry.fromProto(queryContainer); + + // Verify the result + assertNotNull("QueryBuilder should not be null", queryBuilder); + assertEquals( + "Should be a MatchPhrasePrefixQueryBuilder", + "org.opensearch.index.query.MatchPhrasePrefixQueryBuilder", + queryBuilder.getClass().getName() + ); + } }