Skip to content

Commit 52ea1e5

Browse files
Added Query Function As Alternate Syntax For Query String Function
Signed-off-by: GabeFernandez310 <gabrielf@bitquilltech.com>
1 parent 89b3e1a commit 52ea1e5

18 files changed

Lines changed: 727 additions & 25 deletions

File tree

core/src/main/java/org/opensearch/sql/expression/DSL.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,10 @@ public FunctionExpression simple_query_string(Expression... args) {
707707
return compile(BuiltinFunctionName.SIMPLE_QUERY_STRING, args);
708708
}
709709

710+
public FunctionExpression query(Expression... args) {
711+
return compile(BuiltinFunctionName.QUERY, args);
712+
}
713+
710714
public FunctionExpression query_string(Expression... args) {
711715
return compile(BuiltinFunctionName.QUERY_STRING, args);
712716
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public void register(BuiltinFunctionRepository repository) {
3030
repository.register(match());
3131
repository.register(multi_match());
3232
repository.register(simple_query_string());
33+
repository.register(query());
3334
repository.register(query_string());
3435
// Register MATCHPHRASE as MATCH_PHRASE as well for backwards
3536
// compatibility.
@@ -68,6 +69,11 @@ private static FunctionResolver simple_query_string() {
6869
return new RelevanceFunctionResolver(funcName, STRUCT);
6970
}
7071

72+
private static FunctionResolver query() {
73+
FunctionName funcName = BuiltinFunctionName.QUERY.getName();
74+
return new RelevanceFunctionResolver(funcName, STRING);
75+
}
76+
7177
private static FunctionResolver query_string() {
7278
FunctionName funcName = BuiltinFunctionName.QUERY_STRING.getName();
7379
return new RelevanceFunctionResolver(funcName, STRUCT);

core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,15 @@ void simple_query_string_expression_two_fields() {
487487
AstDSL.unresolvedArg("query", stringLiteral("sample query"))));
488488
}
489489

490+
@Test
491+
void query_expression() {
492+
assertAnalyzeEqual(
493+
dsl.query(
494+
dsl.namedArgument("query", DSL.literal("field:query"))),
495+
AstDSL.function("query",
496+
AstDSL.unresolvedArg("query", stringLiteral("field:query"))));
497+
}
498+
490499
@Test
491500
void query_string_expression() {
492501
assertAnalyzeEqual(

core/src/test/java/org/opensearch/sql/expression/function/OpenSearchFunctionsTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ void simple_query_string() {
183183
expr.toString());
184184
}
185185

186+
@Test
187+
void query() {
188+
FunctionExpression expr = dsl.query(query);
189+
assertEquals(String.format("query(query=%s)", query.getValue()),
190+
expr.toString());
191+
}
192+
186193
@Test
187194
void query_string() {
188195
FunctionExpression expr = dsl.query_string(fields, query);

docs/user/dql/functions.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3017,6 +3017,67 @@ Another example to show how to set custom values for the optional parameters::
30173017
+------+--------------------------+----------------------+
30183018

30193019

3020+
QUERY
3021+
-----
3022+
3023+
Description
3024+
>>>>>>>>>>>
3025+
3026+
``query("query_expression" [, option=<option_value>]*)``
3027+
3028+
The `query` function is an alternative syntax to the `query_string`_ function. It maps to the query_string query used in search engine, to return the documents that match a provided text, number, date or boolean value with a given query expression.
3029+
``query_expression`` must be a string provided in Lucene query string syntax. Please refer to examples below:
3030+
3031+
| ``query('Tags:taste OR Body:taste', ...)``
3032+
| ``query("Tags:taste AND Body:taste", ...)``
3033+
3034+
Available parameters include:
3035+
3036+
- analyzer
3037+
- escape
3038+
- allow_leading_wildcard
3039+
- analyze_wildcard
3040+
- auto_generate_synonyms_phrase_query
3041+
- boost
3042+
- default_operator
3043+
- enable_position_increments
3044+
- fuzziness
3045+
- fuzzy_max_expansions
3046+
- fuzzy_prefix_length
3047+
- fuzzy_transpositions
3048+
- fuzzy_rewrite
3049+
- tie_breaker
3050+
- lenient
3051+
- type
3052+
- max_determinized_states
3053+
- minimum_should_match
3054+
- quote_analyzer
3055+
- phrase_slop
3056+
- quote_field_suffix
3057+
- rewrite
3058+
- time_zone
3059+
3060+
Example with only ``query_expressions``, and all other parameters are set default values::
3061+
3062+
os> select * from books where query('title:Pooh House');
3063+
fetched rows / total rows = 2/2
3064+
+------+--------------------------+----------------------+
3065+
| id | title | author |
3066+
|------+--------------------------+----------------------|
3067+
| 1 | The House at Pooh Corner | Alan Alexander Milne |
3068+
| 2 | Winnie-the-Pooh | Alan Alexander Milne |
3069+
+------+--------------------------+----------------------+
3070+
3071+
Another example to show how to set custom values for the optional parameters::
3072+
3073+
os> select * from books where query('title:Pooh House', default_operator='AND');
3074+
fetched rows / total rows = 1/1
3075+
+------+--------------------------+----------------------+
3076+
| id | title | author |
3077+
|------+--------------------------+----------------------|
3078+
| 1 | The House at Pooh Corner | Alan Alexander Milne |
3079+
+------+--------------------------+----------------------+
3080+
30203081
HIGHLIGHT
30213082
------------
30223083

integ-test/src/test/java/org/opensearch/sql/legacy/MethodQueryIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void queryTest() throws IOException {
3838
"select address from %s where query('address:880 Holmes Lane') limit 3",
3939
TestsConstants.TEST_INDEX_ACCOUNT));
4040
Assert.assertThat(result,
41-
containsString("query_string\":{\"query\":\"address:880 Holmes Lane"));
41+
containsString("query_string\\\":{\\\"query\\\":\\\"address:880 Holmes Lane"));
4242

4343
}
4444

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.sql;
7+
8+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BEER;
9+
10+
import java.io.IOException;
11+
import org.json.JSONObject;
12+
import org.junit.Test;
13+
import org.opensearch.sql.legacy.SQLIntegTestCase;
14+
15+
public class QueryIT extends SQLIntegTestCase {
16+
@Override
17+
public void init() throws IOException {
18+
loadIndex(Index.BEER);
19+
}
20+
21+
@Test
22+
public void all_fields_test() throws IOException {
23+
String query = "SELECT * FROM "
24+
+ TEST_INDEX_BEER + " WHERE query('*:taste')";
25+
JSONObject result = executeJdbcRequest(query);
26+
assertEquals(16, result.getInt("total"));
27+
}
28+
29+
@Test
30+
public void mandatory_params_test() throws IOException {
31+
String query = "SELECT Id FROM "
32+
+ TEST_INDEX_BEER + " WHERE query('Tags:taste OR Body:taste')";
33+
JSONObject result = executeJdbcRequest(query);
34+
assertEquals(16, result.getInt("total"));
35+
}
36+
37+
@Test
38+
public void all_params_test() throws IOException {
39+
String query = "SELECT Id FROM " + TEST_INDEX_BEER
40+
+ " WHERE query('Tags:taste', escape=false,"
41+
+ "allow_leading_wildcard=true, enable_position_increments=true,"
42+
+ "fuzziness= 1, fuzzy_rewrite='constant_score', max_determinized_states = 10000,"
43+
+ "analyzer='standard', analyze_wildcard = false, quote_field_suffix = '.exact',"
44+
+ "auto_generate_synonyms_phrase_query=true, boost = 0.77,"
45+
+ "quote_analyzer='standard', phrase_slop=0, rewrite='constant_score', type='best_fields',"
46+
+ "tie_breaker=0.3, time_zone='Canada/Pacific', default_operator='or',"
47+
+ "fuzzy_transpositions = false, lenient = true, fuzzy_max_expansions = 25,"
48+
+ "minimum_should_match = '2<-25% 9<-3', fuzzy_prefix_length = 7);";
49+
JSONObject result = executeJdbcRequest(query);
50+
assertEquals(8, result.getInt("total"));
51+
}
52+
53+
@Test
54+
public void wildcard_test() throws IOException {
55+
String query1 = "SELECT Id FROM "
56+
+ TEST_INDEX_BEER + " WHERE query('Tags:taste')";
57+
JSONObject result1 = executeJdbcRequest(query1);
58+
String query2 = "SELECT Id FROM "
59+
+ TEST_INDEX_BEER + " WHERE query('*:taste')";
60+
JSONObject result2 = executeJdbcRequest(query2);
61+
assertNotEquals(result2.getInt("total"), result1.getInt("total"));
62+
63+
String query3 = "SELECT Id FROM " + TEST_INDEX_BEER
64+
+ " WHERE query('Tags:tas*');";
65+
JSONObject result3 = executeJdbcRequest(query3);
66+
assertEquals(8, result3.getInt("total"));
67+
68+
String query4 = "SELECT Id FROM " + TEST_INDEX_BEER
69+
+ " WHERE query('Tags:tas?e');";
70+
JSONObject result4 = executeJdbcRequest(query3);
71+
assertEquals(8, result4.getInt("total"));
72+
}
73+
74+
@Test
75+
public void query_string_and_query_return_the_same_results_test() throws IOException {
76+
String query1 = "SELECT Id FROM "
77+
+ TEST_INDEX_BEER + " WHERE query('Tags:taste')";
78+
JSONObject result1 = executeJdbcRequest(query1);
79+
String query2 = "SELECT Id FROM "
80+
+ TEST_INDEX_BEER + " WHERE query_string(['Tags'],'taste')";
81+
JSONObject result2 = executeJdbcRequest(query2);
82+
assertEquals(result2.getInt("total"), result1.getInt("total"));
83+
}
84+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.MatchPhraseQuery;
3535
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.MatchQuery;
3636
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.MultiMatchQuery;
37+
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.QueryQuery;
3738
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.QueryStringQuery;
3839
import org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance.SimpleQueryStringQuery;
3940
import org.opensearch.sql.opensearch.storage.serialization.ExpressionSerializer;
@@ -60,7 +61,7 @@ public class FilterQueryBuilder extends ExpressionNodeVisitor<QueryBuilder, Obje
6061
.put(BuiltinFunctionName.MATCH.getName(), new MatchQuery())
6162
.put(BuiltinFunctionName.MATCH_PHRASE.getName(), new MatchPhraseQuery())
6263
.put(BuiltinFunctionName.MATCHPHRASE.getName(), new MatchPhraseQuery())
63-
.put(BuiltinFunctionName.QUERY.getName(), new MatchQuery())
64+
.put(BuiltinFunctionName.QUERY.getName(), new QueryQuery())
6465
.put(BuiltinFunctionName.MATCH_QUERY.getName(), new MatchQuery())
6566
.put(BuiltinFunctionName.MATCHQUERY.getName(), new MatchQuery())
6667
.put(BuiltinFunctionName.MULTI_MATCH.getName(), new MultiMatchQuery())
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Objects;
11+
import java.util.stream.Collectors;
12+
import org.opensearch.index.query.QueryBuilder;
13+
import org.opensearch.sql.common.antlr.SyntaxCheckException;
14+
import org.opensearch.sql.exception.SemanticCheckException;
15+
import org.opensearch.sql.expression.FunctionExpression;
16+
import org.opensearch.sql.expression.NamedArgumentExpression;
17+
18+
/**
19+
* Base class to represent relevance queries that search multiple fields.
20+
*
21+
* @param <T> The builder class for the OpenSearch query.
22+
*/
23+
abstract class NoFieldQuery<T extends QueryBuilder> extends RelevanceQuery<T> {
24+
public NoFieldQuery(Map<String, QueryBuilderStep<T>> queryBuildActions) {
25+
super(queryBuildActions);
26+
}
27+
28+
@Override
29+
protected void ignoreArguments(List<NamedArgumentExpression> arguments) {
30+
arguments.removeIf(a -> a.getArgName().equalsIgnoreCase("query"));
31+
}
32+
33+
@Override
34+
protected void checkValidArguments(String argNormalized, T queryBuilder) {
35+
if (!getQueryBuildActions().containsKey(argNormalized)) {
36+
throw new SemanticCheckException(
37+
String.format("Parameter %s is invalid for %s function.",
38+
argNormalized, getQueryName()));
39+
}
40+
}
41+
/**
42+
* Override build function because RelevanceQuery requires 2 fields,
43+
* but NoFieldQuery must have no fields.
44+
*
45+
* @param func : Contains function name and passed in arguments.
46+
* @return : QueryBuilder object
47+
*/
48+
49+
@Override
50+
public QueryBuilder build(FunctionExpression func) {
51+
var arguments = func.getArguments().stream().map(
52+
a -> (NamedArgumentExpression) a).collect(Collectors.toList());
53+
if (arguments.size() < 1) {
54+
throw new SyntaxCheckException(String.format(
55+
"%s requires at least one parameter", func.getFunctionName()));
56+
}
57+
58+
return loadArguments(arguments);
59+
}
60+
61+
62+
@Override
63+
public T createQueryBuilder(List<NamedArgumentExpression> arguments) {
64+
// Extract 'query'
65+
var query = arguments.stream().filter(a -> a.getArgName().equalsIgnoreCase("query")).findFirst()
66+
.orElseThrow(() -> new SemanticCheckException("'query' parameter is missing"));
67+
68+
return createBuilder(query.getValue().valueOf(null).stringValue());
69+
}
70+
71+
protected abstract T createBuilder(String query);
72+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.opensearch.storage.script.filter.lucene.relevance;
7+
8+
import org.opensearch.index.query.QueryBuilders;
9+
import org.opensearch.index.query.QueryStringQueryBuilder;
10+
11+
/**
12+
* Class for Lucene query that builds the query_string query.
13+
*/
14+
public class QueryQuery extends NoFieldQuery<QueryStringQueryBuilder> {
15+
16+
final String queryQueryName = "query";
17+
18+
/**
19+
* Default constructor for QueryQuery configures how RelevanceQuery.build() handles
20+
* named arguments by calling the constructor of QueryStringQuery.
21+
*/
22+
public QueryQuery() {
23+
super(FunctionParameterRepository.QueryStringQueryBuildActions);
24+
}
25+
26+
/**
27+
* Builds QueryBuilder with query value and other default parameter values set.
28+
*
29+
* @param query : Query value for query_string query
30+
* @return : Builder for query query
31+
*/
32+
protected QueryStringQueryBuilder createBuilder(String query) {
33+
return QueryBuilders.queryStringQuery(query);
34+
}
35+
36+
@Override
37+
public String getQueryName() {
38+
return queryQueryName;
39+
}
40+
}

0 commit comments

Comments
 (0)