Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support For Legacy Syntax For Match Function In New Engine #1090

Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public class OpenSearchFunctions {
*/
public void register(BuiltinFunctionRepository repository) {
repository.register(match_bool_prefix());
repository.register(match());
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());
Expand All @@ -44,8 +46,8 @@ private static FunctionResolver match_bool_prefix() {
return new RelevanceFunctionResolver(name, STRING);
}

private static FunctionResolver match() {
FunctionName funcName = BuiltinFunctionName.MATCH.getName();
private static FunctionResolver match(BuiltinFunctionName match) {
FunctionName funcName = match.getName();
return new RelevanceFunctionResolver(funcName, STRING);
}

Expand Down
93 changes: 93 additions & 0 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2730,6 +2730,99 @@ Another example to show how to set custom values for the optional parameters::
+------------+


MATCHQUERY
dai-chen marked this conversation as resolved.
Show resolved Hide resolved
-----

Description
>>>>>>>>>>>

``matchquery(field_expression, query_expression[, option=<option_value>]*)``

The matchquery function maps to the match query used in search engine, to return the documents that match a provided text, number, date or boolean value with a given field. This is alternate syntax for the `match`_ function. Available parameters include:

- analyzer
- auto_generate_synonyms_phrase
- fuzziness
- max_expansions
- prefix_length
- fuzzy_transpositions
- fuzzy_rewrite
- lenient
- operator
- minimum_should_match
- zero_terms_query
- boost

For backwards compatibility, matchquery is supported and mapped to the match query.

Example with only ``field`` and ``query`` expressions, and all other parameters are set default values::

os> SELECT lastname, address FROM accounts WHERE matchquery(address, 'Street');
fetched rows / total rows = 2/2
+------------+--------------------+
| lastname | address |
|------------+--------------------|
| Bond | 671 Bristol Street |
| Bates | 789 Madison Street |
+------------+--------------------+

Another example to show how to set custom values for the optional parameters::

os> SELECT lastname FROM accounts WHERE matchquery(firstname, 'Hattie', operator='AND', boost=2.0);
fetched rows / total rows = 1/1
+------------+
| lastname |
|------------|
| Bond |
+------------+

MATCH_QUERY
-----

Description
>>>>>>>>>>>

``match_query(field_expression, query_expression[, option=<option_value>]*)``

The match_query function maps to the match query used in search engine, to return the documents that match_query a provided text, number, date or boolean value with a given field. This is alternate syntax for the `match`_ function. Available parameters include:

- analyzer
- auto_generate_synonyms_phrase
- fuzziness
- max_expansions
- prefix_length
- fuzzy_transpositions
- fuzzy_rewrite
- lenient
- operator
- minimum_should_match
- zero_terms_query
- boost

For backwards compatibility, match_query is supported and mapped to the match query.

Example with only ``field`` and ``query`` expressions, and all other parameters are set default values::

os> SELECT lastname, address FROM accounts WHERE match_query(address, 'Street');
fetched rows / total rows = 2/2
+------------+--------------------+
| lastname | address |
|------------+--------------------|
| Bond | 671 Bristol Street |
| Bates | 789 Madison Street |
+------------+--------------------+

Another example to show how to set custom values for the optional parameters::

os> SELECT lastname FROM accounts WHERE match_query(firstname, 'Hattie', operator='AND', boost=2.0);
fetched rows / total rows = 1/1
+------------+
| lastname |
|------------|
| Bond |
+------------+


MATCH_PHRASE
------------

Expand Down
44 changes: 44 additions & 0 deletions integ-test/src/test/java/org/opensearch/sql/sql/MatchIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,48 @@ public void match_in_having() throws IOException {
verifySchema(result, schema("lastname", "text"));
verifyDataRows(result, rows("Bates"));
}

@Test
public void matchquery_in_where() throws IOException {
JSONObject result = executeJdbcRequest("SELECT firstname FROM " + TEST_INDEX_ACCOUNT + " WHERE matchquery(lastname, 'Bates')");
verifySchema(result, schema("firstname", "text"));
verifyDataRows(result, rows("Nanette"));
}

@Test
public void matchquery_in_having() throws IOException {
JSONObject result = executeJdbcRequest("SELECT lastname FROM " + TEST_INDEX_ACCOUNT + " HAVING matchquery(firstname, 'Nanette')");
verifySchema(result, schema("lastname", "text"));
verifyDataRows(result, rows("Bates"));
}

@Test
public void match_query_in_where() throws IOException {
JSONObject result = executeJdbcRequest("SELECT firstname FROM " + TEST_INDEX_ACCOUNT + " WHERE match_query(lastname, 'Bates')");
verifySchema(result, schema("firstname", "text"));
verifyDataRows(result, rows("Nanette"));
}

@Test
public void match_query_in_having() throws IOException {
JSONObject result = executeJdbcRequest(
"SELECT lastname FROM " + TEST_INDEX_ACCOUNT + " HAVING match_query(firstname, 'Nanette')");
verifySchema(result, schema("lastname", "text"));
verifyDataRows(result, rows("Bates"));
}

@Test
public void alternate_syntaxes_return_the_same_results() throws IOException {
String query1 = "SELECT lastname FROM "
+ TEST_INDEX_ACCOUNT + " HAVING match(firstname, 'Nanette')";
JSONObject result1 = executeJdbcRequest(query1);
String query2 = "SELECT lastname FROM "
+ TEST_INDEX_ACCOUNT + " HAVING matchquery(firstname, 'Nanette')";
JSONObject result2 = executeJdbcRequest(query2);
String query3 = "SELECT lastname FROM "
+ TEST_INDEX_ACCOUNT + " HAVING match_query(firstname, 'Nanette')";
JSONObject result3 = executeJdbcRequest(query3);
assertEquals(result1.getInt("total"), result2.getInt("total"));
assertEquals(result1.getInt("total"), result3.getInt("total"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
public class MatchQueryTest {
private final DSL dsl = new ExpressionConfig().dsl(new ExpressionConfig().functionRepository());
private final MatchQuery matchQuery = new MatchQuery();
private final FunctionName match = FunctionName.of("match");
private final FunctionName matchName = FunctionName.of("match");
private final FunctionName matchQueryName = FunctionName.of("matchquery");
private final FunctionName matchQueryWithUnderscoreName = FunctionName.of("match_query");
private final FunctionName[] functionNames =
{matchName,matchQueryName, matchQueryWithUnderscoreName};

static Stream<List<Expression>> generateValidData() {
final DSL dsl = new ExpressionConfig().dsl(new ExpressionConfig().functionRepository());
Expand Down Expand Up @@ -139,13 +143,87 @@ public void test_SemanticCheckException_when_invalid_parameter() {
() -> matchQuery.build(new MatchExpression(arguments)));
}

@ParameterizedTest
@MethodSource("generateValidData")
public void test_valid_parameters_matchquery_syntax(List<Expression> validArgs) {
Assertions.assertNotNull(matchQuery.build(
new MatchExpression(validArgs, MatchQueryTest.this.matchQueryName)));
}

@Test
public void test_SyntaxCheckException_when_no_arguments_matchquery_syntax() {
List<Expression> arguments = List.of();
assertThrows(SyntaxCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryName)));
}

@Test
public void test_SyntaxCheckException_when_one_argument_matchquery_syntax() {
List<Expression> arguments = List.of(namedArgument("field", "field_value"));
assertThrows(SyntaxCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryName)));
}

@Test
public void test_SemanticCheckException_when_invalid_parameter_matchquery_syntax() {
List<Expression> arguments = List.of(
namedArgument("field", "field_value"),
namedArgument("query", "query_value"),
namedArgument("unsupported", "unsupported_value"));
Assertions.assertThrows(SemanticCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryName)));
}

@ParameterizedTest
@MethodSource("generateValidData")
public void test_valid_parameters_match_query_syntax(List<Expression> validArgs) {
Assertions.assertNotNull(matchQuery.build(
new MatchExpression(validArgs, MatchQueryTest.this.matchQueryWithUnderscoreName)));
}

@Test
public void test_SyntaxCheckException_when_no_arguments_match_query_syntax() {
List<Expression> arguments = List.of();
assertThrows(SyntaxCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryWithUnderscoreName)));
}

@Test
public void test_SyntaxCheckException_when_one_argument_match_query_syntax() {
List<Expression> arguments = List.of(namedArgument("field", "field_value"));
assertThrows(SyntaxCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryWithUnderscoreName)));
}

@Test
public void test_SemanticCheckException_when_invalid_parameter_match_query_syntax() {
List<Expression> arguments = List.of(
namedArgument("field", "field_value"),
namedArgument("query", "query_value"),
namedArgument("unsupported", "unsupported_value"));
Assertions.assertThrows(SemanticCheckException.class,
() -> matchQuery.build(
new MatchExpression(arguments, MatchQueryTest.this.matchQueryWithUnderscoreName)));
}


private NamedArgumentExpression namedArgument(String name, String value) {
return dsl.namedArgument(name, DSL.literal(value));
}

private class MatchExpression extends FunctionExpression {

public MatchExpression(List<Expression> arguments) {
super(MatchQueryTest.this.match, arguments);
super(MatchQueryTest.this.matchName, arguments);
}

public MatchExpression(List<Expression> arguments, FunctionName funcName) {
super(funcName, arguments);
}

@Override
Expand Down
7 changes: 2 additions & 5 deletions sql/src/main/antlr/OpenSearchSQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ systemFunctionName
;

singleFieldRelevanceFunctionName
: MATCH | MATCH_PHRASE | MATCHPHRASE
: MATCH | MATCHQUERY | MATCH_QUERY
| MATCH_PHRASE | MATCHPHRASE
| MATCH_BOOL_PREFIX | MATCH_PHRASE_PREFIX
;

Expand All @@ -435,10 +436,6 @@ multiFieldRelevanceFunctionName
| QUERY_STRING
;

legacyRelevanceFunctionName
: QUERY | MATCH_QUERY | MATCHQUERY
;

functionArgs
: (functionArg (COMMA functionArg)*)?
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,31 @@ public void can_parse_match_relevance_function() {
assertNotNull(parser.parse("SELECT * FROM test WHERE match(`column`, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match(`column`, 'this is a test')"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match(column, 100500)"));
}

@Test
public void can_parse_matchquery_relevance_function() {
assertNotNull(parser.parse("SELECT * FROM test WHERE matchquery(column, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE matchquery(column, 'this is a test')"));
assertNotNull(parser.parse(
"SELECT * FROM test WHERE matchquery(`column`, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE matchquery(`column`, 'this is a test')"));
assertNotNull(parser.parse("SELECT * FROM test WHERE matchquery(column, 100500)"));
}

@Test
public void can_parse_match_query_relevance_function() {
assertNotNull(parser.parse(
"SELECT * FROM test WHERE match_query(column, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match_query(column, 'this is a test')"));
assertNotNull(parser.parse(
"SELECT * FROM test WHERE match_query(`column`, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match_query(`column`, 'this is a test')"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match_query(column, 100500)"));
}

@Test
public void can_parse_match_phrase_relevance_function() {
assertNotNull(
parser.parse("SELECT * FROM test WHERE match_phrase(column, \"this is a test\")"));
assertNotNull(parser.parse("SELECT * FROM test WHERE match_phrase(column, 'this is a test')"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,38 @@ public void relevanceMatch() {
buildExprAst("match('message', 'search query', analyzer='keyword', operator='AND')"));
}

@Test
public void relevanceMatchQuery() {
assertEquals(AstDSL.function("matchquery",
unresolvedArg("field", stringLiteral("message")),
unresolvedArg("query", stringLiteral("search query"))),
buildExprAst("matchquery('message', 'search query')")
);

assertEquals(AstDSL.function("matchquery",
unresolvedArg("field", stringLiteral("message")),
unresolvedArg("query", stringLiteral("search query")),
unresolvedArg("analyzer", stringLiteral("keyword")),
unresolvedArg("operator", stringLiteral("AND"))),
buildExprAst("matchquery('message', 'search query', analyzer='keyword', operator='AND')"));
}

@Test
public void relevanceMatch_Query() {
assertEquals(AstDSL.function("match_query",
unresolvedArg("field", stringLiteral("message")),
unresolvedArg("query", stringLiteral("search query"))),
buildExprAst("match_query('message', 'search query')")
);

assertEquals(AstDSL.function("match_query",
unresolvedArg("field", stringLiteral("message")),
unresolvedArg("query", stringLiteral("search query")),
unresolvedArg("analyzer", stringLiteral("keyword")),
unresolvedArg("operator", stringLiteral("AND"))),
buildExprAst("match_query('message', 'search query', analyzer='keyword', operator='AND')"));
}

@Test
public void relevanceMulti_match() {
assertEquals(AstDSL.function("multi_match",
Expand Down