diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java index 9dcd73a427..4eaea7f995 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java @@ -222,7 +222,9 @@ public enum BuiltinFunctionName { QUERY(FunctionName.of("query")), MATCH_QUERY(FunctionName.of("match_query")), MATCHQUERY(FunctionName.of("matchquery")), - MULTI_MATCH(FunctionName.of("multi_match")); + MULTI_MATCH(FunctionName.of("multi_match")), + MULTIMATCH(FunctionName.of("multimatch")), + MULTIMATCHQUERY(FunctionName.of("multimatchquery")); private final FunctionName name; diff --git a/core/src/main/java/org/opensearch/sql/expression/function/OpenSearchFunctions.java b/core/src/main/java/org/opensearch/sql/expression/function/OpenSearchFunctions.java index 83d69062e1..2041b9762e 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/OpenSearchFunctions.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/OpenSearchFunctions.java @@ -27,10 +27,12 @@ public class OpenSearchFunctions { */ public void register(BuiltinFunctionRepository repository) { repository.register(match_bool_prefix()); + repository.register(multi_match(BuiltinFunctionName.MULTI_MATCH)); + repository.register(multi_match(BuiltinFunctionName.MULTIMATCH)); + repository.register(multi_match(BuiltinFunctionName.MULTIMATCHQUERY)); repository.register(match(BuiltinFunctionName.MATCH)); repository.register(match(BuiltinFunctionName.MATCHQUERY)); repository.register(match(BuiltinFunctionName.MATCH_QUERY)); - repository.register(multi_match()); repository.register(simple_query_string()); repository.register(query()); repository.register(query_string()); @@ -62,9 +64,8 @@ private static FunctionResolver match_phrase(BuiltinFunctionName matchPhrase) { return new RelevanceFunctionResolver(funcName, STRING); } - private static FunctionResolver multi_match() { - FunctionName funcName = BuiltinFunctionName.MULTI_MATCH.getName(); - return new RelevanceFunctionResolver(funcName, STRUCT); + private static FunctionResolver multi_match(BuiltinFunctionName multiMatchName) { + return new RelevanceFunctionResolver(multiMatchName.getName(), STRUCT); } private static FunctionResolver simple_query_string() { diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 0644b970f5..9a16c45e90 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -2931,12 +2931,20 @@ Description >>>>>>>>>>> ``multi_match([field_expression+], query_expression[, option=]*)`` +``multi_match(query=query_expression+, fields=[field_expression+][, option=]*)`` The multi_match function maps to the multi_match query used in search engine, to return the documents that match a provided text, number, date or boolean value with a given field or fields. The **^** lets you *boost* certain fields. Boosts are multipliers that weigh matches in one field more heavily than matches in other fields. The syntax allows to specify the fields in double quotes, single quotes, in backtick or even without any wrap. All fields search using star ``"*"`` is also available (star symbol should be wrapped). The weight is optional and should be specified using after the field name, it could be delimeted by the `caret` character or by whitespace. Please, refer to examples below: + +- ``MULTI_MATCH(...)`` +- ``MULTIMATCH(...)`` +- ``MULTIMATCHQUERY(...)`` + | ``multi_match(["Tags" ^ 2, 'Title' 3.4, `Body`, Comments ^ 0.3], ...)`` | ``multi_match(["*"], ...)`` +| ``multimatch(query='query value', fields=["Tags^2,Title^3.4,Body"], ...)`` +| ``multimatchquery('query'='query value', 'fields'='Title', ...)`` Available parameters include: diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/MultiMatchIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/MultiMatchIT.java index ba62e6629f..24ce45fd20 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/MultiMatchIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/MultiMatchIT.java @@ -6,6 +6,8 @@ package org.opensearch.sql.sql; import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BEER; +import static org.opensearch.sql.util.MatcherUtils.rows; +import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; import java.io.IOException; import org.json.JSONObject; @@ -27,38 +29,99 @@ public void init() throws IOException { */ @Test - public void test_mandatory_params() throws IOException { + public void test_mandatory_params() { String query = "SELECT Id FROM " + TEST_INDEX_BEER + " WHERE multi_match([\\\"Tags\\\" ^ 1.5, Title, `Body` 4.2], 'taste')"; - var result = new JSONObject(executeQuery(query, "jdbc")); + JSONObject result = executeJdbcRequest(query); assertEquals(16, result.getInt("total")); } @Test - public void test_all_params() throws IOException { + public void test_all_params() { String query = "SELECT Id FROM " + TEST_INDEX_BEER + " WHERE multi_match(['Body', Tags], 'taste beer', operator='and', analyzer=english," + "auto_generate_synonyms_phrase_query=true, boost = 0.77, cutoff_frequency=0.33," + "fuzziness = 'AUTO:1,5', fuzzy_transpositions = false, lenient = true, max_expansions = 25," + "minimum_should_match = '2<-25% 9<-3', prefix_length = 7, tie_breaker = 0.3," + "type = most_fields, slop = 2, zero_terms_query = 'ALL');"; - var result = new JSONObject(executeQuery(query, "jdbc")); + JSONObject result = executeJdbcRequest(query); assertEquals(10, result.getInt("total")); } @Test - public void verify_wildcard_test() throws IOException { + public void verify_wildcard_test() { String query1 = "SELECT Id FROM " + TEST_INDEX_BEER + " WHERE multi_match(['Tags'], 'taste')"; - var result1 = new JSONObject(executeQuery(query1, "jdbc")); + JSONObject result1 = executeJdbcRequest(query1); String query2 = "SELECT Id FROM " + TEST_INDEX_BEER + " WHERE multi_match(['T*'], 'taste')"; - var result2 = new JSONObject(executeQuery(query2, "jdbc")); + JSONObject result2 = executeJdbcRequest(query2); assertNotEquals(result2.getInt("total"), result1.getInt("total")); String query = "SELECT Id FROM " + TEST_INDEX_BEER + " WHERE multi_match(['*Date'], '2014-01-22');"; - var result = new JSONObject(executeQuery(query, "jdbc")); + JSONObject result = executeJdbcRequest(query); assertEquals(10, result.getInt("total")); } + + @Test + public void test_multimatch_alternate_parameter_syntax() { + String query = "SELECT Tags FROM " + TEST_INDEX_BEER + + " WHERE multimatch('query'='taste', 'fields'='Tags')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(8, result.getInt("total")); + } + + @Test + public void test_multimatchquery_alternate_parameter_syntax() { + String query = "SELECT Tags FROM " + TEST_INDEX_BEER + + " WHERE multimatchquery(query='cicerone', fields='Tags')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + verifyDataRows(result, rows("serving cicerone restaurants"), + rows("taste cicerone")); + } + + @Test + public void test_quoted_multi_match_alternate_parameter_syntax() { + String query = "SELECT Tags FROM " + TEST_INDEX_BEER + + " WHERE multi_match('query'='cicerone', 'fields'='Tags')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + verifyDataRows(result, rows("serving cicerone restaurants"), + rows("taste cicerone")); + } + + @Test + public void test_multi_match_alternate_parameter_syntax() { + String query = "SELECT Tags FROM " + TEST_INDEX_BEER + + " WHERE multi_match(query='cicerone', fields='Tags')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + verifyDataRows(result, rows("serving cicerone restaurants"), + rows("taste cicerone")); + } + + @Test + public void test_wildcard_multi_match_alternate_parameter_syntax() { + String query = "SELECT Body FROM " + TEST_INDEX_BEER + + " WHERE multi_match(query='IPA', fields='B*') LIMIT 1"; + JSONObject result = executeJdbcRequest(query); + verifyDataRows(result, rows("

I know what makes an IPA an IPA, but what are the unique" + + " characteristics of it's common variants? To be specific, the ones I'm interested in are Double IPA" + + " and Black IPA, but general differences between any other styles would be welcome too.

\n")); + } + + @Test + public void test_all_params_multimatchquery_alternate_parameter_syntax() { + String query = "SELECT Id FROM " + TEST_INDEX_BEER + + " WHERE multimatchquery(query='cicerone', fields='Tags', 'operator'='or', analyzer=english," + + "auto_generate_synonyms_phrase_query=true, boost = 0.77, cutoff_frequency=0.33," + + "fuzziness = 'AUTO:1,5', fuzzy_transpositions = false, lenient = true, max_expansions = 25," + + "minimum_should_match = '2<-25% 9<-3', prefix_length = 7, tie_breaker = 0.3," + + "type = most_fields, slop = 2, zero_terms_query = 'ALL');"; + + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + } } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilder.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilder.java index 843096eee0..2c55a28b88 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilder.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilder.java @@ -66,6 +66,8 @@ public class FilterQueryBuilder extends ExpressionNodeVisitor(ImmutableMap.of( "title", ExprValueUtils.floatValue(1.F), @@ -129,27 +131,69 @@ static Stream> generateValidData() { @ParameterizedTest @MethodSource("generateValidData") - public void test_valid_parameters(List validArgs) { + public void test_valid_parameters_multiMatch(List validArgs) { Assertions.assertNotNull(multiMatchQuery.build( new MultiMatchExpression(validArgs))); } + @ParameterizedTest + @MethodSource("generateValidData") + public void test_valid_parameters_multi_match(List validArgs) { + Assertions.assertNotNull(multiMatchQuery.build( + new MultiMatchExpression(validArgs, snakeCaseMultiMatchName))); + } + + @ParameterizedTest + @MethodSource("generateValidData") + public void test_valid_parameters_multiMatchQuery(List validArgs) { + Assertions.assertNotNull(multiMatchQuery.build( + new MultiMatchExpression(validArgs, multiMatchQueryName))); + } + @Test - public void test_SyntaxCheckException_when_no_arguments() { + public void test_SyntaxCheckException_when_no_arguments_multiMatch() { List arguments = List.of(); assertThrows(SyntaxCheckException.class, () -> multiMatchQuery.build(new MultiMatchExpression(arguments))); } @Test - public void test_SyntaxCheckException_when_one_argument() { + public void test_SyntaxCheckException_when_no_arguments_multi_match() { + List arguments = List.of(); + assertThrows(SyntaxCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchName))); + } + + @Test + public void test_SyntaxCheckException_when_no_arguments_multiMatchQuery() { + List arguments = List.of(); + assertThrows(SyntaxCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName))); + } + + @Test + public void test_SyntaxCheckException_when_one_argument_multiMatch() { List arguments = List.of(namedArgument("fields", fields_value)); assertThrows(SyntaxCheckException.class, () -> multiMatchQuery.build(new MultiMatchExpression(arguments))); } @Test - public void test_SemanticCheckException_when_invalid_parameter() { + public void test_SyntaxCheckException_when_one_argument_multi_match() { + List arguments = List.of(namedArgument("fields", fields_value)); + assertThrows(SyntaxCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, snakeCaseMultiMatchName))); + } + + @Test + public void test_SyntaxCheckException_when_one_argument_multiMatchQuery() { + List arguments = List.of(namedArgument("fields", fields_value)); + assertThrows(SyntaxCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName))); + } + + @Test + public void test_SemanticCheckException_when_invalid_parameter_multiMatch() { List arguments = List.of( namedArgument("fields", fields_value), namedArgument("query", query_value), @@ -158,15 +202,40 @@ public void test_SemanticCheckException_when_invalid_parameter() { () -> multiMatchQuery.build(new MultiMatchExpression(arguments))); } + @Test + public void test_SemanticCheckException_when_invalid_parameter_multi_match() { + List arguments = List.of( + namedArgument("fields", fields_value), + namedArgument("query", query_value), + DSL.namedArgument("unsupported", "unsupported_value")); + Assertions.assertThrows(SemanticCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, snakeCaseMultiMatchName))); + } + + @Test + public void test_SemanticCheckException_when_invalid_parameter_multiMatchQuery() { + List arguments = List.of( + namedArgument("fields", fields_value), + namedArgument("query", query_value), + DSL.namedArgument("unsupported", "unsupported_value")); + Assertions.assertThrows(SemanticCheckException.class, + () -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName))); + } + private NamedArgumentExpression namedArgument(String name, LiteralExpression value) { return DSL.namedArgument(name, value); } private class MultiMatchExpression extends FunctionExpression { public MultiMatchExpression(List arguments) { - super(MultiMatchTest.this.multiMatch, arguments); + super(multiMatchName, arguments); } + public MultiMatchExpression(List arguments, FunctionName funcName) { + super(funcName, arguments); + } + + @Override public ExprValue valueOf(Environment valueEnv) { throw new UnsupportedOperationException("Invalid function call, " diff --git a/sql/src/main/antlr/OpenSearchSQLLexer.g4 b/sql/src/main/antlr/OpenSearchSQLLexer.g4 index df104d2a2a..d11877488f 100644 --- a/sql/src/main/antlr/OpenSearchSQLLexer.g4 +++ b/sql/src/main/antlr/OpenSearchSQLLexer.g4 @@ -306,6 +306,7 @@ MINUTE_OF_HOUR: 'MINUTE_OF_HOUR'; MONTH_OF_YEAR: 'MONTH_OF_YEAR'; MULTIMATCH: 'MULTIMATCH'; MULTI_MATCH: 'MULTI_MATCH'; +MULTIMATCHQUERY: 'MULTIMATCHQUERY'; NESTED: 'NESTED'; PERCENTILES: 'PERCENTILES'; REGEXP_QUERY: 'REGEXP_QUERY'; diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index b17c25261a..3bdd836e27 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -355,6 +355,8 @@ multiFieldRelevanceFunction : multiFieldRelevanceFunctionName LR_BRACKET LT_SQR_PRTHS field=relevanceFieldAndWeight (COMMA field=relevanceFieldAndWeight)* RT_SQR_PRTHS COMMA query=relevanceQuery (COMMA relevanceArg)* RR_BRACKET + | multiFieldRelevanceFunctionName LR_BRACKET + alternateMultiMatchQuery COMMA alternateMultiMatchField (COMMA relevanceArg)* RR_BRACKET ; convertedDataType @@ -467,6 +469,8 @@ singleFieldRelevanceFunctionName multiFieldRelevanceFunctionName : MULTI_MATCH + | MULTIMATCH + | MULTIMATCHQUERY | SIMPLE_QUERY_STRING | QUERY_STRING ; @@ -481,6 +485,7 @@ functionArg relevanceArg : relevanceArgName EQUAL_SYMBOL relevanceArgValue + | argName=stringLiteral EQUAL_SYMBOL argVal=relevanceArgValue ; highlightArg @@ -530,3 +535,18 @@ highlightArgValue : stringLiteral ; +alternateMultiMatchArgName + : FIELDS + | QUERY + | stringLiteral + ; + +alternateMultiMatchQuery + : argName=alternateMultiMatchArgName EQUAL_SYMBOL argVal=relevanceArgValue + ; + +alternateMultiMatchField + : argName=alternateMultiMatchArgName EQUAL_SYMBOL argVal=relevanceArgValue + | argName=alternateMultiMatchArgName EQUAL_SYMBOL + LT_SQR_PRTHS argVal=relevanceArgValue RT_SQR_PRTHS + ; diff --git a/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java b/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java index 18efef039f..3c59a5f8f2 100644 --- a/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java +++ b/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java @@ -51,7 +51,9 @@ import com.google.common.collect.ImmutableMap; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.antlr.v4.runtime.RuleContext; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -78,6 +80,7 @@ import org.opensearch.sql.ast.expression.WindowFunction; import org.opensearch.sql.ast.tree.Sort.SortOption; import org.opensearch.sql.common.utils.StringUtils; +import org.opensearch.sql.expression.function.BuiltinFunctionName; import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser; import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.AndExpressionContext; import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.ColumnNameContext; @@ -396,9 +399,22 @@ public UnresolvedExpression visitSingleFieldRelevanceFunction( @Override public UnresolvedExpression visitMultiFieldRelevanceFunction( MultiFieldRelevanceFunctionContext ctx) { - return new Function( - ctx.multiFieldRelevanceFunctionName().getText().toLowerCase(), - multiFieldRelevanceArguments(ctx)); + // To support alternate syntax for MULTI_MATCH like + // 'MULTI_MATCH('query'='query_val', 'fields'='*fields_val')' + String funcName = StringUtils.unquoteText(ctx.multiFieldRelevanceFunctionName().getText()); + if ((funcName.equalsIgnoreCase(BuiltinFunctionName.MULTI_MATCH.toString()) + || funcName.equalsIgnoreCase(BuiltinFunctionName.MULTIMATCH.toString()) + || funcName.equalsIgnoreCase(BuiltinFunctionName.MULTIMATCHQUERY.toString())) + && ! ctx.getRuleContexts(OpenSearchSQLParser.AlternateMultiMatchQueryContext.class) + .isEmpty()) { + return new Function( + ctx.multiFieldRelevanceFunctionName().getText().toLowerCase(), + alternateMultiMatchArguments(ctx)); + } else { + return new Function( + ctx.multiFieldRelevanceFunctionName().getText().toLowerCase(), + multiFieldRelevanceArguments(ctx)); + } } private Function buildFunction(String functionName, @@ -423,9 +439,13 @@ private QualifiedName visitIdentifiers(List identifiers) { private void fillRelevanceArgs(List args, ImmutableList.Builder builder) { - args.forEach(v -> builder.add(new UnresolvedArgument( - v.relevanceArgName().getText().toLowerCase(), new Literal(StringUtils.unquoteText( - v.relevanceArgValue().getText()), DataType.STRING)))); + // To support old syntax we must support argument keys as quoted strings. + args.forEach(v -> builder.add(v.argName == null + ? new UnresolvedArgument(v.relevanceArgName().getText().toLowerCase(), + new Literal(StringUtils.unquoteText(v.relevanceArgValue().getText()), + DataType.STRING)) + : new UnresolvedArgument(StringUtils.unquoteText(v.argName.getText()).toLowerCase(), + new Literal(StringUtils.unquoteText(v.argVal.getText()), DataType.STRING)))); } private List noFieldRelevanceArguments( @@ -471,4 +491,41 @@ private List multiFieldRelevanceArguments( fillRelevanceArgs(ctx.relevanceArg(), builder); return builder.build(); } + + /** + * Adds support for multi_match alternate syntax like + * MULTI_MATCH('query'='Dale', 'fields'='*name'). + * @param ctx : Context for multi field relevance function. + * @return : Returns list of all arguments for relevance function. + */ + private List alternateMultiMatchArguments( + OpenSearchSQLParser.MultiFieldRelevanceFunctionContext ctx) { + // all the arguments are defaulted to string values + // to skip environment resolving and function signature resolving + ImmutableList.Builder builder = ImmutableList.builder(); + Map fieldAndWeightMap = new HashMap<>(); + + String[] fieldAndWeights = StringUtils.unquoteText( + ctx.getRuleContexts(OpenSearchSQLParser.AlternateMultiMatchFieldContext.class) + .stream().findFirst().get().argVal.getText()).split(","); + + for (var fieldAndWeight : fieldAndWeights) { + String[] splitFieldAndWeights = fieldAndWeight.split("\\^"); + fieldAndWeightMap.put(splitFieldAndWeights[0], + splitFieldAndWeights.length > 1 ? Float.parseFloat(splitFieldAndWeights[1]) : 1F); + } + builder.add(new UnresolvedArgument("fields", + new RelevanceFieldList(fieldAndWeightMap))); + + ctx.getRuleContexts(OpenSearchSQLParser.AlternateMultiMatchQueryContext.class) + .stream().findFirst().ifPresent( + arg -> + builder.add(new UnresolvedArgument("query", + new Literal(StringUtils.unquoteText(arg.argVal.getText()), DataType.STRING))) + ); + + fillRelevanceArgs(ctx.relevanceArg(), builder); + + return builder.build(); + } } diff --git a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java index bb5707539d..e51d0c0063 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java @@ -193,6 +193,16 @@ public void can_parse_now_like_functions(String name, Boolean hasFsp, Boolean ha @Test public void can_parse_multi_match_relevance_function() { + assertNotNull(parser.parse( + "SELECT id FROM test WHERE multimatch(\"fields\"=\"field\", query=\"query\")")); + assertNotNull(parser.parse( + "SELECT id FROM test WHERE multimatchquery(fields=\"field\", \"query\"=\"query\")")); + assertNotNull(parser.parse( + "SELECT id FROM test WHERE multi_match(\"fields\"=\"field\", \"query\"=\"query\")")); + assertNotNull(parser.parse( + "SELECT id FROM test WHERE multi_match(\'fields\'=\'field\', \'query\'=\'query\')")); + assertNotNull(parser.parse( + "SELECT id FROM test WHERE multi_match(fields=\'field\', query=\'query\')")); assertNotNull(parser.parse( "SELECT id FROM test WHERE multi_match(['address'], 'query')")); assertNotNull(parser.parse( diff --git a/sql/src/test/java/org/opensearch/sql/sql/parser/AstExpressionBuilderTest.java b/sql/src/test/java/org/opensearch/sql/sql/parser/AstExpressionBuilderTest.java index 72358986e6..85c4135b45 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/parser/AstExpressionBuilderTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/parser/AstExpressionBuilderTest.java @@ -549,9 +549,47 @@ public void relevanceMulti_match() { unresolvedArg("analyzer", stringLiteral("keyword")), unresolvedArg("operator", stringLiteral("AND"))), buildExprAst("multi_match(['field1', 'field2' ^ 3.2], 'search query'," + + "analyzer='keyword', 'operator'='AND')")); + } + + @Test + public void relevanceMultimatch_alternate_parameter_syntax() { + assertEquals(AstDSL.function("multimatch", + unresolvedArg("fields", new RelevanceFieldList(ImmutableMap.of( + "field1", 1F, "field2", 2F))), + unresolvedArg("query", stringLiteral("search query"))), + buildExprAst("multimatch(query='search query', fields=['field1^1.0,field2^2.0'])") + ); + + assertEquals(AstDSL.function("multimatch", + unresolvedArg("fields", new RelevanceFieldList(ImmutableMap.of( + "field1", 1F, "field2", 2F))), + unresolvedArg("query", stringLiteral("search query")), + unresolvedArg("analyzer", stringLiteral("keyword")), + unresolvedArg("operator", stringLiteral("AND"))), + buildExprAst("multimatch(query='search query', fields=['field1^1.0,field2^2.0']," + "analyzer='keyword', operator='AND')")); } + @Test + public void relevanceMultimatchquery_alternate_parameter_syntax() { + assertEquals(AstDSL.function("multimatchquery", + unresolvedArg("fields", new RelevanceFieldList(ImmutableMap.of( + "field", 1F))), + unresolvedArg("query", stringLiteral("search query"))), + buildExprAst("multimatchquery(query='search query', fields='field')") + ); + + assertEquals(AstDSL.function("multimatchquery", + unresolvedArg("fields", new RelevanceFieldList(ImmutableMap.of( + "field", 1F))), + unresolvedArg("query", stringLiteral("search query")), + unresolvedArg("analyzer", stringLiteral("keyword")), + unresolvedArg("operator", stringLiteral("AND"))), + buildExprAst("multimatchquery(query='search query', fields='field'," + + "analyzer='keyword', 'operator'='AND')")); + } + @Test public void relevanceSimple_query_string() { assertEquals(AstDSL.function("simple_query_string",