1818import org .apache .calcite .rel .type .RelDataType ;
1919import org .apache .calcite .rel .type .RelDataTypeFactory ;
2020import org .apache .calcite .rex .RexBuilder ;
21+ import org .apache .calcite .rex .RexCorrelVariable ;
2122import org .apache .calcite .rex .RexNode ;
2223import org .apache .calcite .sql .SqlIntervalQualifier ;
2324import org .apache .calcite .sql .fun .SqlStdOperatorTable ;
2425import org .apache .calcite .sql .parser .SqlParserUtil ;
2526import org .apache .calcite .sql .type .SqlTypeName ;
2627import org .apache .calcite .util .DateString ;
28+ import org .apache .calcite .util .Holder ;
2729import org .apache .calcite .util .TimeString ;
2830import org .apache .calcite .util .TimestampString ;
31+ import org .checkerframework .checker .nullness .qual .Nullable ;
2932import org .opensearch .sql .ast .AbstractNodeVisitor ;
3033import org .opensearch .sql .ast .expression .Alias ;
3134import org .opensearch .sql .ast .expression .And ;
4144import org .opensearch .sql .ast .expression .SpanUnit ;
4245import org .opensearch .sql .ast .expression .UnresolvedExpression ;
4346import org .opensearch .sql .ast .expression .Xor ;
47+ import org .opensearch .sql .ast .expression .subquery .ExistsSubquery ;
4448import org .opensearch .sql .ast .expression .subquery .InSubquery ;
4549import org .opensearch .sql .ast .tree .UnresolvedPlan ;
4650import org .opensearch .sql .calcite .utils .BuiltinFunctionUtils ;
@@ -156,40 +160,62 @@ public RexNode visitEqualTo(EqualTo node, CalcitePlanContext context) {
156160
157161 @ Override
158162 public RexNode visitQualifiedName (QualifiedName node , CalcitePlanContext context ) {
163+ // 1. resolve QualifiedName in join condition
159164 if (context .isResolvingJoinCondition ()) {
160165 List <String > parts = node .getParts ();
161166 if (parts .size () == 1 ) {
162- // Handle the case of `id = cid`
167+ // 1.1 Handle the case of `id = cid`
163168 try {
164169 return context .relBuilder .field (2 , 0 , parts .getFirst ());
165170 } catch (IllegalArgumentException ee ) {
166171 return context .relBuilder .field (2 , 1 , parts .getFirst ());
167172 }
168173 } else if (parts .size () == 2 ) {
169- // Handle the case of `t1.id = t2.id` or `alias1.id = alias2.id`
174+ // 1.2 Handle the case of `t1.id = t2.id` or `alias1.id = alias2.id`
170175 return context .relBuilder .field (2 , parts .get (0 ), parts .get (1 ));
171176 } else if (parts .size () == 3 ) {
172177 throw new UnsupportedOperationException ("Unsupported qualified name: " + node );
173178 }
174179 }
180+
181+ // 2. resolve QualifiedName in non-join condition
175182 String qualifiedName = node .toString ();
176183 List <String > currentFields = context .relBuilder .peek ().getRowType ().getFieldNames ();
177184 if (currentFields .contains (qualifiedName )) {
185+ // 2.1 resolve QualifiedName from stack top
178186 return context .relBuilder .field (qualifiedName );
179187 } else if (node .getParts ().size () == 2 ) {
188+ // 2.2 resolve QualifiedName with an alias or table name
180189 List <String > parts = node .getParts ();
181- return context .relBuilder .field (parts .get (0 ), parts .get (1 ));
190+ try {
191+ return context .relBuilder .field (1 , parts .get (0 ), parts .get (1 ));
192+ } catch (IllegalArgumentException e ) {
193+ // 2.3 resolve QualifiedName with outer alias
194+ return context
195+ .peekCorrelVar ()
196+ .map (correlVar -> context .relBuilder .field (correlVar , parts .get (1 )))
197+ .orElseThrow (() -> e ); // Re-throw the exception if no correlated variable exists
198+ }
182199 } else if (currentFields .stream ().noneMatch (f -> f .startsWith (qualifiedName ))) {
183- return context .relBuilder .field (qualifiedName );
200+ // 2.4 try resolving combination of 2.1 and 2.3 to resolve rest cases
201+ return context
202+ .peekCorrelVar ()
203+ .map (correlVar -> context .relBuilder .field (correlVar , qualifiedName ))
204+ .orElseGet (() -> context .relBuilder .field (qualifiedName ));
184205 }
185- // Handle the overriding fields, for example, `eval SAL = SAL + 1` will delete the original SAL
186- // and add a SAL0
206+ // 3. resolve overriding fields, for example, `eval SAL = SAL + 1` will delete the original SAL
207+ // and add a SAL0. SAL0 in currentFields, but qualifiedName is SAL.
208+ // TODO now we cannot handle the case using a overriding fields in subquery, for example
209+ // source = EMP | eval DEPTNO = DEPTNO + 1 | where exists [ source = DEPT | where emp.DEPTNO =
210+ // DEPTNO ]
187211 Map <String , String > fieldMap =
188212 currentFields .stream ().collect (Collectors .toMap (s -> s .replaceAll ("\\ d" , "" ), s -> s ));
189213 if (fieldMap .containsKey (qualifiedName )) {
190214 return context .relBuilder .field (fieldMap .get (qualifiedName ));
191215 } else {
192- return null ;
216+ throw new IllegalArgumentException (
217+ String .format (
218+ "field [%s] not found; input fields are: %s" , qualifiedName , currentFields ));
193219 }
194220 }
195221
@@ -256,20 +282,8 @@ public RexNode visitFunction(Function node, CalcitePlanContext context) {
256282 @ Override
257283 public RexNode visitInSubquery (InSubquery node , CalcitePlanContext context ) {
258284 List <RexNode > nodes = node .getChild ().stream ().map (child -> analyze (child , context )).toList ();
259- // clear and store the outer state
260- boolean isResolvingJoinConditionOuter = context .isResolvingJoinCondition ();
261- if (isResolvingJoinConditionOuter ) {
262- context .setResolvingJoinCondition (false );
263- }
264285 UnresolvedPlan subquery = node .getQuery ();
265-
266- RelNode subqueryRel = subquery .accept (planVisitor , context );
267- // pop the inner plan
268- context .relBuilder .build ();
269- // restore to the previous state
270- if (isResolvingJoinConditionOuter ) {
271- context .setResolvingJoinCondition (true );
272- }
286+ RelNode subqueryRel = resolveSubqueryPlan (subquery , false , context );
273287 try {
274288 return context .relBuilder .in (subqueryRel , nodes );
275289 // TODO
@@ -288,4 +302,32 @@ public RexNode visitInSubquery(InSubquery node, CalcitePlanContext context) {
288302 + " of columns in the output of subquery" );
289303 }
290304 }
305+
306+ @ Override
307+ public RexNode visitExistsSubquery (ExistsSubquery node , CalcitePlanContext context ) {
308+ final Holder <@ Nullable RexCorrelVariable > v = Holder .empty ();
309+ return context .relBuilder .exists (
310+ b -> {
311+ UnresolvedPlan subquery = node .getQuery ();
312+ return resolveSubqueryPlan (subquery , true , context );
313+ });
314+ }
315+
316+ private RelNode resolveSubqueryPlan (
317+ UnresolvedPlan subquery , boolean isExists , CalcitePlanContext context ) {
318+ // clear and store the outer state
319+ boolean isResolvingJoinConditionOuter = context .isResolvingJoinCondition ();
320+ if (isResolvingJoinConditionOuter ) {
321+ context .setResolvingJoinCondition (false );
322+ }
323+ RelNode subqueryRel = subquery .accept (planVisitor , context );
324+ // pop the inner plan
325+ context .relBuilder .build ();
326+ // clear the exists subquery resolving state
327+ // restore to the previous state
328+ if (isResolvingJoinConditionOuter ) {
329+ context .setResolvingJoinCondition (true );
330+ }
331+ return subqueryRel ;
332+ }
291333}
0 commit comments