Skip to content

Commit 52b8c05

Browse files
committed
Adding alternate multi_match syntax.
Signed-off-by: forestmvey <[email protected]>
1 parent 43ceda1 commit 52b8c05

File tree

10 files changed

+288
-25
lines changed

10 files changed

+288
-25
lines changed

core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,9 @@ public enum BuiltinFunctionName {
222222
QUERY(FunctionName.of("query")),
223223
MATCH_QUERY(FunctionName.of("match_query")),
224224
MATCHQUERY(FunctionName.of("matchquery")),
225-
MULTI_MATCH(FunctionName.of("multi_match"));
225+
MULTI_MATCH(FunctionName.of("multi_match")),
226+
MULTIMATCH(FunctionName.of("multimatch")),
227+
MULTIMATCHQUERY(FunctionName.of("multimatchquery"));
226228

227229
private final FunctionName name;
228230

core/src/main/java/org/opensearch/sql/expression/function/OpenSearchFunctions.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ public class OpenSearchFunctions {
2727
*/
2828
public void register(BuiltinFunctionRepository repository) {
2929
repository.register(match_bool_prefix());
30+
repository.register(multi_match(BuiltinFunctionName.MULTI_MATCH));
31+
repository.register(multi_match(BuiltinFunctionName.MULTIMATCH));
32+
repository.register(multi_match(BuiltinFunctionName.MULTIMATCHQUERY));
3033
repository.register(match(BuiltinFunctionName.MATCH));
3134
repository.register(match(BuiltinFunctionName.MATCHQUERY));
3235
repository.register(match(BuiltinFunctionName.MATCH_QUERY));
33-
repository.register(multi_match());
3436
repository.register(simple_query_string());
3537
repository.register(query());
3638
repository.register(query_string());
@@ -62,9 +64,8 @@ private static FunctionResolver match_phrase(BuiltinFunctionName matchPhrase) {
6264
return new RelevanceFunctionResolver(funcName, STRING);
6365
}
6466

65-
private static FunctionResolver multi_match() {
66-
FunctionName funcName = BuiltinFunctionName.MULTI_MATCH.getName();
67-
return new RelevanceFunctionResolver(funcName, STRUCT);
67+
private static FunctionResolver multi_match(BuiltinFunctionName multiMatchName) {
68+
return new RelevanceFunctionResolver(multiMatchName.getName(), STRUCT);
6869
}
6970

7071
private static FunctionResolver simple_query_string() {

integ-test/src/test/java/org/opensearch/sql/sql/MultiMatchIT.java

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package org.opensearch.sql.sql;
77

88
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BEER;
9+
import static org.opensearch.sql.util.MatcherUtils.rows;
10+
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
911

1012
import java.io.IOException;
1113
import org.json.JSONObject;
@@ -27,38 +29,99 @@ public void init() throws IOException {
2729
*/
2830

2931
@Test
30-
public void test_mandatory_params() throws IOException {
32+
public void test_mandatory_params() {
3133
String query = "SELECT Id FROM " + TEST_INDEX_BEER
3234
+ " WHERE multi_match([\\\"Tags\\\" ^ 1.5, Title, `Body` 4.2], 'taste')";
33-
var result = new JSONObject(executeQuery(query, "jdbc"));
35+
JSONObject result = executeJdbcRequest(query);
3436
assertEquals(16, result.getInt("total"));
3537
}
3638

3739
@Test
38-
public void test_all_params() throws IOException {
40+
public void test_all_params() {
3941
String query = "SELECT Id FROM " + TEST_INDEX_BEER
4042
+ " WHERE multi_match(['Body', Tags], 'taste beer', operator='and', analyzer=english,"
4143
+ "auto_generate_synonyms_phrase_query=true, boost = 0.77, cutoff_frequency=0.33,"
4244
+ "fuzziness = 'AUTO:1,5', fuzzy_transpositions = false, lenient = true, max_expansions = 25,"
4345
+ "minimum_should_match = '2<-25% 9<-3', prefix_length = 7, tie_breaker = 0.3,"
4446
+ "type = most_fields, slop = 2, zero_terms_query = 'ALL');";
45-
var result = new JSONObject(executeQuery(query, "jdbc"));
47+
JSONObject result = executeJdbcRequest(query);
4648
assertEquals(10, result.getInt("total"));
4749
}
4850

4951
@Test
50-
public void verify_wildcard_test() throws IOException {
52+
public void verify_wildcard_test() {
5153
String query1 = "SELECT Id FROM " + TEST_INDEX_BEER
5254
+ " WHERE multi_match(['Tags'], 'taste')";
53-
var result1 = new JSONObject(executeQuery(query1, "jdbc"));
55+
JSONObject result1 = executeJdbcRequest(query1);
5456
String query2 = "SELECT Id FROM " + TEST_INDEX_BEER
5557
+ " WHERE multi_match(['T*'], 'taste')";
56-
var result2 = new JSONObject(executeQuery(query2, "jdbc"));
58+
JSONObject result2 = executeJdbcRequest(query2);
5759
assertNotEquals(result2.getInt("total"), result1.getInt("total"));
5860

5961
String query = "SELECT Id FROM " + TEST_INDEX_BEER
6062
+ " WHERE multi_match(['*Date'], '2014-01-22');";
61-
var result = new JSONObject(executeQuery(query, "jdbc"));
63+
JSONObject result = executeJdbcRequest(query);
6264
assertEquals(10, result.getInt("total"));
6365
}
66+
67+
@Test
68+
public void test_multimatch_alternate_parameter_syntax() {
69+
String query = "SELECT Tags FROM " + TEST_INDEX_BEER
70+
+ " WHERE multimatch('query'='taste', 'fields'='Tags')";
71+
JSONObject result = executeJdbcRequest(query);
72+
assertEquals(8, result.getInt("total"));
73+
}
74+
75+
@Test
76+
public void test_multimatchquery_alternate_parameter_syntax() {
77+
String query = "SELECT Tags FROM " + TEST_INDEX_BEER
78+
+ " WHERE multimatchquery(query='cicerone', fields='Tags')";
79+
JSONObject result = executeJdbcRequest(query);
80+
assertEquals(2, result.getInt("total"));
81+
verifyDataRows(result, rows("serving cicerone restaurants"),
82+
rows("taste cicerone"));
83+
}
84+
85+
@Test
86+
public void test_quoted_multi_match_alternate_parameter_syntax() {
87+
String query = "SELECT Tags FROM " + TEST_INDEX_BEER
88+
+ " WHERE multi_match('query'='cicerone', 'fields'='Tags')";
89+
JSONObject result = executeJdbcRequest(query);
90+
assertEquals(2, result.getInt("total"));
91+
verifyDataRows(result, rows("serving cicerone restaurants"),
92+
rows("taste cicerone"));
93+
}
94+
95+
@Test
96+
public void test_multi_match_alternate_parameter_syntax() {
97+
String query = "SELECT Tags FROM " + TEST_INDEX_BEER
98+
+ " WHERE multi_match(query='cicerone', fields='Tags')";
99+
JSONObject result = executeJdbcRequest(query);
100+
assertEquals(2, result.getInt("total"));
101+
verifyDataRows(result, rows("serving cicerone restaurants"),
102+
rows("taste cicerone"));
103+
}
104+
105+
@Test
106+
public void test_wildcard_multi_match_alternate_parameter_syntax() {
107+
String query = "SELECT Body FROM " + TEST_INDEX_BEER
108+
+ " WHERE multi_match(query='IPA', fields='B*') LIMIT 1";
109+
JSONObject result = executeJdbcRequest(query);
110+
verifyDataRows(result, rows("<p>I know what makes an IPA an IPA, but what are the unique" +
111+
" characteristics of it's common variants? To be specific, the ones I'm interested in are Double IPA" +
112+
" and Black IPA, but general differences between any other styles would be welcome too. </p>\n"));
113+
}
114+
115+
@Test
116+
public void test_all_params_multimatchquery_alternate_parameter_syntax() {
117+
String query = "SELECT Id FROM " + TEST_INDEX_BEER
118+
+ " WHERE multimatchquery(query='cicerone', fields='Tags', 'operator'='or', analyzer=english,"
119+
+ "auto_generate_synonyms_phrase_query=true, boost = 0.77, cutoff_frequency=0.33,"
120+
+ "fuzziness = 'AUTO:1,5', fuzzy_transpositions = false, lenient = true, max_expansions = 25,"
121+
+ "minimum_should_match = '2<-25% 9<-3', prefix_length = 7, tie_breaker = 0.3,"
122+
+ "type = most_fields, slop = 2, zero_terms_query = 'ALL');";
123+
124+
JSONObject result = executeJdbcRequest(query);
125+
assertEquals(2, result.getInt("total"));
126+
}
64127
}

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public class FilterQueryBuilder extends ExpressionNodeVisitor<QueryBuilder, Obje
6666
.put(BuiltinFunctionName.MATCH_QUERY.getName(), new MatchQuery())
6767
.put(BuiltinFunctionName.MATCHQUERY.getName(), new MatchQuery())
6868
.put(BuiltinFunctionName.MULTI_MATCH.getName(), new MultiMatchQuery())
69+
.put(BuiltinFunctionName.MULTIMATCH.getName(), new MultiMatchQuery())
70+
.put(BuiltinFunctionName.MULTIMATCHQUERY.getName(), new MultiMatchQuery())
6971
.put(BuiltinFunctionName.SIMPLE_QUERY_STRING.getName(), new SimpleQueryStringQuery())
7072
.put(BuiltinFunctionName.QUERY_STRING.getName(), new QueryStringQuery())
7173
.put(BuiltinFunctionName.MATCH_BOOL_PREFIX.getName(), new MatchBoolPrefixQuery())

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/MultiMatchTest.java

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
3737
class MultiMatchTest {
3838
private final MultiMatchQuery multiMatchQuery = new MultiMatchQuery();
39-
private final FunctionName multiMatch = FunctionName.of("multi_match");
39+
private final FunctionName multiMatchName = FunctionName.of("multimatch");
40+
private final FunctionName snakeCaseMultiMatchName = FunctionName.of("multi_match");
41+
private final FunctionName multiMatchQueryName = FunctionName.of("multimatchquery");
4042
private static final LiteralExpression fields_value = DSL.literal(
4143
new ExprTupleValue(new LinkedHashMap<>(ImmutableMap.of(
4244
"title", ExprValueUtils.floatValue(1.F),
@@ -129,27 +131,69 @@ static Stream<List<Expression>> generateValidData() {
129131

130132
@ParameterizedTest
131133
@MethodSource("generateValidData")
132-
public void test_valid_parameters(List<Expression> validArgs) {
134+
public void test_valid_parameters_multiMatch(List<Expression> validArgs) {
133135
Assertions.assertNotNull(multiMatchQuery.build(
134136
new MultiMatchExpression(validArgs)));
135137
}
136138

139+
@ParameterizedTest
140+
@MethodSource("generateValidData")
141+
public void test_valid_parameters_multi_match(List<Expression> validArgs) {
142+
Assertions.assertNotNull(multiMatchQuery.build(
143+
new MultiMatchExpression(validArgs, snakeCaseMultiMatchName)));
144+
}
145+
146+
@ParameterizedTest
147+
@MethodSource("generateValidData")
148+
public void test_valid_parameters_multiMatchQuery(List<Expression> validArgs) {
149+
Assertions.assertNotNull(multiMatchQuery.build(
150+
new MultiMatchExpression(validArgs, multiMatchQueryName)));
151+
}
152+
137153
@Test
138-
public void test_SyntaxCheckException_when_no_arguments() {
154+
public void test_SyntaxCheckException_when_no_arguments_multiMatch() {
139155
List<Expression> arguments = List.of();
140156
assertThrows(SyntaxCheckException.class,
141157
() -> multiMatchQuery.build(new MultiMatchExpression(arguments)));
142158
}
143159

144160
@Test
145-
public void test_SyntaxCheckException_when_one_argument() {
161+
public void test_SyntaxCheckException_when_no_arguments_multi_match() {
162+
List<Expression> arguments = List.of();
163+
assertThrows(SyntaxCheckException.class,
164+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchName)));
165+
}
166+
167+
@Test
168+
public void test_SyntaxCheckException_when_no_arguments_multiMatchQuery() {
169+
List<Expression> arguments = List.of();
170+
assertThrows(SyntaxCheckException.class,
171+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName)));
172+
}
173+
174+
@Test
175+
public void test_SyntaxCheckException_when_one_argument_multiMatch() {
146176
List<Expression> arguments = List.of(namedArgument("fields", fields_value));
147177
assertThrows(SyntaxCheckException.class,
148178
() -> multiMatchQuery.build(new MultiMatchExpression(arguments)));
149179
}
150180

151181
@Test
152-
public void test_SemanticCheckException_when_invalid_parameter() {
182+
public void test_SyntaxCheckException_when_one_argument_multi_match() {
183+
List<Expression> arguments = List.of(namedArgument("fields", fields_value));
184+
assertThrows(SyntaxCheckException.class,
185+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, snakeCaseMultiMatchName)));
186+
}
187+
188+
@Test
189+
public void test_SyntaxCheckException_when_one_argument_multiMatchQuery() {
190+
List<Expression> arguments = List.of(namedArgument("fields", fields_value));
191+
assertThrows(SyntaxCheckException.class,
192+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName)));
193+
}
194+
195+
@Test
196+
public void test_SemanticCheckException_when_invalid_parameter_multiMatch() {
153197
List<Expression> arguments = List.of(
154198
namedArgument("fields", fields_value),
155199
namedArgument("query", query_value),
@@ -158,15 +202,40 @@ public void test_SemanticCheckException_when_invalid_parameter() {
158202
() -> multiMatchQuery.build(new MultiMatchExpression(arguments)));
159203
}
160204

205+
@Test
206+
public void test_SemanticCheckException_when_invalid_parameter_multi_match() {
207+
List<Expression> arguments = List.of(
208+
namedArgument("fields", fields_value),
209+
namedArgument("query", query_value),
210+
DSL.namedArgument("unsupported", "unsupported_value"));
211+
Assertions.assertThrows(SemanticCheckException.class,
212+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, snakeCaseMultiMatchName)));
213+
}
214+
215+
@Test
216+
public void test_SemanticCheckException_when_invalid_parameter_multiMatchQuery() {
217+
List<Expression> arguments = List.of(
218+
namedArgument("fields", fields_value),
219+
namedArgument("query", query_value),
220+
DSL.namedArgument("unsupported", "unsupported_value"));
221+
Assertions.assertThrows(SemanticCheckException.class,
222+
() -> multiMatchQuery.build(new MultiMatchExpression(arguments, multiMatchQueryName)));
223+
}
224+
161225
private NamedArgumentExpression namedArgument(String name, LiteralExpression value) {
162226
return DSL.namedArgument(name, value);
163227
}
164228

165229
private class MultiMatchExpression extends FunctionExpression {
166230
public MultiMatchExpression(List<Expression> arguments) {
167-
super(MultiMatchTest.this.multiMatch, arguments);
231+
super(multiMatchName, arguments);
168232
}
169233

234+
public MultiMatchExpression(List<Expression> arguments, FunctionName funcName) {
235+
super(funcName, arguments);
236+
}
237+
238+
170239
@Override
171240
public ExprValue valueOf(Environment<Expression, ExprValue> valueEnv) {
172241
throw new UnsupportedOperationException("Invalid function call, "

sql/src/main/antlr/OpenSearchSQLLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ MINUTE_OF_HOUR: 'MINUTE_OF_HOUR';
306306
MONTH_OF_YEAR: 'MONTH_OF_YEAR';
307307
MULTIMATCH: 'MULTIMATCH';
308308
MULTI_MATCH: 'MULTI_MATCH';
309+
MULTIMATCHQUERY: 'MULTIMATCHQUERY';
309310
NESTED: 'NESTED';
310311
PERCENTILES: 'PERCENTILES';
311312
REGEXP_QUERY: 'REGEXP_QUERY';

sql/src/main/antlr/OpenSearchSQLParser.g4

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ multiFieldRelevanceFunction
355355
: multiFieldRelevanceFunctionName LR_BRACKET
356356
LT_SQR_PRTHS field=relevanceFieldAndWeight (COMMA field=relevanceFieldAndWeight)* RT_SQR_PRTHS
357357
COMMA query=relevanceQuery (COMMA relevanceArg)* RR_BRACKET
358+
| multiFieldRelevanceFunctionName LR_BRACKET
359+
alternateMultiMatchQuery COMMA alternateMultiMatchField (COMMA relevanceArg)* RR_BRACKET
358360
;
359361

360362
convertedDataType
@@ -467,6 +469,8 @@ singleFieldRelevanceFunctionName
467469

468470
multiFieldRelevanceFunctionName
469471
: MULTI_MATCH
472+
| MULTIMATCH
473+
| MULTIMATCHQUERY
470474
| SIMPLE_QUERY_STRING
471475
| QUERY_STRING
472476
;
@@ -481,6 +485,7 @@ functionArg
481485

482486
relevanceArg
483487
: relevanceArgName EQUAL_SYMBOL relevanceArgValue
488+
| argName=stringLiteral EQUAL_SYMBOL argVal=relevanceArgValue
484489
;
485490

486491
highlightArg
@@ -530,3 +535,18 @@ highlightArgValue
530535
: stringLiteral
531536
;
532537

538+
alternateMultiMatchArgName
539+
: FIELDS
540+
| QUERY
541+
| stringLiteral
542+
;
543+
544+
alternateMultiMatchQuery
545+
: argName=alternateMultiMatchArgName EQUAL_SYMBOL argVal=relevanceArgValue
546+
;
547+
548+
alternateMultiMatchField
549+
: argName=alternateMultiMatchArgName EQUAL_SYMBOL argVal=relevanceArgValue
550+
| argName=alternateMultiMatchArgName EQUAL_SYMBOL
551+
LT_SQR_PRTHS argVal=relevanceArgValue RT_SQR_PRTHS
552+
;

0 commit comments

Comments
 (0)