Skip to content

Commit 554105b

Browse files
committed
New implementation for Syntax option #1
Signed-off-by: forestmvey <[email protected]>
1 parent 2d6736f commit 554105b

File tree

4 files changed

+63
-119
lines changed

4 files changed

+63
-119
lines changed

core/src/main/java/org/opensearch/sql/analysis/Analyzer.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -220,36 +220,13 @@ public LogicalPlan visitLimit(Limit node, AnalysisContext context) {
220220
public LogicalPlan visitFilter(Filter node, AnalysisContext context) {
221221
LogicalPlan child = node.getChild().get(0).accept(this, context);
222222
Expression condition = expressionAnalyzer.analyze(node.getCondition(), context);
223-
verifySupportsCondition(condition);
224223

225224
ExpressionReferenceOptimizer optimizer =
226225
new ExpressionReferenceOptimizer(expressionAnalyzer.getRepository(), child);
227226
Expression optimized = optimizer.optimize(condition, context);
228227
return new LogicalFilter(child, optimized);
229228
}
230229

231-
/**
232-
* Ensure NESTED function is not used in WHERE, GROUP BY, and HAVING clauses.
233-
* Fallback to legacy engine. Can remove when support is added for NESTED function in WHERE,
234-
* GROUP BY, ORDER BY, and HAVING clauses.
235-
* @param condition : Filter condition
236-
*/
237-
private void verifySupportsCondition(Expression condition) {
238-
if (condition instanceof FunctionExpression) {
239-
if (((FunctionExpression) condition).getFunctionName().getFunctionName().equalsIgnoreCase(
240-
BuiltinFunctionName.NESTED.name()
241-
)) {
242-
throw new SyntaxCheckException(
243-
"Falling back to legacy engine. Nested function is not supported in WHERE,"
244-
+ " GROUP BY, and HAVING clauses."
245-
);
246-
}
247-
((FunctionExpression)condition).getArguments().stream()
248-
.forEach(e -> verifySupportsCondition(e)
249-
);
250-
}
251-
}
252-
253230
/**
254231
* Build {@link LogicalRename}.
255232
*/
@@ -300,7 +277,6 @@ public LogicalPlan visitAggregation(Aggregation node, AnalysisContext context) {
300277

301278
for (UnresolvedExpression expr : node.getGroupExprList()) {
302279
NamedExpression resolvedExpr = namedExpressionAnalyzer.analyze(expr, context);
303-
verifySupportsCondition(resolvedExpr.getDelegated());
304280
groupbyBuilder.add(resolvedExpr);
305281
}
306282
ImmutableList<NamedExpression> groupBys = groupbyBuilder.build();

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.opensearch.sql.expression.Expression;
2626
import org.opensearch.sql.expression.ExpressionNodeVisitor;
2727
import org.opensearch.sql.expression.FunctionExpression;
28+
import org.opensearch.sql.expression.LiteralExpression;
2829
import org.opensearch.sql.expression.ReferenceExpression;
2930
import org.opensearch.sql.expression.function.BuiltinFunctionName;
3031
import org.opensearch.sql.expression.function.FunctionName;
@@ -123,6 +124,10 @@ public QueryBuilder visitFunction(FunctionExpression func, Object context) {
123124

124125
// example: WHERE nested(foo.bar, nested(zoo.blah, condition))
125126

127+
if (func.getArguments().size() == 1) { // Syntax: nested(field | field, path) OPERATOR LITERAL
128+
LuceneQuery query = luceneQueries.get(name);
129+
return query.build(func);
130+
}
126131
if (func.getArguments().size() > 1) {
127132
Expression secondArgument = func.getArguments().get(1);
128133
if (secondArgument instanceof FunctionExpression) {
@@ -140,11 +145,43 @@ public QueryBuilder visitFunction(FunctionExpression func, Object context) {
140145
if (query != null && query.canSupport(func)) {
141146
return query.build(func);
142147
}
148+
if (query != null && query.isNestedFunction(func)) {
149+
QueryBuilder outerQuery = query.buildNested(func);
150+
boolean hasPathParam = (((FunctionExpression)func.getArguments().get(0)).getArguments().size() == 2);
151+
String pathStr = !hasPathParam ?
152+
getNestedPathString((ReferenceExpression) ((FunctionExpression)func.getArguments().get(0)).getArguments().get(0)) :
153+
((FunctionExpression)func.getArguments().get(0)).getArguments().get(0).toString();
154+
NestedQuery innerQuery = (NestedQuery) luceneQueries.get(((FunctionExpression)func.getArguments().get(0)).getFunctionName());
155+
return innerQuery.adInnerQuery(outerQuery, pathStr);
156+
}
157+
158+
// Nested used in predicate expression with syntax 'WHERE nested(field | field, path) = ...'
159+
if (func.getArguments().get(0) instanceof FunctionExpression
160+
&& ((FunctionExpression)func.getArguments().get(0)).getFunctionName().getFunctionName().equalsIgnoreCase(BuiltinFunctionName.NESTED.name())) {
161+
LuceneQuery innerQuery = luceneQueries.get(((FunctionExpression)func.getArguments().get(0)).getFunctionName());
162+
return innerQuery.build(func);
163+
// containsInnerNestedQuery()
164+
// innerQuery.buildPredicateExpression()
165+
} else if (query instanceof NestedQuery) {
166+
// TODO Throw exception if does not have conditional parameter.
167+
return query.build(func);
168+
}
143169
return buildScriptQuery(func);
144170
}
145171
}
146172
}
147173

174+
private boolean funcArgsIsPredicateExpression(FunctionExpression func) {
175+
func.getArguments().stream().forEach(
176+
a -> {
177+
if (a instanceof FunctionExpression) {
178+
funcArgsIsPredicateExpression((FunctionExpression) a);
179+
}
180+
}
181+
);
182+
return false;
183+
}
184+
148185
private String getNestedPathString(ReferenceExpression field) {
149186
String ret = "";
150187
for (int i = 0; i < field.getPaths().size() - 1; i++) {

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/LuceneQuery.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import com.google.common.collect.ImmutableMap;
1010

11+
import java.sql.Ref;
1112
import java.util.ArrayList;
1213
import java.util.Map;
1314
import java.util.function.BiFunction;
@@ -58,11 +59,10 @@ public boolean canSupport(FunctionExpression func) {
5859
&& (func.getArguments().get(0) instanceof ReferenceExpression)
5960
&& (func.getArguments().get(1) instanceof LiteralExpression
6061
|| literalExpressionWrappedByCast(func))
61-
|| isMultiParameterQuery(func)
62-
|| isNestedFunction(func);
62+
|| isMultiParameterQuery(func);
6363
}
6464

65-
private boolean isNestedFunction(FunctionExpression func) {
65+
public boolean isNestedFunction(FunctionExpression func) {
6666
return ((func.getArguments().get(0) instanceof FunctionExpression
6767
&& ((FunctionExpression)func.getArguments().get(0)).getFunctionName().getFunctionName().equalsIgnoreCase("nested"))
6868
|| func.getFunctionName().getFunctionName().equalsIgnoreCase("nested"));
@@ -110,6 +110,16 @@ public QueryBuilder build(FunctionExpression func) {
110110
return doBuild(ref.getAttr(), ref.type(), literalValue);
111111
}
112112

113+
public QueryBuilder buildNested(FunctionExpression func) {
114+
FunctionExpression ref = (FunctionExpression) func.getArguments().get(0);
115+
Expression expr = func.getArguments().get(1);
116+
ExprValue literalValue = expr instanceof LiteralExpression ? expr
117+
.valueOf() : cast((FunctionExpression) expr);
118+
119+
ReferenceExpression funcExpr = (ReferenceExpression) ref.getArguments().get(0);
120+
121+
return doBuild(funcExpr.getAttr(), ref.type(), literalValue);
122+
}
113123

114124
private ExprValue cast(FunctionExpression castFunction) {
115125
return castMap.get(castFunction.getFunctionName()).apply(

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/filter/lucene/NestedQuery.java

Lines changed: 13 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,7 @@ public class NestedQuery extends LuceneQuery {
2828
@Override
2929
public QueryBuilder build(FunctionExpression func) {
3030
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
31-
// WHERE nested(message, message.info = '' AND comment.data = '')
32-
if (func.getFunctionName().getFunctionName().equalsIgnoreCase(BuiltinFunctionName.NESTED.name())) {
33-
switch (((FunctionExpression)func.getArguments().get(1)).getFunctionName().getFunctionName()) {
34-
case "and":
35-
applyInnerQueryOrRecurse(((FunctionExpression)func.getArguments().get(1)), BoolQueryBuilder::must, boolQuery);
36-
break;
37-
case "or":
38-
applyInnerQueryOrRecurse(((FunctionExpression)func.getArguments().get(1)), BoolQueryBuilder::should, boolQuery);
39-
break;
40-
case "not":
41-
applyInnerQueryOrRecurse(((FunctionExpression)func.getArguments().get(1)), BoolQueryBuilder::mustNot, boolQuery);
42-
break;
43-
default:
44-
applyInnerQueryOrRecurse(((FunctionExpression)func.getArguments().get(1)), BoolQueryBuilder::filter, boolQuery);
45-
}
46-
// No recursion for operators needed
47-
} else if (func.getArguments().get(0) instanceof ReferenceExpression) { // TODO can this be handled in just the else?
31+
if (func.getArguments().get(0) instanceof ReferenceExpression) { // TODO can this be handled in just the else?
4832
applyInnerQuery(func, BoolQueryBuilder::filter, boolQuery);
4933
} else if (func.getArguments().get(0) instanceof FunctionExpression &&
5034
((FunctionExpression)func.getArguments().get(0)).getFunctionName().getFunctionName().equalsIgnoreCase("nested")) { // Is predicate expression
@@ -53,85 +37,22 @@ public QueryBuilder build(FunctionExpression func) {
5337
return boolQuery;
5438
}
5539

56-
private QueryBuilder applyInnerQueryOrRecurse(FunctionExpression func, BiFunction<BoolQueryBuilder, QueryBuilder,
57-
QueryBuilder> accumulator, BoolQueryBuilder boolQuery) {
58-
if (func.getFunctionName().getFunctionName().equalsIgnoreCase("nested")) {
59-
ReferenceExpression nestedPath = (ReferenceExpression) func.getArguments().get(0);
60-
ReferenceExpression nestedField = (ReferenceExpression) ((FunctionExpression)func.getArguments().get(1)).getArguments().get(0);
61-
ExprValue literal = ((FunctionExpression)func.getArguments().get(1)).getArguments().get(1).valueOf();
62-
String fieldName = convertTextToKeyword(nestedField.toString(), nestedField.type());
63-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(literal));
64-
NestedQueryBuilder ret = QueryBuilders.nestedQuery(nestedPath.toString(), termQuery, ScoreMode.None);
65-
return ret;
66-
} else if (func.getArguments().get(0) instanceof ReferenceExpression) {
67-
// innerNestedQuery
68-
// nested(message, message.info = 'a' AND comment.data = 'ab' OR message.dayOfWeek = 1)
69-
// nested(message, message.info = 'a' AND message.dayOfWeek = 1)
70-
ReferenceExpression field = (ReferenceExpression)func.getArguments().get(0);
71-
String fieldName = convertTextToKeyword(field.toString(), field.type());// function ret type?
72-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(func.getArguments().get(1).valueOf()));
73-
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), termQuery, ScoreMode.None);
74-
return nestedQueryBuilder;
75-
} else if (func.getArguments().get(1) instanceof LiteralExpression) { // Syntax: 'WHERE nested(message.info) = 'a'
76-
ReferenceExpression field = (ReferenceExpression)((FunctionExpression)func.getArguments().get(0)).getArguments().get(0);
77-
String fieldName = convertTextToKeyword(field.toString(), field.type());// function ret type?
78-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(func.getArguments().get(1).valueOf()));
79-
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), termQuery, ScoreMode.None);
80-
return nestedQueryBuilder;
81-
} else { // Syntax: recursion...
82-
for (Expression arg : func.getArguments()) {
83-
if (arg instanceof FunctionExpression) {
84-
switch (((FunctionExpression)arg).getFunctionName().getFunctionName()) {
85-
case "and":
86-
accumulator.apply(boolQuery, applyInnerQueryOrRecurse(((FunctionExpression)arg), BoolQueryBuilder::must, QueryBuilders.boolQuery()));
87-
break;
88-
case "or":
89-
accumulator.apply(boolQuery, applyInnerQueryOrRecurse(((FunctionExpression)arg), BoolQueryBuilder::should, QueryBuilders.boolQuery()));
90-
break;
91-
case "not":
92-
accumulator.apply(boolQuery, applyInnerQueryOrRecurse(((FunctionExpression)arg), BoolQueryBuilder::mustNot, QueryBuilders.boolQuery()));
93-
break;
94-
default:
95-
accumulator.apply(boolQuery, applyInnerQueryOrRecurse(((FunctionExpression)arg), BoolQueryBuilder::filter, QueryBuilders.boolQuery()));
96-
}
97-
} else {
98-
// Throw exception? Shouldn't get here.
99-
}
100-
}
101-
}
102-
return boolQuery;
40+
public QueryBuilder adInnerQuery(QueryBuilder builder, String path) {
41+
// BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
42+
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(path, builder, ScoreMode.None);
43+
return nestedQueryBuilder;
10344
}
10445

10546
private QueryBuilder applyInnerQuery(FunctionExpression func, BiFunction<BoolQueryBuilder, QueryBuilder,
10647
QueryBuilder> accumulator, BoolQueryBuilder boolQuery) {
107-
if (func.getFunctionName().getFunctionName().equalsIgnoreCase("nested")) { // Not sure if we need this case anymore.
108-
ReferenceExpression nestedPath = (ReferenceExpression) func.getArguments().get(0);
109-
ReferenceExpression nestedField = (ReferenceExpression) ((FunctionExpression)func.getArguments().get(1)).getArguments().get(0);
110-
ExprValue literal = ((FunctionExpression)func.getArguments().get(1)).getArguments().get(1).valueOf();
111-
String fieldName = convertTextToKeyword(nestedField.toString(), nestedField.type());
112-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(literal));
113-
NestedQueryBuilder ret = QueryBuilders.nestedQuery(nestedPath.toString(), termQuery, ScoreMode.None);
114-
return accumulator.apply(boolQuery, ret);
115-
} else if (func.getArguments().get(0) instanceof ReferenceExpression) {
116-
// innerNestedQuery
117-
// nested(message, message.info = 'a' AND comment.data = 'ab' OR message.dayOfWeek = 1)
118-
ReferenceExpression field = (ReferenceExpression)func.getArguments().get(0);
119-
String fieldName = convertTextToKeyword(field.toString(), field.type());// function ret type?
120-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(func.getArguments().get(1).valueOf()));
121-
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), termQuery, ScoreMode.None);
122-
return accumulator.apply(boolQuery, nestedQueryBuilder);
123-
} else if (func.getArguments().get(1) instanceof LiteralExpression) {
124-
// Syntax: 'WHERE nested(message.info) = 'a'
125-
// nestedFunctionAsPredicateExpression
126-
ReferenceExpression field = (ReferenceExpression)((FunctionExpression)func.getArguments().get(0)).getArguments().get(0);
127-
String fieldName = convertTextToKeyword(field.toString(), field.type());// function ret type?
128-
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(func.getArguments().get(1).valueOf()));
129-
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), termQuery, ScoreMode.None);
130-
return accumulator.apply(boolQuery, nestedQueryBuilder);
131-
} else {
132-
// Error? Shouldn't get here.
133-
return null;
134-
}
48+
// Syntax: 'WHERE nested(message.info) = 'a'
49+
// nestedFunctionAsPredicateExpression
50+
ReferenceExpression field = (ReferenceExpression)((FunctionExpression)func.getArguments().get(0)).getArguments().get(0);
51+
String fieldName = convertTextToKeyword(field.toString(), field.type());// function ret type?
52+
TermQueryBuilder termQuery = QueryBuilders.termQuery(fieldName, value(func.getArguments().get(1).valueOf()));
53+
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(getNestedPathString(field), termQuery, ScoreMode.None);
54+
return accumulator.apply(boolQuery, nestedQueryBuilder);
55+
// TODO add range query and others that may apply...
13556
}
13657

13758
private String getNestedPathString(ReferenceExpression field) {

0 commit comments

Comments
 (0)