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 ;
1341
1442/**
1543 * Use this unresolved plan visitor to check if a plan can be serialized by PaginatedPlanCache.
16- * If plan.accept(new CanPaginateVisitor(...)) returns true,
44+ * If <pre> plan.accept(new CanPaginateVisitor(...))</pre> returns <em> true</em> ,
1745 * then PaginatedPlanCache.convertToCursor will succeed. Otherwise, it will fail.
1846 * 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`
47+ * Currently, V2 engine does not support queries with:
48+ * - aggregation (GROUP BY clause or aggregation functions like min/max)
49+ * - in memory aggregation (window function)
50+ * - ORDER BY clause
51+ * - LIMIT/OFFSET clause(s)
52+ * - without FROM clause
53+ * - JOIN
54+ * - a subquery
55+ * V2 also requires that the table being queried should be an OpenSearch index.
2556 * See PaginatedPlanCache.canConvertToCursor for usage.
2657 */
2758public class CanPaginateVisitor extends AbstractNodeVisitor <Boolean , Object > {
@@ -36,22 +67,176 @@ public Boolean visitRelation(Relation node, Object context) {
3667 return Boolean .TRUE ;
3768 }
3869
70+ private Boolean canPaginate (Node node , Object context ) {
71+ var childList = node .getChild ();
72+ if (childList != null ) {
73+ return childList .stream ().allMatch (n -> n .accept (this , context ));
74+ }
75+ return Boolean .TRUE ;
76+ }
77+
78+ //For queries with WHERE clause:
79+ @ Override
80+ public Boolean visitFilter (Filter node , Object context ) {
81+ return canPaginate (node , context ) && node .getCondition ().accept (this , context );
82+ }
83+
84+ // Queries with GROUP BY clause are not supported
85+ @ Override
86+ public Boolean visitAggregation (Aggregation node , Object context ) {
87+ return Boolean .FALSE ;
88+ }
89+
90+ // Queries with ORDER BY clause are not supported
91+ @ Override
92+ public Boolean visitSort (Sort node , Object context ) {
93+ return Boolean .FALSE ;
94+ }
95+
96+ // Queries without FROM clause are not supported
97+ @ Override
98+ public Boolean visitValues (Values node , Object context ) {
99+ return Boolean .FALSE ;
100+ }
101+
102+ @ Override
103+ public Boolean visitLimit (Limit node , Object context ) {
104+ return super .visitLimit (node , context );
105+ }
106+
107+ @ Override
108+ public Boolean visitLiteral (Literal node , Object context ) {
109+ return canPaginate (node , context );
110+ }
111+
112+ @ Override
113+ public Boolean visitField (Field node , Object context ) {
114+ return canPaginate (node , context ) && node .getFieldArgs ().stream ()
115+ .allMatch (n -> n .accept (this , context ));
116+ }
117+
118+ @ Override
119+ public Boolean visitAlias (Alias node , Object context ) {
120+ return canPaginate (node , context ) && node .getDelegated ().accept (this , context );
121+ }
122+
123+ @ Override
124+ public Boolean visitAllFields (AllFields node , Object context ) {
125+ return canPaginate (node , context );
126+ }
127+
128+ @ Override
129+ public Boolean visitQualifiedName (QualifiedName node , Object context ) {
130+ return canPaginate (node , context );
131+ }
132+
133+ @ Override
134+ public Boolean visitEqualTo (EqualTo node , Object context ) {
135+ return canPaginate (node , context );
136+ }
137+
138+ @ Override
139+ public Boolean visitRelevanceFieldList (RelevanceFieldList node , Object context ) {
140+ return canPaginate (node , context );
141+ }
142+
143+ @ Override
144+ public Boolean visitInterval (Interval node , Object context ) {
145+ return canPaginate (node , context );
146+ }
147+
148+ @ Override
149+ public Boolean visitCompare (Compare node , Object context ) {
150+ return canPaginate (node , context );
151+ }
152+
153+ @ Override
154+ public Boolean visitNot (Not node , Object context ) {
155+ return canPaginate (node , context );
156+ }
157+
158+ @ Override
159+ public Boolean visitOr (Or node , Object context ) {
160+ return canPaginate (node , context );
161+ }
162+
163+ @ Override
164+ public Boolean visitAnd (And node , Object context ) {
165+ return canPaginate (node , context );
166+ }
167+
168+ @ Override
169+ public Boolean visitArgument (Argument node , Object context ) {
170+ return canPaginate (node , context );
171+ }
172+
173+ @ Override
174+ public Boolean visitXor (Xor node , Object context ) {
175+ return canPaginate (node , context );
176+ }
177+
178+ @ Override
179+ public Boolean visitFunction (Function node , Object context ) {
180+ return canPaginate (node , context );
181+ }
182+
183+ @ Override
184+ public Boolean visitIn (In node , Object context ) {
185+ return canPaginate (node , context ) && node .getValueList ().stream ()
186+ .allMatch (n -> n .accept (this , context ));
187+ }
188+
189+ @ Override
190+ public Boolean visitBetween (Between node , Object context ) {
191+ return canPaginate (node , context );
192+ }
193+
194+ @ Override
195+ public Boolean visitCase (Case node , Object context ) {
196+ return canPaginate (node , context );
197+ }
198+
199+ @ Override
200+ public Boolean visitWhen (When node , Object context ) {
201+ return canPaginate (node , context );
202+ }
203+
204+ @ Override
205+ public Boolean visitCast (Cast node , Object context ) {
206+ return canPaginate (node , context ) && node .getConvertedType ().accept (this , context );
207+ }
208+
209+ @ Override
210+ public Boolean visitHighlightFunction (HighlightFunction node , Object context ) {
211+ return canPaginate (node , context );
212+ }
213+
214+ @ Override
215+ public Boolean visitUnresolvedArgument (UnresolvedArgument node , Object context ) {
216+ return canPaginate (node , context );
217+ }
218+
219+ @ Override
220+ public Boolean visitUnresolvedAttribute (UnresolvedAttribute node , Object context ) {
221+ return canPaginate (node , context );
222+ }
223+
39224 @ Override
40225 public Boolean visitChildren (Node node , Object context ) {
226+ // for all not listed (= unchecked) - false
41227 return Boolean .FALSE ;
42228 }
43229
44230 @ 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).
231+ public Boolean visitWindowFunction (WindowFunction node , Object context ) {
232+ // don't support in-memory aggregation
48233 // SELECT max(age) OVER (PARTITION BY city) ...
49- var projections = node .getProjectList ();
50- if (projections .size () != 1 ) {
51- return Boolean .FALSE ;
52- }
234+ return Boolean .FALSE ;
235+ }
53236
54- if (!(projections .get (0 ) instanceof AllFields )) {
237+ @ Override
238+ public Boolean visitProject (Project node , Object context ) {
239+ if (!node .getProjectList ().stream ().allMatch (n -> n .accept (this , context ))) {
55240 return Boolean .FALSE ;
56241 }
57242
0 commit comments