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 @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Implement GRPC MatchPhrase, MultiMatch queries ([#19449](https://github.com/opensearch-project/OpenSearch/pull/19449))
- Optimize gRPC transport thread management for improved throughput ([#19278](https://github.com/opensearch-project/OpenSearch/pull/19278))
- Implement GRPC Boolean query and inject registry for all internal query converters ([#19391](https://github.com/opensearch-project/OpenSearch/pull/19391))
- Implement GRPC Script query ([#19455](https://github.com/opensearch-project/OpenSearch/pull/19455))

### Changed
- Refactor `if-else` chains to use `Java 17 pattern matching switch expressions`(([#18965](https://github.com/opensearch-project/OpenSearch/pull/18965))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected void registerBuiltInConverters() {
delegate.registerConverter(new MatchPhraseQueryBuilderProtoConverter());
delegate.registerConverter(new MultiMatchQueryBuilderProtoConverter());
delegate.registerConverter(new BoolQueryBuilderProtoConverter());
delegate.registerConverter(new ScriptQueryBuilderProtoConverter());

// Set the registry on all converters so they can access each other
delegate.setRegistryOnAllConverters(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 Script queries.
* This class implements the QueryBuilderProtoConverter interface to provide Script query support
* for the gRPC transport module.
*/
public class ScriptQueryBuilderProtoConverter implements QueryBuilderProtoConverter {

/**
* Constructs a new ScriptQueryBuilderProtoConverter.
*/
public ScriptQueryBuilderProtoConverter() {
// Default constructor
}

@Override
public QueryContainer.QueryContainerCase getHandledQueryCase() {
return QueryContainer.QueryContainerCase.SCRIPT;
}

@Override
public QueryBuilder fromProto(QueryContainer queryContainer) {
if (queryContainer == null || !queryContainer.hasScript()) {
throw new IllegalArgumentException("QueryContainer does not contain a Script query");
}

return ScriptQueryBuilderProtoUtils.fromProto(queryContainer.getScript());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.ScriptQueryBuilder;
import org.opensearch.protobufs.ScriptQuery;
import org.opensearch.script.Script;
import org.opensearch.transport.grpc.proto.request.common.ScriptProtoUtils;

/**
* Utility class for converting ScriptQuery Protocol Buffers to ScriptQueryBuilder objects.
* This class handles the conversion of Protocol Buffer representations to their
* corresponding OpenSearch query builder objects.
*/
class ScriptQueryBuilderProtoUtils {

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

/**
* Converts a ScriptQuery Protocol Buffer to a ScriptQueryBuilder object.
* This method follows the same pattern as ScriptQueryBuilder.fromXContent().
*
* @param scriptQueryProto the Protocol Buffer ScriptQuery to convert
* @return the converted ScriptQueryBuilder object
* @throws IllegalArgumentException if the script query proto is null or invalid
*/
static ScriptQueryBuilder fromProto(ScriptQuery scriptQueryProto) {
if (scriptQueryProto == null) {
throw new IllegalArgumentException("ScriptQuery cannot be null");
}

if (!scriptQueryProto.hasScript()) {
throw new IllegalArgumentException("script must be provided with a [script] query");
}

Script script = ScriptProtoUtils.parseFromProtoRequest(scriptQueryProto.getScript());

float boost = ScriptQueryBuilder.DEFAULT_BOOST;
String queryName = null;

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

if (scriptQueryProto.hasXName()) {
queryName = scriptQueryProto.getXName();
}

return new ScriptQueryBuilder(script).boost(boost).queryName(queryName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.protobufs.BoolQuery;
import org.opensearch.protobufs.FieldValue;
import org.opensearch.protobufs.InlineScript;
import org.opensearch.protobufs.MatchAllQuery;
import org.opensearch.protobufs.MatchPhraseQuery;
import org.opensearch.protobufs.MinimumShouldMatch;
import org.opensearch.protobufs.MultiMatchQuery;
import org.opensearch.protobufs.QueryContainer;
import org.opensearch.protobufs.Script;
import org.opensearch.protobufs.ScriptQuery;
import org.opensearch.protobufs.TermQuery;
import org.opensearch.protobufs.TextQueryType;
import org.opensearch.test.OpenSearchTestCase;
Expand Down Expand Up @@ -65,6 +68,22 @@ public void testTermQueryConversion() {
assertEquals("Should be a TermQueryBuilder", "org.opensearch.index.query.TermQueryBuilder", queryBuilder.getClass().getName());
}

public void testScriptQueryConversion() {
// Create a Script query container with inline script
Script script = Script.newBuilder().setInline(InlineScript.newBuilder().setSource("doc['field'].value > 100").build()).build();

ScriptQuery scriptQuery = ScriptQuery.newBuilder().setScript(script).setBoost(1.5f).setXName("test_script_query").build();

QueryContainer queryContainer = QueryContainer.newBuilder().setScript(scriptQuery).build();

// Convert using the registry
QueryBuilder queryBuilder = registry.fromProto(queryContainer);

// Verify the result
assertNotNull("QueryBuilder should not be null", queryBuilder);
assertEquals("Should be a ScriptQueryBuilder", "org.opensearch.index.query.ScriptQueryBuilder", queryBuilder.getClass().getName());
}

public void testNullQueryContainer() {
expectThrows(IllegalArgumentException.class, () -> registry.fromProto(null));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.InlineScript;
import org.opensearch.protobufs.QueryContainer;
import org.opensearch.protobufs.Script;
import org.opensearch.protobufs.ScriptQuery;
import org.opensearch.test.OpenSearchTestCase;

/**
* Unit tests for ScriptQueryBuilderProtoConverter.
* Tests only the converter-specific logic (QueryContainer handling).
* The core conversion logic is tested in ScriptQueryBuilderProtoUtilsTests.
*/
public class ScriptQueryBuilderProtoConverterTests extends OpenSearchTestCase {

private ScriptQueryBuilderProtoConverter converter;

@Override
public void setUp() throws Exception {
super.setUp();
converter = new ScriptQueryBuilderProtoConverter();
}

public void testGetHandledQueryCase() {
assertEquals(QueryContainer.QueryContainerCase.SCRIPT, converter.getHandledQueryCase());
}

public void testFromProtoWithValidScriptQuery() {
ScriptQuery scriptQuery = ScriptQuery.newBuilder()
.setScript(Script.newBuilder().setInline(InlineScript.newBuilder().setSource("true").build()).build())
.build();

QueryContainer container = QueryContainer.newBuilder().setScript(scriptQuery).build();

QueryBuilder result = converter.fromProto(container);

assertNotNull(result);
assertTrue(result instanceof org.opensearch.index.query.ScriptQueryBuilder);
}

public void testFromProtoWithNullContainer() {
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(null));
assertEquals("QueryContainer does not contain a Script query", exception.getMessage());
}

public void testFromProtoWithContainerWithoutScript() {
QueryContainer container = QueryContainer.newBuilder().build();

IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> converter.fromProto(container));
assertEquals("QueryContainer does not contain a Script query", exception.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* 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.ScriptQueryBuilder;
import org.opensearch.protobufs.BuiltinScriptLanguage;
import org.opensearch.protobufs.InlineScript;
import org.opensearch.protobufs.Script;
import org.opensearch.protobufs.ScriptLanguage;
import org.opensearch.protobufs.ScriptQuery;
import org.opensearch.test.OpenSearchTestCase;

/**
* Unit tests for ScriptQueryBuilderProtoUtils.
*/
public class ScriptQueryBuilderProtoUtilsTests extends OpenSearchTestCase {

public void testFromProtoWithInlineScript() {
// Test inline script with all features
ScriptQuery scriptQuery = ScriptQuery.newBuilder()
.setScript(
Script.newBuilder()
.setInline(
InlineScript.newBuilder()
.setSource("doc['field'].value > 0")
.setLang(ScriptLanguage.newBuilder().setBuiltin(BuiltinScriptLanguage.BUILTIN_SCRIPT_LANGUAGE_PAINLESS))
.setParams(
org.opensearch.protobufs.ObjectMap.newBuilder()
.putFields("multiplier", org.opensearch.protobufs.ObjectMap.Value.newBuilder().setDouble(2.5).build())
.build()
)
.putOptions("content_type", "application/json")
.build()
)
.build()
)
.setBoost(2.0f)
.setXName("test_query")
.build();

ScriptQueryBuilder result = ScriptQueryBuilderProtoUtils.fromProto(scriptQuery);

assertNotNull(result);
assertEquals(2.0f, result.boost(), 0.001f);
assertEquals("test_query", result.queryName());
assertNotNull(result.script());
assertEquals("doc['field'].value > 0", result.script().getIdOrCode());
assertEquals("painless", result.script().getLang());
assertNotNull(result.script().getParams());
assertEquals(2.5, result.script().getParams().get("multiplier"));
assertNotNull(result.script().getOptions());
assertEquals("application/json", result.script().getOptions().get("content_type"));
}

public void testFromProtoWithStoredScript() {
ScriptQuery scriptQuery = ScriptQuery.newBuilder()
.setScript(
Script.newBuilder()
.setStored(
org.opensearch.protobufs.StoredScriptId.newBuilder()
.setId("my_stored_script")
.setParams(
org.opensearch.protobufs.ObjectMap.newBuilder()
.putFields(
"param1",
org.opensearch.protobufs.ObjectMap.Value.newBuilder().setString("test_value").build()
)
.build()
)
.build()
)
.build()
)
.build();

ScriptQueryBuilder result = ScriptQueryBuilderProtoUtils.fromProto(scriptQuery);

assertNotNull(result);
assertEquals("my_stored_script", result.script().getIdOrCode());
assertNull(result.script().getLang());
assertNotNull(result.script().getParams());
assertEquals("test_value", result.script().getParams().get("param1"));
}

public void testFromProtoWithDifferentScriptLanguages() {
// Test different script languages
String[] languages = { "painless", "java", "mustache", "expression", "custom_lang" };
BuiltinScriptLanguage[] builtinLangs = {
BuiltinScriptLanguage.BUILTIN_SCRIPT_LANGUAGE_PAINLESS,
BuiltinScriptLanguage.BUILTIN_SCRIPT_LANGUAGE_JAVA,
BuiltinScriptLanguage.BUILTIN_SCRIPT_LANGUAGE_MUSTACHE,
BuiltinScriptLanguage.BUILTIN_SCRIPT_LANGUAGE_EXPRESSION,
null };

for (int i = 0; i < languages.length; i++) {
ScriptQuery.Builder scriptQueryBuilder = ScriptQuery.newBuilder();
InlineScript.Builder inlineScriptBuilder = InlineScript.newBuilder().setSource("test_script_" + i);

if (builtinLangs[i] != null) {
inlineScriptBuilder.setLang(ScriptLanguage.newBuilder().setBuiltin(builtinLangs[i]));
} else {
inlineScriptBuilder.setLang(ScriptLanguage.newBuilder().setCustom(languages[i]));
}

scriptQueryBuilder.setScript(Script.newBuilder().setInline(inlineScriptBuilder.build()).build());

ScriptQueryBuilder result = ScriptQueryBuilderProtoUtils.fromProto(scriptQueryBuilder.build());

assertNotNull(result);
assertEquals(languages[i], result.script().getLang());
assertEquals("test_script_" + i, result.script().getIdOrCode());
}
}

public void testFromProtoWithDefaults() {
ScriptQuery scriptQuery = ScriptQuery.newBuilder()
.setScript(Script.newBuilder().setInline(InlineScript.newBuilder().setSource("true").build()).build())
.build();

ScriptQueryBuilder result = ScriptQueryBuilderProtoUtils.fromProto(scriptQuery);

assertNotNull(result);
assertEquals(1.0f, result.boost(), 0.001f);
assertNull(result.queryName());
assertNotNull(result.script());
assertEquals("true", result.script().getIdOrCode());
}

public void testFromProtoWithEdgeCases() {
ScriptQuery scriptQuery = ScriptQuery.newBuilder()
.setScript(Script.newBuilder().setInline(InlineScript.newBuilder().setSource("true").build()).build())
.setBoost(0.0f)
.setXName("")
.build();

ScriptQueryBuilder result = ScriptQueryBuilderProtoUtils.fromProto(scriptQuery);

assertNotNull(result);
assertEquals(0.0f, result.boost(), 0.001f);
assertEquals("", result.queryName());
}

public void testFromProtoWithNullInput() {
IllegalArgumentException exception = expectThrows(
IllegalArgumentException.class,
() -> ScriptQueryBuilderProtoUtils.fromProto(null)
);
assertEquals("ScriptQuery cannot be null", exception.getMessage());
}

public void testFromProtoWithMissingScript() {
ScriptQuery scriptQuery = ScriptQuery.newBuilder().build();

IllegalArgumentException exception = expectThrows(
IllegalArgumentException.class,
() -> ScriptQueryBuilderProtoUtils.fromProto(scriptQuery)
);
assertEquals("script must be provided with a [script] query", exception.getMessage());
}
}
Loading