77
88import org .opensearch .sql .ast .AbstractNodeVisitor ;
99import org .opensearch .sql .ast .Node ;
10+ import org .opensearch .sql .ast .expression .Alias ;
1011import 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 ;
1137import org .opensearch .sql .ast .tree .Project ;
1238import 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 */
2759public 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