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 934d6d7396..9b37afa2d1 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,7 +14,9 @@ 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.NestedQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.ScriptQueryBuilder; @@ -23,6 +25,7 @@ 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; @@ -99,20 +102,42 @@ public QueryBuilder visitFunction(FunctionExpression func, Object context) { return buildBoolQuery(func, context, BoolQueryBuilder::should); case "not": return buildBoolQuery(func, context, BoolQueryBuilder::mustNot); + case "nested": + // nested (field, condition) + // example: WHERE message.info = 'a' OR message.info = 'b' + // => bool { must { bool { should { term(message.info, 'a') , term(message.info, 'b') } } } + + // example: WHERE nested(message.info) = 'a' OR nested(message.info) = 'b' + // => bool { must { bool { should { term( nested { term { message.info} } }, 'a') , term( nested { term { message.info} } }, 'b') } } } + // => bool { must { bool { nested { bool { must { term( message.info, 'a') , term( message.info, 'b') } } } + + // example: WHERE nested(message.info, message.info = 'a' OR message.info = 'b' ...) + // => bool { must { bool { nested { bool { must { term( message.info, 'a') , term( message.info, 'b') } } } + + // => bool { must { nested { bool { should { term() , term() } } } } + // example: WHERE nested(message.info, message.age > 20 OR message.info = 'b' ...) + // example: WHERE nested(message.info.name, message.info.name = 'a') OR nested(message.info.address, message.info.address = 'b') + + // WHERE nested(message, message.info.name, message.info.name = "Andrew" OR message.comment = "SECOND") + // WHERE (nested(message.info.name) = "Andrew" AND nested(message.comment) = "FIRST") OR (nested(message.info.name) = "Guian" AND nested(message.comment = "SECOND")) + + // example: WHERE nested(foo.bar, nested(zoo.blah, condition)) + + if (func.getArguments().size() > 1) { + Expression secondArgument = func.getArguments().get(1); + if (secondArgument instanceof FunctionExpression) { + ReferenceExpression field = (ReferenceExpression)func.getArguments().get(0); + FunctionExpression condition = (FunctionExpression)func.getArguments().get(1); + QueryBuilder queryBuilder = visitFunction(condition, context); + NestedQueryBuilder + nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), queryBuilder, ScoreMode.None); + return nestedQueryBuilder; + } + } + // else, this doesn't have a condition so we need to just call nested without a queryBuilder default: { LuceneQuery query = luceneQueries.get(name); - // Nested used in predicate expression with syntax 'WHERE nested(field | field, path) = ...' - if (func.getArguments().get(0) instanceof FunctionExpression - && ((FunctionExpression)func.getArguments().get(0)).getFunctionName().getFunctionName().equalsIgnoreCase(BuiltinFunctionName.NESTED.name())) { - LuceneQuery innerQuery = luceneQueries.get(((FunctionExpression)func.getArguments().get(0)).getFunctionName()); - return innerQuery.build(func); - // containsInnerNestedQuery() - // innerQuery.buildPredicateExpression() - } else if (query instanceof NestedQuery) { - // TODO Throw exception if does not have conditional parameter. - return query.build(func); - } - if (query != null && query.canSupport(func)) { + if (query != null && query.canSupport(func)) { return query.build(func); } return buildScriptQuery(func); @@ -120,6 +145,14 @@ public QueryBuilder visitFunction(FunctionExpression func, Object context) { } } + private String getNestedPathString(ReferenceExpression field) { + String ret = ""; + for (int i = 0; i < field.getPaths().size() - 1; i++) { + ret += (i == 0) ? field.getPaths().get(i) : "." + field.getPaths().get(i); + } + return ret; + } + private BoolQueryBuilder buildBoolQuery(FunctionExpression node, Object context, BiFunction