Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add update and delete support in pull-based ingestion ([#17822](https://github.com/opensearch-project/OpenSearch/pull/17822))
- Allow maxPollSize and pollTimeout in IngestionSource to be configurable ([#17863](https://github.com/opensearch-project/OpenSearch/pull/17863))
- [Star Tree] [Search] Add query changes to support unsigned-long in star tree ([#17275](https://github.com/opensearch-project/OpenSearch/pull/17275))
- Add TermsQuery support to Search GRPC endpoint ([#17888](https://github.com/opensearch-project/OpenSearch/pull/17888))

### Changed
- Migrate BC libs to their FIPS counterparts ([#14912](https://github.com/opensearch-project/OpenSearch/pull/14912))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public static QueryBuilder parseInnerQueryBuilderProto(QueryContainer queryConta
result = MatchNoneQueryBuilderProtoUtils.fromProto(queryContainer.getMatchNone());
} else if (queryContainer.getTermCount() > 0) {
result = TermQueryBuilderProtoUtils.fromProto(queryContainer.getTermMap());
} else if (queryContainer.hasTerms()) {
result = TermsQueryBuilderProtoUtils.fromProto(queryContainer.getTerms());
}
// TODO add more query types
else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.plugin.transport.grpc.proto.request.search.query;

import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.indices.TermsLookup;
import org.opensearch.protobufs.TermsLookupField;

/**
* Utility class for converting TermsLookup Protocol Buffers to OpenSearch objects.
* This class provides methods to transform Protocol Buffer representations of terms lookups
* into their corresponding OpenSearch TermsLookup implementations for search operations.
*/
public class TermsLookupProtoUtils {

private TermsLookupProtoUtils() {
// Utility class, no instances
}

/**
* Converts a Protocol Buffer TermsLookupField to an OpenSearch TermsLookup object.
* Similar to {@link TermsLookup#parseTermsLookup(XContentParser)}
*
* @param termsLookupFieldProto The Protocol Buffer TermsLookupField object containing index, id, path, and optional routing/store values
* @return A configured TermsLookup instance with the appropriate settings
*/
protected static TermsLookup parseTermsLookup(TermsLookupField termsLookupFieldProto) {

String index = termsLookupFieldProto.getIndex();
String id = termsLookupFieldProto.getId();
String path = termsLookupFieldProto.getPath();

TermsLookup termsLookup = new TermsLookup(index, id, path);

if (termsLookupFieldProto.hasRouting()) {
termsLookup.routing(termsLookupFieldProto.getRouting());
}

if (termsLookupFieldProto.hasStore()) {
termsLookup.store(termsLookupFieldProto.getStore());
}

return termsLookup;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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.plugin.transport.grpc.proto.request.search.query;

import com.google.protobuf.ProtocolStringList;
import org.apache.lucene.util.BytesRef;
import org.opensearch.core.common.bytes.BytesArray;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.query.AbstractQueryBuilder;
import org.opensearch.index.query.TermsQueryBuilder;
import org.opensearch.indices.TermsLookup;
import org.opensearch.protobufs.TermsLookupField;
import org.opensearch.protobufs.TermsLookupFieldStringArrayMap;
import org.opensearch.protobufs.TermsQueryField;
import org.opensearch.protobufs.ValueType;

import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

import static org.opensearch.index.query.AbstractQueryBuilder.maybeConvertToBytesRef;

/**
* Utility class for converting TermQuery Protocol Buffers to OpenSearch objects.
* This class provides methods to transform Protocol Buffer representations of term queries
* into their corresponding OpenSearch TermQueryBuilder implementations for search operations.
*/
public class TermsQueryBuilderProtoUtils {

private TermsQueryBuilderProtoUtils() {
// Utility class, no instances
}

/**
* Converts a Protocol Buffer TermQuery map to an OpenSearch TermQueryBuilder.
* Similar to {@link TermsQueryBuilder#fromXContent(XContentParser)}, this method
* parses the Protocol Buffer representation and creates a properly configured
* TermQueryBuilder with the appropriate field name, value, boost, query name,
* and case sensitivity settings.
*
* @param termsQueryProto The map of field names to Protocol Buffer TermsQuery objects
* @return A configured TermQueryBuilder instance
* @throws IllegalArgumentException if the term query map has more than one element,
* if the field value type is not supported, or if the term query field value is not recognized
*/
protected static TermsQueryBuilder fromProto(TermsQueryField termsQueryProto) {

String fieldName = null;
List<Object> values = null;
TermsLookup termsLookup = null;

String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String valueTypeStr = TermsQueryBuilder.ValueType.DEFAULT.name();

if (termsQueryProto.hasBoost()) {
boost = termsQueryProto.getBoost();
}

if (termsQueryProto.hasName()) {
queryName = termsQueryProto.getName();
}

// TODO: remove this parameter when backporting to under OS 2.17
if (termsQueryProto.hasValueType()) {
valueTypeStr = parseValueType(termsQueryProto.getValueType()).name();
}

if (termsQueryProto.getTermsLookupFieldStringArrayMapMap().size() > 1) {
throw new IllegalArgumentException("[" + TermsQueryBuilder.NAME + "] query does not support more than one field. ");
}

for (Map.Entry<String, TermsLookupFieldStringArrayMap> entry : termsQueryProto.getTermsLookupFieldStringArrayMapMap().entrySet()) {
fieldName = entry.getKey();
TermsLookupFieldStringArrayMap termsLookupFieldStringArrayMap = entry.getValue();

if (termsLookupFieldStringArrayMap.hasTermsLookupField()) {
TermsLookupField termsLookupField = termsLookupFieldStringArrayMap.getTermsLookupField();
termsLookup = TermsLookupProtoUtils.parseTermsLookup(termsLookupField);
} else if (termsLookupFieldStringArrayMap.hasStringArray()) {
values = parseValues(termsLookupFieldStringArrayMap.getStringArray().getStringArrayList());
} else {
throw new IllegalArgumentException("termsLookupField and stringArray fields cannot both be null");

Check warning on line 89 in plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java

View check run for this annotation

Codecov / codecov/patch

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java#L89

Added line #L89 was not covered by tests
}
}

TermsQueryBuilder.ValueType valueType = TermsQueryBuilder.ValueType.fromString(valueTypeStr);

if (valueType == TermsQueryBuilder.ValueType.BITMAP) {
if (values != null && values.size() == 1 && values.get(0) instanceof BytesRef) {
values.set(0, new BytesArray(Base64.getDecoder().decode(((BytesRef) values.get(0)).utf8ToString())));
} else if (termsLookup == null) {
throw new IllegalArgumentException(
"Invalid value for bitmap type: Expected a single-element array with a base64 encoded serialized bitmap."
);
}
}

TermsQueryBuilder termsQueryBuilder;
if (values == null) {
termsQueryBuilder = new TermsQueryBuilder(fieldName, termsLookup);
} else if (termsLookup == null) {
termsQueryBuilder = new TermsQueryBuilder(fieldName, values);
} else {
throw new IllegalArgumentException("values and termsLookup cannot both be null");

Check warning on line 111 in plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java

View check run for this annotation

Codecov / codecov/patch

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java#L111

Added line #L111 was not covered by tests
}

return termsQueryBuilder.boost(boost).queryName(queryName).valueType(valueType);
}

/**
* Parses a protobuf ScriptLanguage to a String representation
*
* See {@link org.opensearch.index.query.TermsQueryBuilder.ValueType#fromString(String)} }
* *
* @param valueType the Protocol Buffer ValueType to convert
* @return the string representation of the script language
* @throws UnsupportedOperationException if no language was specified
*/
public static TermsQueryBuilder.ValueType parseValueType(ValueType valueType) {
switch (valueType) {
case VALUE_TYPE_BITMAP:
return TermsQueryBuilder.ValueType.BITMAP;
case VALUE_TYPE_DEFAULT:
return TermsQueryBuilder.ValueType.DEFAULT;
case VALUE_TYPE_UNSPECIFIED:
default:
return TermsQueryBuilder.ValueType.DEFAULT;
}
}

/**
* Similar to {@link TermsQueryBuilder#parseValues(XContentParser)}
* @param termsLookupFieldStringArray
* @return
* @throws IllegalArgumentException
*/
static List<Object> parseValues(ProtocolStringList termsLookupFieldStringArray) throws IllegalArgumentException {
List<Object> values = new ArrayList<>();

for (Object value : termsLookupFieldStringArray) {
Object convertedValue = maybeConvertToBytesRef(value);
if (value == null) {
throw new IllegalArgumentException("No value specified for terms query");

Check warning on line 150 in plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java

View check run for this annotation

Codecov / codecov/patch

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/TermsQueryBuilderProtoUtils.java#L150

Added line #L150 was not covered by tests
}
values.add(convertedValue);
}
return values;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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.plugin.transport.grpc.proto.request.search.query;

import org.opensearch.index.query.MatchAllQueryBuilder;
import org.opensearch.index.query.MatchNoneQueryBuilder;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.TermQueryBuilder;
import org.opensearch.index.query.TermsQueryBuilder;
import org.opensearch.protobufs.FieldValue;
import org.opensearch.protobufs.MatchAllQuery;
import org.opensearch.protobufs.MatchNoneQuery;
import org.opensearch.protobufs.QueryContainer;
import org.opensearch.protobufs.StringArray;
import org.opensearch.protobufs.TermQuery;
import org.opensearch.protobufs.TermsLookupFieldStringArrayMap;
import org.opensearch.protobufs.TermsQueryField;
import org.opensearch.test.OpenSearchTestCase;

import java.util.HashMap;
import java.util.Map;

public class AbstractQueryBuilderProtoUtilsTests extends OpenSearchTestCase {

public void testParseInnerQueryBuilderProtoWithMatchAll() {
// Create a QueryContainer with MatchAllQuery
MatchAllQuery matchAllQuery = MatchAllQuery.newBuilder().build();
QueryContainer queryContainer = QueryContainer.newBuilder().setMatchAll(matchAllQuery).build();

// Call parseInnerQueryBuilderProto
QueryBuilder queryBuilder = AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(queryContainer);

// Verify the result
assertNotNull("QueryBuilder should not be null", queryBuilder);
assertTrue("QueryBuilder should be a MatchAllQueryBuilder", queryBuilder instanceof MatchAllQueryBuilder);
}

public void testParseInnerQueryBuilderProtoWithMatchNone() {
// Create a QueryContainer with MatchNoneQuery
MatchNoneQuery matchNoneQuery = MatchNoneQuery.newBuilder().build();
QueryContainer queryContainer = QueryContainer.newBuilder().setMatchNone(matchNoneQuery).build();

// Call parseInnerQueryBuilderProto
QueryBuilder queryBuilder = AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(queryContainer);

// Verify the result
assertNotNull("QueryBuilder should not be null", queryBuilder);
assertTrue("QueryBuilder should be a MatchNoneQueryBuilder", queryBuilder instanceof MatchNoneQueryBuilder);
}

public void testParseInnerQueryBuilderProtoWithTerm() {
// Create a QueryContainer with Term query
Map<String, TermQuery> termMap = new HashMap<>();

// Create a FieldValue for the term value
FieldValue fieldValue = FieldValue.newBuilder().setStringValue("test-value").build();

// Create a TermQuery with the FieldValue
TermQuery termQuery = TermQuery.newBuilder().setValue(fieldValue).build();

termMap.put("test-field", termQuery);

QueryContainer queryContainer = QueryContainer.newBuilder().putAllTerm(termMap).build();

// Call parseInnerQueryBuilderProto
QueryBuilder queryBuilder = AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(queryContainer);

// Verify the result
assertNotNull("QueryBuilder should not be null", queryBuilder);
assertTrue("QueryBuilder should be a TermQueryBuilder", queryBuilder instanceof TermQueryBuilder);
TermQueryBuilder termQueryBuilder = (TermQueryBuilder) queryBuilder;
assertEquals("Field name should match", "test-field", termQueryBuilder.fieldName());
assertEquals("Value should match", "test-value", termQueryBuilder.value());
}

public void testParseInnerQueryBuilderProtoWithTerms() {
// Create a StringArray for terms values
StringArray stringArray = StringArray.newBuilder().addStringArray("value1").addStringArray("value2").build();

// Create a TermsLookupFieldStringArrayMap
TermsLookupFieldStringArrayMap termsLookupFieldStringArrayMap = TermsLookupFieldStringArrayMap.newBuilder()
.setStringArray(stringArray)
.build();

// Create a map for TermsLookupFieldStringArrayMap
Map<String, TermsLookupFieldStringArrayMap> termsLookupFieldStringArrayMapMap = new HashMap<>();
termsLookupFieldStringArrayMapMap.put("test-field", termsLookupFieldStringArrayMap);

// Create a TermsQueryField
TermsQueryField termsQueryField = TermsQueryField.newBuilder()
.putAllTermsLookupFieldStringArrayMap(termsLookupFieldStringArrayMapMap)
.build();

// Create a QueryContainer with Terms query
QueryContainer queryContainer = QueryContainer.newBuilder().setTerms(termsQueryField).build();

// Call parseInnerQueryBuilderProto
QueryBuilder queryBuilder = AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(queryContainer);

// Verify the result
assertNotNull("QueryBuilder should not be null", queryBuilder);
assertTrue("QueryBuilder should be a TermsQueryBuilder", queryBuilder instanceof TermsQueryBuilder);
TermsQueryBuilder termsQueryBuilder = (TermsQueryBuilder) queryBuilder;
assertEquals("Field name should match", "test-field", termsQueryBuilder.fieldName());
assertEquals("Values size should match", 2, termsQueryBuilder.values().size());
assertEquals("First value should match", "value1", termsQueryBuilder.values().get(0));
assertEquals("Second value should match", "value2", termsQueryBuilder.values().get(1));
}

public void testParseInnerQueryBuilderProtoWithUnsupportedQueryType() {
// Create an empty QueryContainer (no query type specified)
QueryContainer queryContainer = QueryContainer.newBuilder().build();

// Call parseInnerQueryBuilderProto, should throw UnsupportedOperationException
UnsupportedOperationException exception = expectThrows(
UnsupportedOperationException.class,
() -> AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(queryContainer)
);

// Verify the exception message
assertTrue("Exception message should mention 'not supported yet'", exception.getMessage().contains("not supported yet"));
}
}
Loading
Loading