From 0c0eb1a2d7ada2b98020ce230ddbd614b9aabecf Mon Sep 17 00:00:00 2001 From: forestmvey Date: Thu, 25 May 2023 08:17:35 -0700 Subject: [PATCH 1/2] Adding support for nested function used in predicate expression. Signed-off-by: forestmvey --- .../org/opensearch/sql/analysis/Analyzer.java | 4 +- .../opensearch/sql/analysis/AnalyzerTest.java | 23 ---- docs/user/dql/functions.rst | 11 ++ .../java/org/opensearch/sql/sql/NestedIT.java | 67 ++++++++-- .../request/OpenSearchRequestBuilder.java | 68 ++++++++-- .../script/filter/FilterQueryBuilder.java | 16 +++ .../script/filter/lucene/LuceneQuery.java | 14 +- .../script/filter/lucene/NestedQuery.java | 89 +++++++++++++ .../request/OpenSearchRequestBuilderTest.java | 39 ++++++ .../script/filter/FilterQueryBuilderTest.java | 126 ++++++++++++++++++ .../opensearch/sql/sql/parser/AstBuilder.java | 23 ++++ .../sql/sql/parser/AstBuilderTest.java | 15 +++ 12 files changed, 447 insertions(+), 48 deletions(-) create mode 100644 opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/NestedQuery.java diff --git a/core/src/main/java/org/opensearch/sql/analysis/Analyzer.java b/core/src/main/java/org/opensearch/sql/analysis/Analyzer.java index 29c0e4050a..de7d72fd0d 100644 --- a/core/src/main/java/org/opensearch/sql/analysis/Analyzer.java +++ b/core/src/main/java/org/opensearch/sql/analysis/Analyzer.java @@ -63,7 +63,6 @@ import org.opensearch.sql.common.antlr.SyntaxCheckException; import org.opensearch.sql.data.model.ExprMissingValue; import org.opensearch.sql.data.type.ExprCoreType; -import org.opensearch.sql.data.type.ExprType; import org.opensearch.sql.datasource.DataSourceService; import org.opensearch.sql.exception.SemanticCheckException; import org.opensearch.sql.expression.DSL; @@ -220,7 +219,6 @@ public LogicalPlan visitLimit(Limit node, AnalysisContext context) { public LogicalPlan visitFilter(Filter node, AnalysisContext context) { LogicalPlan child = node.getChild().get(0).accept(this, context); Expression condition = expressionAnalyzer.analyze(node.getCondition(), context); - verifySupportsCondition(condition); ExpressionReferenceOptimizer optimizer = new ExpressionReferenceOptimizer(expressionAnalyzer.getRepository(), child); @@ -229,7 +227,7 @@ public LogicalPlan visitFilter(Filter node, AnalysisContext context) { } /** - * Ensure NESTED function is not used in WHERE, GROUP BY, and HAVING clauses. + * Ensure NESTED function is not used in GROUP BY, and HAVING clauses. * Fallback to legacy engine. Can remove when support is added for NESTED function in WHERE, * GROUP BY, ORDER BY, and HAVING clauses. * @param condition : Filter condition diff --git a/core/src/test/java/org/opensearch/sql/analysis/AnalyzerTest.java b/core/src/test/java/org/opensearch/sql/analysis/AnalyzerTest.java index 5a2b37c017..9cabaccb0e 100644 --- a/core/src/test/java/org/opensearch/sql/analysis/AnalyzerTest.java +++ b/core/src/test/java/org/opensearch/sql/analysis/AnalyzerTest.java @@ -1041,29 +1041,6 @@ public void nested_group_by_clause_throws_syntax_exception() { exception.getMessage()); } - /** - * Ensure Nested function falls back to legacy engine when used in WHERE clause. - * TODO Remove this test when support is added. - */ - @Test - public void nested_where_clause_throws_syntax_exception() { - SyntaxCheckException exception = assertThrows(SyntaxCheckException.class, - () -> analyze( - AstDSL.filter( - AstDSL.relation("schema"), - AstDSL.equalTo( - AstDSL.function("nested", qualifiedName("message", "info")), - AstDSL.stringLiteral("str") - ) - ) - ) - ); - assertEquals("Falling back to legacy engine. Nested function is not supported in WHERE," - + " GROUP BY, and HAVING clauses.", - exception.getMessage()); - } - - /** * SELECT name, AVG(age) FROM test GROUP BY name. */ diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 19b724d7d3..2a6e70a085 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -4456,6 +4456,17 @@ Example with ``field`` and ``path`` parameters:: +---------------------------------+ +Example with ``field`` and ``path`` parameters in the SELECT and WHERE clause:: + + os> SELECT nested(message.info, message) FROM nested WHERE nested(message.info, message) = 'b'; + fetched rows / total rows = 1/1 + +---------------------------------+ + | nested(message.info, message) | + |---------------------------------| + | b | + +---------------------------------+ + + System Functions ================ diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java index 0076b30fa3..6dc26f80f1 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java @@ -170,18 +170,6 @@ public void nested_function_mixed_with_non_nested_type_test() { rows("zz", "a")); } - @Test - public void nested_function_with_where_clause() { - String query = - "SELECT nested(message.info) FROM " + TEST_INDEX_NESTED_TYPE + " WHERE nested(message.info) = 'a'"; - JSONObject result = executeJdbcRequest(query); - - assertEquals(2, result.getInt("total")); - verifyDataRows(result, - rows("a"), - rows("a")); - } - @Test public void nested_function_with_order_by_clause() { String query = @@ -313,4 +301,59 @@ public void nested_missing_path_argument() { "}" )); } + + @Test + public void test_nested_where_with_and_conditional() { + String query = "SELECT nested(message.info), nested(message.author) FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(message, message.info = 'a' AND message.author = 'e')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(1, result.getInt("total")); + verifyDataRows(result, rows("a", "e")); + } + + @Test + public void test_nested_in_select_and_where_as_predicate_expression() { + String query = "SELECT nested(message.info) FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(message.info) = 'a'"; + JSONObject result = executeJdbcRequest(query); + assertEquals(3, result.getInt("total")); + verifyDataRows( + result, + rows("a"), + rows("c"), + rows("a") + ); + } + + @Test + public void test_nested_in_where_as_predicate_expression() { + String query = "SELECT message.info FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(message.info) = 'a'"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + // Only first index of array is returned. Second index has 'a' + verifyDataRows(result, rows("a"), rows("c")); + } + + @Test + public void test_nested_in_where_as_predicate_expression_with_multiple_conditions() { + String query = "SELECT message.info, comment.data, message.dayOfWeek FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(message.info) = 'zz' OR nested(comment.data) = 'ab' AND nested(message.dayOfWeek) >= 4"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + verifyDataRows( + result, + rows("c", "ab", 4), + rows("zz", "aa", 6) + ); + } + + @Test + public void test_nested_in_where_as_predicate_expression_with_relevance_query() { + String query = "SELECT comment.likes, someField FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(comment.likes) = 10 AND match(someField, 'a')"; + JSONObject result = executeJdbcRequest(query); + assertEquals(1, result.getInt("total")); + verifyDataRows(result, rows(10, "a")); + } } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilder.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilder.java index 9f1b588af9..6f0618d961 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilder.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilder.java @@ -8,17 +8,20 @@ import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; -import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.matchAllQuery; import static org.opensearch.index.query.QueryBuilders.nestedQuery; import static org.opensearch.search.sort.FieldSortBuilder.DOC_FIELD_NAME; import static org.opensearch.search.sort.SortOrder.ASC; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -44,7 +47,6 @@ import org.opensearch.sql.opensearch.data.type.OpenSearchDataType; import org.opensearch.sql.opensearch.data.value.OpenSearchExprValueFactory; import org.opensearch.sql.opensearch.response.agg.OpenSearchAggregationResponseParser; -import org.opensearch.sql.planner.logical.LogicalNested; /** * OpenSearch search request builder. @@ -257,13 +259,33 @@ private boolean isSortByDocOnly() { */ public void pushDownNested(List> nestedArgs) { initBoolQueryFilter(); + List nestedQueries = extractNestedQueries(query()); groupFieldNamesByPath(nestedArgs).forEach( - (path, fieldNames) -> buildInnerHit( - fieldNames, createEmptyNestedQuery(path) - ) + (path, fieldNames) -> + buildInnerHit(fieldNames, findNestedQueryWithSamePath(nestedQueries, path)) ); } + /** + * InnerHit must be added to the NestedQueryBuilder. We need to extract + * the nested queries currently in the query if there is already a filter + * push down with nested query. + * @param query : current query. + * @return : grouped nested queries currently in query. + */ + private List extractNestedQueries(QueryBuilder query) { + List result = new ArrayList<>(); + if (query instanceof NestedQueryBuilder) { + result.add((NestedQueryBuilder) query); + } else if (query instanceof BoolQueryBuilder) { + BoolQueryBuilder boolQ = (BoolQueryBuilder) query; + Stream.of(boolQ.filter(), boolQ.must(), boolQ.should()) + .flatMap(Collection::stream) + .forEach(q -> result.addAll(extractNestedQueries(q))); + } + return result; + } + /** * Initialize bool query for push down. */ @@ -307,13 +329,41 @@ private void buildInnerHit(List paths, NestedQueryBuilder query) { )); } + /** + * We need to group nested queries with same path for adding new fields with same path of + * inner hits. If we try to add additional inner hits with same path we get an OS error. + * @param nestedQueries Current list of nested queries in query. + * @param path path comparing with current nested queries. + * @return Query with same path or new empty nested query. + */ + private NestedQueryBuilder findNestedQueryWithSamePath( + List nestedQueries, String path + ) { + return nestedQueries.stream() + .filter(query -> isSamePath(path, query)) + .findAny() + .orElseGet(createEmptyNestedQuery(path)); + } + + /** + * Check if is nested query is of the same path value. + * @param path Value of path to compare with nested query. + * @param query nested query builder to compare with path. + * @return true if nested query has same path. + */ + private boolean isSamePath(String path, NestedQueryBuilder query) { + return nestedQuery(path, query.query(), query.scoreMode()).equals(query); + } + /** * Create a nested query with match all filter to place inner hits. */ - private NestedQueryBuilder createEmptyNestedQuery(String path) { - NestedQueryBuilder nestedQuery = nestedQuery(path, matchAllQuery(), ScoreMode.None); - ((BoolQueryBuilder) query().filter().get(0)).must(nestedQuery); - return nestedQuery; + private Supplier createEmptyNestedQuery(String path) { + return () -> { + NestedQueryBuilder nestedQuery = nestedQuery(path, matchAllQuery(), ScoreMode.None); + ((BoolQueryBuilder) query().filter().get(0)).must(nestedQuery); + return nestedQuery; + }; } /** 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 5f36954d4a..560bd52da9 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 @@ -14,18 +14,23 @@ import java.util.Map; import java.util.function.BiFunction; import lombok.RequiredArgsConstructor; +import org.apache.lucene.search.join.ScoreMode; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.ScriptQueryBuilder; import org.opensearch.script.Script; +import org.opensearch.sql.ast.expression.Function; +import org.opensearch.sql.common.antlr.SyntaxCheckException; import org.opensearch.sql.expression.Expression; import org.opensearch.sql.expression.ExpressionNodeVisitor; import org.opensearch.sql.expression.FunctionExpression; +import org.opensearch.sql.expression.ReferenceExpression; import org.opensearch.sql.expression.function.BuiltinFunctionName; import org.opensearch.sql.expression.function.FunctionName; import org.opensearch.sql.opensearch.storage.script.filter.lucene.LikeQuery; import org.opensearch.sql.opensearch.storage.script.filter.lucene.LuceneQuery; +import org.opensearch.sql.opensearch.storage.script.filter.lucene.NestedQuery; import org.opensearch.sql.opensearch.storage.script.filter.lucene.RangeQuery; import org.opensearch.sql.opensearch.storage.script.filter.lucene.RangeQuery.Comparison; import org.opensearch.sql.opensearch.storage.script.filter.lucene.TermQuery; @@ -75,6 +80,7 @@ public class FilterQueryBuilder extends ExpressionNodeVisitor 2) { + throw new IllegalArgumentException( + "nested function supports 2 parameters (field, path) or 1 parameter (field)" + ); + } + + for (var arg : nestedFunc.getArguments()) { + if (!(arg instanceof ReferenceExpression)) { + throw new IllegalArgumentException( + String.format("Illegal nested field name: %s", + arg.toString() + ) + ); + } + } + + if (!(rightExpression instanceof LiteralExpression)) { + throw new IllegalArgumentException( + String.format("Illegal argument on right side of predicate expression: %s", + rightExpression.toString() + ) + ); + } + } +} diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilderTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilderTest.java index 187f319d44..4685609f58 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilderTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/request/OpenSearchRequestBuilderTest.java @@ -324,6 +324,45 @@ void testPushDownNestedWithFilter() { requestBuilder.getSourceBuilder()); } + @Test + void testPushDownNestedWithNestedFilter() { + List> args = List.of( + Map.of( + "field", new ReferenceExpression("message.info", STRING), + "path", new ReferenceExpression("message", STRING) + ) + ); + + List projectList = + List.of( + new NamedExpression("message.info", DSL.nested(DSL.ref("message.info", STRING)), null) + ); + + QueryBuilder innerFilterQuery = QueryBuilders.rangeQuery("myNum").gt(3); + QueryBuilder filterQuery = + QueryBuilders.nestedQuery("message", innerFilterQuery, ScoreMode.None); + LogicalNested nested = new LogicalNested(null, args, projectList); + requestBuilder.getSourceBuilder().query(filterQuery); + requestBuilder.pushDownNested(nested.getFields()); + + NestedQueryBuilder nestedQuery = nestedQuery("message", matchAllQuery(), ScoreMode.None) + .innerHit(new InnerHitBuilder().setFetchSourceContext( + new FetchSourceContext(true, new String[]{"message.info"}, null))); + + assertEquals( + new SearchSourceBuilder() + .query( + QueryBuilders.boolQuery().filter( + QueryBuilders.boolQuery() + .must(filterQuery) + ) + ) + .from(DEFAULT_OFFSET) + .size(DEFAULT_LIMIT) + .timeout(DEFAULT_QUERY_TIMEOUT), + requestBuilder.getSourceBuilder()); + } + @Test void testPushTypeMapping() { Map typeMapping = Map.of("intA", OpenSearchDataType.of(INTEGER)); diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilderTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilderTest.java index 96245909a4..eb07076257 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilderTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/storage/script/filter/FilterQueryBuilderTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.sql.common.antlr.SyntaxCheckException; import org.opensearch.sql.common.utils.StringUtils; import org.opensearch.sql.data.model.ExprDateValue; import org.opensearch.sql.data.model.ExprDatetimeValue; @@ -299,6 +300,131 @@ void should_use_keyword_for_multi_field_in_like_expression() { literal("John%")))); } + @Test + void should_build_term_query_predicate_expression_with_nested_function() { + assertJsonEquals( + "{\n" + + " \"nested\" : {\n" + + " \"query\" : {\n" + + " \"term\" : {\n" + + " \"message.info\" : {\n" + + " \"value\" : \"string_value\",\n" + + " \"boost\" : 1.0\n" + + " }\n" + + " }\n" + + " },\n" + + " \"path\" : \"message\",\n" + + " \"ignore_unmapped\" : false,\n" + + " \"score_mode\" : \"none\",\n" + + " \"boost\" : 1.0\n" + + " }\n" + + "}", + buildQuery( + DSL.equal(DSL.nested( + DSL.ref("message.info", STRING), + DSL.ref("message", STRING)), + literal("string_value") + ) + ) + ); + } + + @Test + void should_build_range_query_predicate_expression_with_nested_function() { + assertJsonEquals( + "{\n" + + " \"nested\" : {\n" + + " \"query\" : {\n" + + " \"range\" : {\n" + + " \"lottery.number.id\" : {\n" + + " \"from\" : 1234,\n" + + " \"to\" : null,\n" + + " \"include_lower\" : false,\n" + + " \"include_upper\" : true,\n" + + " \"boost\" : 1.0\n" + + " }\n" + + " }\n" + + " },\n" + + " \"path\" : \"lottery.number\",\n" + + " \"ignore_unmapped\" : false,\n" + + " \"score_mode\" : \"none\",\n" + + " \"boost\" : 1.0\n" + + " }\n" + + "}", + buildQuery( + DSL.greater(DSL.nested( + DSL.ref("lottery.number.id", INTEGER)), literal(1234) + ) + ) + ); + } + + // TODO remove this test when alternate syntax is implemented for nested + // function in WHERE clause: nested(path, condition) + @Test + void ensure_alternate_syntax_falls_back_to_legacy_engine() { + assertThrows(SyntaxCheckException.class, () -> + buildQuery( + DSL.nested( + DSL.ref("message", STRING), + DSL.equal(DSL.literal("message.info"), literal("a")) + ) + ) + ); + } + + @Test + void nested_filter_wrong_right_side_type_in_predicate_throws_exception() { + assertThrows(IllegalArgumentException.class, () -> + buildQuery( + DSL.equal(DSL.nested( + DSL.ref("message.info", STRING), + DSL.ref("message", STRING)), + DSL.ref("string_value", STRING) + ) + ) + ); + } + + @Test + void nested_filter_wrong_first_param_type_throws_exception() { + assertThrows(IllegalArgumentException.class, () -> + buildQuery( + DSL.equal(DSL.nested( + DSL.namedArgument("field", literal("message"))), + literal("string_value") + ) + ) + ); + } + + @Test + void nested_filter_wrong_second_param_type_throws_exception() { + assertThrows(IllegalArgumentException.class, () -> + buildQuery( + DSL.equal(DSL.nested( + DSL.ref("message.info", STRING), + DSL.literal(2)), + literal("string_value") + ) + ) + ); + } + + @Test + void nested_filter_too_many_params_throws_exception() { + assertThrows(IllegalArgumentException.class, () -> + buildQuery( + DSL.equal(DSL.nested( + DSL.ref("message.info", STRING), + DSL.ref("message", STRING), + DSL.ref("message", STRING)), + literal("string_value") + ) + ) + ); + } + @Test void should_build_match_query_with_default_parameters() { assertJsonEquals( diff --git a/sql/src/main/java/org/opensearch/sql/sql/parser/AstBuilder.java b/sql/src/main/java/org/opensearch/sql/sql/parser/AstBuilder.java index 02a7f7d1b9..020889c082 100644 --- a/sql/src/main/java/org/opensearch/sql/sql/parser/AstBuilder.java +++ b/sql/src/main/java/org/opensearch/sql/sql/parser/AstBuilder.java @@ -37,6 +37,7 @@ import org.opensearch.sql.ast.tree.Values; import org.opensearch.sql.common.antlr.SyntaxCheckException; 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.QuerySpecificationContext; import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParserBaseVisitor; @@ -151,6 +152,8 @@ public UnresolvedPlan visitFromClause(FromClauseContext ctx) { } if (ctx.havingClause() != null) { + UnresolvedPlan havingPlan = visit(ctx.havingClause()); + verifySupportsCondition(((Filter) havingPlan).getCondition()); result = visit(ctx.havingClause()).attach(result); } @@ -161,6 +164,26 @@ public UnresolvedPlan visitFromClause(FromClauseContext ctx) { return result; } + /** + * Ensure NESTED function is not used in HAVING clause and fallback to legacy engine. + * Can remove when support is added for NESTED function in HAVING clause. + * @param func : Function in HAVING clause + */ + private void verifySupportsCondition(UnresolvedExpression func) { + if (func instanceof Function) { + if (((Function) func).getFuncName().equalsIgnoreCase( + BuiltinFunctionName.NESTED.name() + )) { + throw new SyntaxCheckException( + "Falling back to legacy engine. Nested function is not supported in the HAVING clause." + ); + } + ((Function)func).getFuncArgs().stream() + .forEach(e -> verifySupportsCondition(e) + ); + } + } + @Override public UnresolvedPlan visitTableAsRelation(TableAsRelationContext ctx) { String tableAlias = (ctx.alias() == null) ? null diff --git a/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java b/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java index ec135748f9..0c69909334 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java @@ -477,6 +477,21 @@ public void nested_in_order_by_clause_throws_exception() { exception.getMessage()); } + /** + * Ensure Nested function falls back to legacy engine when used in an HAVING clause. + * TODO Remove this test when support is added. + */ + @Test + public void nested_in_having_clause_throws_exception() { + SyntaxCheckException exception = assertThrows(SyntaxCheckException.class, + () -> buildAST("SELECT count(*) FROM test HAVING nested(message.info)") + ); + + assertEquals( + "Falling back to legacy engine. Nested function is not supported in the HAVING clause.", + exception.getMessage()); + } + @Test public void can_build_order_by_sort_order_keyword_insensitive() { assertEquals( From eb48a0a90348a763b552dbe31278f261fba6deca Mon Sep 17 00:00:00 2001 From: forestmvey Date: Thu, 25 May 2023 10:21:40 -0700 Subject: [PATCH 2/2] Add support for LIKE with nested query in predicate expression. Signed-off-by: forestmvey --- .../test/java/org/opensearch/sql/sql/NestedIT.java | 10 ++++++++++ .../storage/script/filter/lucene/LikeQuery.java | 13 ++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java index 6dc26f80f1..6cb7b7580b 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/NestedIT.java @@ -335,6 +335,16 @@ public void test_nested_in_where_as_predicate_expression() { verifyDataRows(result, rows("a"), rows("c")); } + @Test + public void test_nested_in_where_as_predicate_expression_with_like() { + String query = "SELECT message.info FROM " + TEST_INDEX_NESTED_TYPE + + " WHERE nested(message.info) LIKE 'a'"; + JSONObject result = executeJdbcRequest(query); + assertEquals(2, result.getInt("total")); + // Only first index of array is returned. Second index has 'a' + verifyDataRows(result, rows("a"), rows("c")); + } + @Test public void test_nested_in_where_as_predicate_expression_with_multiple_conditions() { String query = "SELECT message.info, comment.data, message.dayOfWeek FROM " + TEST_INDEX_NESTED_TYPE diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/LikeQuery.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/LikeQuery.java index ebb1cd58f0..699af4f3fd 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/LikeQuery.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/LikeQuery.java @@ -9,20 +9,15 @@ import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.WildcardQueryBuilder; import org.opensearch.sql.data.model.ExprValue; -import org.opensearch.sql.expression.Expression; -import org.opensearch.sql.expression.FunctionExpression; -import org.opensearch.sql.expression.ReferenceExpression; +import org.opensearch.sql.data.type.ExprType; import org.opensearch.sql.opensearch.data.type.OpenSearchTextType; import org.opensearch.sql.opensearch.storage.script.StringUtils; public class LikeQuery extends LuceneQuery { @Override - public QueryBuilder build(FunctionExpression func) { - ReferenceExpression ref = (ReferenceExpression) func.getArguments().get(0); - String field = OpenSearchTextType.convertTextToKeyword(ref.getAttr(), ref.type()); - Expression expr = func.getArguments().get(1); - ExprValue literalValue = expr.valueOf(); - return createBuilder(field, literalValue.stringValue()); + public QueryBuilder doBuild(String fieldName, ExprType fieldType, ExprValue literal) { + String field = OpenSearchTextType.convertTextToKeyword(fieldName, fieldType); + return createBuilder(field, literal.stringValue()); } /**