Skip to content

Commit d2a82ae

Browse files
Pagination Phase 2: Support WHERE clause, column list in SELECT clause and for functions and expressions in the query. (#1500) (#1741)
* Add support for `WHERE` clause, column list in `SELECT` clause and for functions and expressions in the query. Signed-off-by: Yury-Fridlyand <[email protected]> * Fix merge issue and address PR feedback by updating comments. Signed-off-by: Yury-Fridlyand <[email protected]> * More comments. Signed-off-by: Yury-Fridlyand <[email protected]> * Add extra check for unset `initialSearchRequest`. Signed-off-by: Yury-Fridlyand <[email protected]> --------- Signed-off-by: Yury-Fridlyand <[email protected]> (cherry picked from commit da386e5) Co-authored-by: Yury-Fridlyand <[email protected]>
1 parent 38fc833 commit d2a82ae

File tree

14 files changed

+793
-123
lines changed

14 files changed

+793
-123
lines changed

core/src/main/java/org/opensearch/sql/ast/expression/Cast.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class Cast extends UnresolvedExpression {
6565
private final UnresolvedExpression expression;
6666

6767
/**
68-
* Expression that represents ELSE statement result.
68+
* Expression that represents name of the target type.
6969
*/
7070
private final UnresolvedExpression convertedType;
7171

core/src/main/java/org/opensearch/sql/executor/pagination/CanPaginateVisitor.java

Lines changed: 207 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,53 @@
77

88
import org.opensearch.sql.ast.AbstractNodeVisitor;
99
import org.opensearch.sql.ast.Node;
10+
import org.opensearch.sql.ast.expression.Alias;
1011
import org.opensearch.sql.ast.expression.AllFields;
12+
import org.opensearch.sql.ast.expression.And;
13+
import org.opensearch.sql.ast.expression.Argument;
14+
import org.opensearch.sql.ast.expression.Between;
15+
import org.opensearch.sql.ast.expression.Case;
16+
import org.opensearch.sql.ast.expression.Cast;
17+
import org.opensearch.sql.ast.expression.Compare;
18+
import org.opensearch.sql.ast.expression.EqualTo;
19+
import org.opensearch.sql.ast.expression.Field;
20+
import org.opensearch.sql.ast.expression.Function;
21+
import org.opensearch.sql.ast.expression.HighlightFunction;
22+
import org.opensearch.sql.ast.expression.In;
23+
import org.opensearch.sql.ast.expression.Interval;
24+
import org.opensearch.sql.ast.expression.Literal;
25+
import org.opensearch.sql.ast.expression.Not;
26+
import org.opensearch.sql.ast.expression.Or;
27+
import org.opensearch.sql.ast.expression.QualifiedName;
28+
import org.opensearch.sql.ast.expression.RelevanceFieldList;
29+
import org.opensearch.sql.ast.expression.UnresolvedArgument;
30+
import org.opensearch.sql.ast.expression.UnresolvedAttribute;
31+
import org.opensearch.sql.ast.expression.When;
32+
import org.opensearch.sql.ast.expression.WindowFunction;
33+
import org.opensearch.sql.ast.expression.Xor;
34+
import org.opensearch.sql.ast.tree.Aggregation;
35+
import org.opensearch.sql.ast.tree.Filter;
36+
import org.opensearch.sql.ast.tree.Limit;
1137
import org.opensearch.sql.ast.tree.Project;
1238
import org.opensearch.sql.ast.tree.Relation;
39+
import org.opensearch.sql.ast.tree.Sort;
40+
import org.opensearch.sql.ast.tree.Values;
41+
import org.opensearch.sql.expression.function.BuiltinFunctionName;
1342

1443
/**
1544
* Use this unresolved plan visitor to check if a plan can be serialized by PaginatedPlanCache.
16-
* If plan.accept(new CanPaginateVisitor(...)) returns true,
45+
* If <pre>plan.accept(new CanPaginateVisitor(...))</pre> returns <em>true</em>,
1746
* then PaginatedPlanCache.convertToCursor will succeed. Otherwise, it will fail.
1847
* The purpose of this visitor is to activate legacy engine fallback mechanism.
19-
* Currently, the conditions are:
20-
* - only projection of a relation is supported.
21-
* - projection only has * (a.k.a. allFields).
22-
* - Relation only scans one table
23-
* - The table is an open search index.
24-
* So it accepts only queries like `select * from $index`
48+
* Currently, V2 engine does not support queries with:
49+
* - aggregation (GROUP BY clause or aggregation functions like min/max)
50+
* - in memory aggregation (window function)
51+
* - ORDER BY clause
52+
* - LIMIT/OFFSET clause(s)
53+
* - without FROM clause
54+
* - JOIN
55+
* - a subquery
56+
* V2 also requires that the table being queried should be an OpenSearch index.
2557
* See PaginatedPlanCache.canConvertToCursor for usage.
2658
*/
2759
public class CanPaginateVisitor extends AbstractNodeVisitor<Boolean, Object> {
@@ -36,22 +68,182 @@ public Boolean visitRelation(Relation node, Object context) {
3668
return Boolean.TRUE;
3769
}
3870

71+
private Boolean canPaginate(Node node, Object context) {
72+
var childList = node.getChild();
73+
if (childList != null) {
74+
return childList.stream().allMatch(n -> n.accept(this, context));
75+
}
76+
return Boolean.TRUE;
77+
}
78+
79+
// For queries with WHERE clause:
3980
@Override
40-
public Boolean visitChildren(Node node, Object context) {
81+
public Boolean visitFilter(Filter node, Object context) {
82+
return canPaginate(node, context) && node.getCondition().accept(this, context);
83+
}
84+
85+
// Queries with GROUP BY clause are not supported
86+
@Override
87+
public Boolean visitAggregation(Aggregation node, Object context) {
4188
return Boolean.FALSE;
4289
}
4390

91+
// Queries with ORDER BY clause are not supported
4492
@Override
45-
public Boolean visitProject(Project node, Object context) {
46-
// Allow queries with 'SELECT *' only. Those restriction could be removed, but consider
47-
// in-memory aggregation performed by window function (see WindowOperator).
48-
// SELECT max(age) OVER (PARTITION BY city) ...
49-
var projections = node.getProjectList();
50-
if (projections.size() != 1) {
93+
public Boolean visitSort(Sort node, Object context) {
94+
return Boolean.FALSE;
95+
}
96+
97+
// Queries without FROM clause are not supported
98+
@Override
99+
public Boolean visitValues(Values node, Object context) {
100+
return Boolean.FALSE;
101+
}
102+
103+
// Queries with LIMIT clause are not supported
104+
@Override
105+
public Boolean visitLimit(Limit node, Object context) {
106+
return Boolean.FALSE;
107+
}
108+
109+
@Override
110+
public Boolean visitLiteral(Literal node, Object context) {
111+
return canPaginate(node, context);
112+
}
113+
114+
@Override
115+
public Boolean visitField(Field node, Object context) {
116+
return canPaginate(node, context) && node.getFieldArgs().stream()
117+
.allMatch(n -> n.accept(this, context));
118+
}
119+
120+
@Override
121+
public Boolean visitAlias(Alias node, Object context) {
122+
return canPaginate(node, context) && node.getDelegated().accept(this, context);
123+
}
124+
125+
@Override
126+
public Boolean visitAllFields(AllFields node, Object context) {
127+
return canPaginate(node, context);
128+
}
129+
130+
@Override
131+
public Boolean visitQualifiedName(QualifiedName node, Object context) {
132+
return canPaginate(node, context);
133+
}
134+
135+
@Override
136+
public Boolean visitEqualTo(EqualTo node, Object context) {
137+
return canPaginate(node, context);
138+
}
139+
140+
@Override
141+
public Boolean visitRelevanceFieldList(RelevanceFieldList node, Object context) {
142+
return canPaginate(node, context);
143+
}
144+
145+
@Override
146+
public Boolean visitInterval(Interval node, Object context) {
147+
return canPaginate(node, context);
148+
}
149+
150+
@Override
151+
public Boolean visitCompare(Compare node, Object context) {
152+
return canPaginate(node, context);
153+
}
154+
155+
@Override
156+
public Boolean visitNot(Not node, Object context) {
157+
return canPaginate(node, context);
158+
}
159+
160+
@Override
161+
public Boolean visitOr(Or node, Object context) {
162+
return canPaginate(node, context);
163+
}
164+
165+
@Override
166+
public Boolean visitAnd(And node, Object context) {
167+
return canPaginate(node, context);
168+
}
169+
170+
@Override
171+
public Boolean visitArgument(Argument node, Object context) {
172+
return canPaginate(node, context);
173+
}
174+
175+
@Override
176+
public Boolean visitXor(Xor node, Object context) {
177+
return canPaginate(node, context);
178+
}
179+
180+
@Override
181+
public Boolean visitFunction(Function node, Object context) {
182+
// https://github.com/opensearch-project/sql/issues/1718
183+
if (node.getFuncName()
184+
.equalsIgnoreCase(BuiltinFunctionName.NESTED.getName().getFunctionName())) {
51185
return Boolean.FALSE;
52186
}
187+
return canPaginate(node, context);
188+
}
189+
190+
@Override
191+
public Boolean visitIn(In node, Object context) {
192+
return canPaginate(node, context) && node.getValueList().stream()
193+
.allMatch(n -> n.accept(this, context));
194+
}
195+
196+
@Override
197+
public Boolean visitBetween(Between node, Object context) {
198+
return canPaginate(node, context);
199+
}
200+
201+
@Override
202+
public Boolean visitCase(Case node, Object context) {
203+
return canPaginate(node, context);
204+
}
205+
206+
@Override
207+
public Boolean visitWhen(When node, Object context) {
208+
return canPaginate(node, context);
209+
}
210+
211+
@Override
212+
public Boolean visitCast(Cast node, Object context) {
213+
return canPaginate(node, context) && node.getConvertedType().accept(this, context);
214+
}
215+
216+
@Override
217+
public Boolean visitHighlightFunction(HighlightFunction node, Object context) {
218+
return canPaginate(node, context);
219+
}
220+
221+
@Override
222+
public Boolean visitUnresolvedArgument(UnresolvedArgument node, Object context) {
223+
return canPaginate(node, context);
224+
}
225+
226+
@Override
227+
public Boolean visitUnresolvedAttribute(UnresolvedAttribute node, Object context) {
228+
return canPaginate(node, context);
229+
}
53230

54-
if (!(projections.get(0) instanceof AllFields)) {
231+
@Override
232+
public Boolean visitChildren(Node node, Object context) {
233+
// for all not listed (= unchecked) - false
234+
return Boolean.FALSE;
235+
}
236+
237+
@Override
238+
public Boolean visitWindowFunction(WindowFunction node, Object context) {
239+
// don't support in-memory aggregation
240+
// SELECT max(age) OVER (PARTITION BY city) ...
241+
return Boolean.FALSE;
242+
}
243+
244+
@Override
245+
public Boolean visitProject(Project node, Object context) {
246+
if (!node.getProjectList().stream().allMatch(n -> n.accept(this, context))) {
55247
return Boolean.FALSE;
56248
}
57249

0 commit comments

Comments
 (0)