Skip to content
Draft
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 @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- 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))
- Implement GRPC Terms, Cardinality, Missing aggregations ([#19894](https://github.com/opensearch-project/OpenSearch/pull/19894))

### Changed
- Faster `terms` query creation for `keyword` field with index and docValues enabled ([#19350](https://github.com/opensearch-project/OpenSearch/pull/19350))
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ kotlin = "1.7.10"
antlr4 = "4.13.1"
guava = "33.2.1-jre"
gson = "2.13.2"
opensearchprotobufs = "0.23.0"
opensearchprotobufs = "0.24.0-SNAPSHOT"
protobuf = "3.25.8"
jakarta_annotation = "1.3.5"
google_http_client = "1.44.1"
Expand Down
6 changes: 6 additions & 0 deletions modules/transport-grpc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ dependencies {
implementation "org.opensearch:protobufs:${versions.opensearchprotobufs}"
testImplementation project(':test:framework')
}
repositories {
maven {
url = 'https://ci.opensearch.org/ci/dbc/snapshots/maven/'
}
// mavenLocal()
}

tasks.named("dependencyLicenses").configure {
mapping from: /grpc-.*/, to: 'grpc'
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20aae22bf8609cb6a963f6897c09384f8d5ecb78
6 changes: 6 additions & 0 deletions modules/transport-grpc/spi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ thirdPartyAudit {
'com.google.common.util.concurrent.ListenableFuture'
)
}

repositories {
maven {
url = 'https://ci.opensearch.org/ci/dbc/snapshots/maven/'
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20aae22bf8609cb6a963f6897c09384f8d5ecb78
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@

import org.opensearch.common.unit.TimeValue;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.protobufs.AggregationContainer;
import org.opensearch.protobufs.DerivedField;
import org.opensearch.protobufs.FieldAndFormat;
import org.opensearch.protobufs.Rescore;
import org.opensearch.protobufs.ScriptField;
import org.opensearch.protobufs.SearchRequestBody;
import org.opensearch.protobufs.TrackHits;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.sort.SortBuilder;
import org.opensearch.transport.grpc.proto.request.common.FetchSourceContextProtoUtils;
import org.opensearch.transport.grpc.proto.request.common.ScriptProtoUtils;
import org.opensearch.transport.grpc.proto.request.search.aggregation.AggregationContainerProtoUtils;
import org.opensearch.transport.grpc.proto.request.search.query.AbstractQueryBuilderProtoUtils;
import org.opensearch.transport.grpc.proto.request.search.sort.SortBuilderProtoUtils;
import org.opensearch.transport.grpc.proto.request.search.suggest.SuggestBuilderProtoUtils;
import org.opensearch.transport.grpc.spi.QueryBuilderProtoConverterRegistry;

import java.io.IOException;
import java.util.Map;
Expand Down Expand Up @@ -55,7 +59,7 @@ public static void parseProto(
AbstractQueryBuilderProtoUtils queryUtils
) throws IOException {
// Parse all non-query fields
parseNonQueryFields(searchSourceBuilder, protoRequest);
parseNonQueryFields(searchSourceBuilder, protoRequest, queryUtils);

// Handle queries using the instance-based approach
if (protoRequest.hasQuery()) {
Expand All @@ -67,9 +71,21 @@ public static void parseProto(
}

/**
* Parses all fields except queries from the protobuf SearchRequestBody.
* Parses all non-query fields from the protobuf SearchRequestBody in the same order as REST API.
* This excludes the main 'query' and 'postFilter' fields which are handled separately.
* This matches the order in {@link SearchSourceBuilder#parseXContent(XContentParser, boolean)}.
*
* @param searchSourceBuilder The SearchSourceBuilder to populate
* @param protoRequest The Protocol Buffer SearchRequestBody to parse
* @param queryUtils The query utils instance for parsing queries in aggregations/filters
* @throws IOException if there's an error during parsing
*/
private static void parseNonQueryFields(SearchSourceBuilder searchSourceBuilder, SearchRequestBody protoRequest) throws IOException {
private static void parseNonQueryFields(
SearchSourceBuilder searchSourceBuilder,
SearchRequestBody protoRequest,
AbstractQueryBuilderProtoUtils queryUtils
) throws IOException {
QueryBuilderProtoConverterRegistry registry = queryUtils.getRegistry();
// TODO what to do about parser.getDeprecationHandler() for protos?

if (protoRequest.hasFrom()) {
Expand Down Expand Up @@ -148,10 +164,14 @@ private static void parseNonQueryFields(SearchSourceBuilder searchSourceBuilder,
}
}

// TODO support aggregations
/*
if(protoRequest.hasAggs()){}
*/
if (protoRequest.getAggregationsCount() > 0) {
for (Map.Entry<String, AggregationContainer> entry : protoRequest.getAggregationsMap().entrySet()) {
String aggName = entry.getKey();
AggregationContainer aggContainer = entry.getValue();
AggregationBuilder aggBuilder = AggregationContainerProtoUtils.fromProto(aggName, aggContainer, queryUtils);
searchSourceBuilder.aggregation(aggBuilder);
}
}

if (protoRequest.hasHighlight()) {
searchSourceBuilder.highlighter(HighlightBuilderProtoUtils.fromProto(protoRequest.getHighlight()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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.aggregation;

import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.protobufs.AggregationContainer;
import org.opensearch.protobufs.ObjectMap;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.transport.grpc.proto.request.common.ObjectMapProtoUtils;
import org.opensearch.transport.grpc.proto.request.search.query.AbstractQueryBuilderProtoUtils;

import java.util.Map;

/**
* Utility class for converting AggregationContainer Protocol Buffers to OpenSearch AggregationBuilder objects.
* This class handles the transformation of Protocol Buffer aggregation containers into their corresponding
* OpenSearch AggregationBuilder implementations. It serves as the main entry point for converting all
* aggregation types from protobuf format to OpenSearch format.
*
* <p>This class is analogous to the REST-side aggregation parsing framework where {@link XContentParser}
* is used to parse aggregations from JSON/REST requests. Each aggregation type's {@code PARSER} field
* (which uses {@code fromXContent}) has a corresponding protobuf converter method that performs the same
* logical transformation but from Protocol Buffer representation.
*
* <p>The dispatch logic mirrors how REST aggregations are parsed: by examining the aggregation type field
* and delegating to the appropriate type-specific parser.
*/
public class AggregationContainerProtoUtils {

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

/**
* Converts a Protocol Buffer AggregationContainer to an OpenSearch AggregationBuilder.
* This method dispatches to the appropriate aggregation-specific converter based on the
* aggregation type specified in the container.
*
* <p>This is the gRPC equivalent of the REST-side aggregation parsing in
* {@code AggregationBuilder.parseAggregators}, where each aggregation type has a registered
* parser that reads from {@link XContentParser} via {@code fromXContent}. This method performs
* the same role but reads from Protocol Buffer structures instead of JSON.
*
* @param name The name of the aggregation
* @param aggContainer The Protocol Buffer AggregationContainer object
* @return A configured AggregationBuilder instance
* @throws IllegalArgumentException if the aggregation type is not supported or unrecognized
* @see org.opensearch.search.aggregations.AggregationBuilder
*/
public static AggregationBuilder fromProto(String name, AggregationContainer aggContainer) {
return fromProto(name, aggContainer, null);
}

/**
* Converts a Protocol Buffer AggregationContainer to an OpenSearch AggregationBuilder.
* This method dispatches to the appropriate aggregation-specific converter based on the
* aggregation type specified in the container. It also supports nested queries in aggregations
* (like filter aggregations) by accepting a queryUtils parameter.
*
* <p>This is the gRPC equivalent of the REST-side aggregation parsing in
* {@code AggregationBuilder.parseAggregators}, where each aggregation type has a registered
* parser that reads from {@link XContentParser} via {@code fromXContent}. This method performs
* the same role but reads from Protocol Buffer structures instead of JSON.
*
* @param name The name of the aggregation
* @param aggContainer The Protocol Buffer AggregationContainer object
* @param queryUtils The query utils instance for parsing nested queries (can be null if not needed)
* @return A configured AggregationBuilder instance
* @throws IllegalArgumentException if the aggregation type is not supported or unrecognized
* @see org.opensearch.search.aggregations.AggregationBuilder
*/
public static AggregationBuilder fromProto(String name, AggregationContainer aggContainer, AbstractQueryBuilderProtoUtils queryUtils) {
AggregationBuilder builder;

// Dispatch based on the aggregation type
switch (aggContainer.getAggregationCase()) {
case CARDINALITY:
builder = CardinalityAggregationProtoUtils.fromProto(name, aggContainer.getCardinality());
break;

case MISSING:
builder = MissingAggregationProtoUtils.fromProto(name, aggContainer.getMissing());
break;

case TERMS:
builder = TermsAggregationProtoUtils.fromProto(name, aggContainer.getTerms());
break;

case FILTER:
if (queryUtils == null) {
throw new IllegalArgumentException("Filter aggregation requires queryUtils to be provided for parsing nested queries");
}
builder = FilterAggregationProtoUtils.fromProto(name, aggContainer.getFilter(), queryUtils);
break;

case AGGREGATION_NOT_SET:
throw new IllegalArgumentException("Aggregation type not set for aggregation: " + name);

default:
throw new IllegalArgumentException(
"Unsupported aggregation type: " + aggContainer.getAggregationCase() + " for aggregation: " + name
);
}

// Handle metadata if present
if (aggContainer.hasMeta()) {
ObjectMap metaProto = aggContainer.getMeta();
Map<String, Object> metadata = ObjectMapProtoUtils.fromProto(metaProto);
builder.setMetadata(metadata);
}

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.aggregation;

import org.opensearch.protobufs.CardinalityAggregation;
import org.opensearch.protobufs.CardinalityExecutionMode;
import org.opensearch.protobufs.FieldValue;
import org.opensearch.search.aggregations.metrics.CardinalityAggregationBuilder;
import org.opensearch.transport.grpc.proto.request.common.ScriptProtoUtils;
import org.opensearch.transport.grpc.proto.response.common.FieldValueProtoUtils;
import org.opensearch.transport.grpc.util.ProtobufEnumUtils;

import java.lang.reflect.Method;

/**
* Utility class for converting CardinalityAggregation Protocol Buffers to OpenSearch objects.
* This class provides methods to transform Protocol Buffer representations of cardinality aggregations
* into their corresponding OpenSearch CardinalityAggregationBuilder implementations.
*/
public class CardinalityAggregationProtoUtils {

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

/**
* Converts a Protocol Buffer CardinalityAggregation to an OpenSearch CardinalityAggregationBuilder.
*
* <p>This method is the gRPC equivalent of {@link CardinalityAggregationBuilder#PARSER}, which parses
* cardinality aggregations from REST/JSON requests via {@code fromXContent}. Similar to how the parser
* reads JSON fields, this method extracts values from the Protocol Buffer representation and creates
* a properly configured CardinalityAggregationBuilder.
*
* <p>The REST-side serialization via {@link CardinalityAggregationBuilder#doXContentBody} produces
* JSON that conceptually mirrors the protobuf structure used here.
*
* @param name The name of the aggregation
* @param cardinalityAggProto The Protocol Buffer CardinalityAggregation object
* @return A configured CardinalityAggregationBuilder instance
* @throws IllegalArgumentException if the field value type is not supported
* @see CardinalityAggregationBuilder#PARSER
* @see CardinalityAggregationBuilder#doXContentBody
*/
public static CardinalityAggregationBuilder fromProto(String name, CardinalityAggregation cardinalityAggProto) {
CardinalityAggregationBuilder builder = new CardinalityAggregationBuilder(name);

// Set field if present
if (cardinalityAggProto.hasField()) {
builder.field(cardinalityAggProto.getField());
}

// Set missing value if present
if (cardinalityAggProto.hasMissing()) {
FieldValue missingValue = cardinalityAggProto.getMissing();
Object missing = FieldValueProtoUtils.fromProto(missingValue, false);
builder.missing(missing);
}

// Set script if present
if (cardinalityAggProto.hasScript()) {
try {
builder.script(ScriptProtoUtils.parseFromProtoRequest(cardinalityAggProto.getScript()));
} catch (Exception e) {
throw new IllegalArgumentException("Failed to parse script for cardinality aggregation", e);
}
}

// Set precision threshold if present
if (cardinalityAggProto.hasPrecisionThreshold()) {
builder.precisionThreshold(cardinalityAggProto.getPrecisionThreshold());
}

// Set execution hint if present (added in OpenSearch 2.19.1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this

// Use reflection for forward-compatibility: if the OpenSearch version doesn't support
// this field yet, it will be silently ignored rather than causing a compilation error.
if (cardinalityAggProto.hasExecutionHint()) {
String executionHint = convertExecutionMode(cardinalityAggProto.getExecutionHint());
if (executionHint != null) {
try {
Method method = CardinalityAggregationBuilder.class.getMethod("executionHint", String.class);
method.invoke(builder, executionHint);
} catch (NoSuchMethodException e) {
// Method doesn't exist in OpenSearch < 2.19.1 - silently ignore
} catch (Exception e) {
throw new IllegalArgumentException("Failed to set execution hint for cardinality aggregation", e);
}
}
}

return builder;
}

/**
* Converts a Protocol Buffer CardinalityExecutionMode to its string representation.
* Supports execution hints added in OpenSearch 2.19.1.
*
* @param mode The Protocol Buffer CardinalityExecutionMode
* @return The string representation of the execution mode, or null if unspecified
*/
private static String convertExecutionMode(CardinalityExecutionMode mode) {
if (mode == CardinalityExecutionMode.CARDINALITY_EXECUTION_MODE_UNSPECIFIED) {
return null;
}
return ProtobufEnumUtils.convertToString(mode);
}
}
Loading
Loading