2323import com .google .common .collect .ImmutableList ;
2424import com .google .common .collect .Iterables ;
2525import com .google .common .collect .Streams ;
26+ import java .lang .reflect .Method ;
2627import java .util .ArrayList ;
2728import java .util .Comparator ;
2829import java .util .HashSet ;
3536import java .util .stream .Stream ;
3637import org .apache .calcite .plan .RelOptTable ;
3738import org .apache .calcite .plan .ViewExpanders ;
39+ import org .apache .calcite .rel .RelCollations ;
3840import org .apache .calcite .rel .RelNode ;
3941import org .apache .calcite .rel .core .Aggregate ;
4042import org .apache .calcite .rel .core .JoinRelType ;
107109import org .opensearch .sql .ast .tree .Trendline .TrendlineType ;
108110import org .opensearch .sql .ast .tree .UnresolvedPlan ;
109111import org .opensearch .sql .ast .tree .Window ;
112+ import org .opensearch .sql .calcite .plan .LogicalSystemLimit ;
110113import org .opensearch .sql .calcite .plan .OpenSearchConstants ;
111114import org .opensearch .sql .calcite .utils .JoinAndLookupUtils ;
112115import org .opensearch .sql .calcite .utils .PlanUtils ;
@@ -133,6 +136,21 @@ public RelNode analyze(UnresolvedPlan unresolved, CalcitePlanContext context) {
133136 return unresolved .accept (this , context );
134137 }
135138
139+ /** Adds a rel node to the top of the stack while preserving the field names and aliases. */
140+ private void replaceTop (RelBuilder relBuilder , RelNode relNode ) {
141+ try {
142+ Method method = RelBuilder .class .getDeclaredMethod ("replaceTop" , RelNode .class );
143+ method .setAccessible (true );
144+ method .invoke (relBuilder , relNode );
145+ } catch (Exception e ) {
146+ throw new IllegalStateException ("Unable to invoke RelBuilder.replaceTop" , e );
147+ }
148+ }
149+
150+ private RelNode sysLimit (RelNode child , RexNode fetch ) {
151+ return LogicalSystemLimit .create (child , RelCollations .EMPTY , null , fetch );
152+ }
153+
136154 @ Override
137155 public RelNode visitRelation (Relation node , CalcitePlanContext context ) {
138156 context .relBuilder .scan (node .getTableQualifiedName ().getParts ());
@@ -642,8 +660,24 @@ private Optional<RexLiteral> extractAliasLiteral(RexNode node) {
642660
643661 @ Override
644662 public RelNode visitJoin (Join node , CalcitePlanContext context ) {
645- List <UnresolvedPlan > children = node .getChildren ();
646- children .forEach (c -> analyze (c , context ));
663+ // 1. visit left child
664+ analyze (node .getLeft (), context );
665+ // 2. add system limit to left side (main-search) if join type is right outer
666+ if (node .getJoinType () == Join .JoinType .RIGHT ) {
667+ replaceTop (
668+ context .relBuilder ,
669+ sysLimit (context .relBuilder .peek (), context .relBuilder .literal (context .querySysLimit )));
670+ }
671+ // 3. visit right child
672+ analyze (node .getRight (), context );
673+ // 4. add system limit to right side (subsearch) if join type is not semi and anti
674+ if (node .getJoinType () != Join .JoinType .RIGHT
675+ && node .getJoinType () != Join .JoinType .SEMI
676+ && node .getJoinType () != Join .JoinType .ANTI ) {
677+ replaceTop (
678+ context .relBuilder ,
679+ sysLimit (context .relBuilder .peek (), context .relBuilder .literal (context .querySysLimit )));
680+ }
647681 RexNode joinCondition =
648682 node .getJoinCondition ()
649683 .map (c -> rexVisitor .analyzeJoinCondition (c , context ))
@@ -768,22 +802,27 @@ public RelNode visitLookup(Lookup node, CalcitePlanContext context) {
768802 expectedProvidedFieldNames = newExpectedFieldNames ;
769803 }
770804
771- // 5. Resolve join condition. Note, this operation should be done after finishing all analyze.
805+ // 5. Add system limit to right side (subsearch)
806+ replaceTop (
807+ context .relBuilder ,
808+ sysLimit (context .relBuilder .peek (), context .relBuilder .literal (context .querySysLimit )));
809+
810+ // 6. Resolve join condition. Note, this operation should be done after finishing all analyze.
772811 JoinAndLookupUtils .addJoinForLookUp (node , context );
773812
774- // 6 . Add projection for coalesce fields if there is.
813+ // 7 . Add projection for coalesce fields if there is.
775814 if (!newCoalesceList .isEmpty ()) {
776815 context .relBuilder .projectPlus (newCoalesceList );
777816 }
778817
779- // 7 . Add projection to remove unnecessary fields
818+ // 8 . Add projection to remove unnecessary fields
780819 // NOTE: Need to lazy invoke projectExcept until finishing all analyzing,
781820 // otherwise the field names may have changed because of field name duplication.
782821 if (!toBeRemovedFields .isEmpty ()) {
783822 context .relBuilder .projectExcept (toBeRemovedFields );
784823 }
785824
786- // 7 . Rename the fields to the expected names.
825+ // 9 . Rename the fields to the expected names.
787826 JoinAndLookupUtils .renameToExpectedFields (
788827 expectedProvidedFieldNames ,
789828 sourceFieldsNames .size () - duplicatedSourceFields .size (),
@@ -1449,14 +1488,18 @@ private void buildExpandRelNode(
14491488 .uncollect (List .of (), false )
14501489 .build ();
14511490
1452- // 6. Perform a nested-loop join (correlate) between the original table and the expanded
1491+ // 6. add system limit to right side
1492+ RelNode rightNodeWithLimit =
1493+ sysLimit (rightNode , context .relBuilder .literal (context .querySysLimit ));
1494+
1495+ // 7. Perform a nested-loop join (correlate) between the original table and the expanded
14531496 // array field.
14541497 // The last parameter has to refer to the array to be expanded on the left side. It will
14551498 // be used by the right side to correlate with the left side.
14561499 context
14571500 .relBuilder
14581501 .push (leftNode )
1459- .push (rightNode )
1502+ .push (rightNodeWithLimit )
14601503 .correlate (JoinRelType .INNER , correlVariable .get ().id , List .of (arrayFieldRex ))
14611504 // 7. Remove the original array field from the output.
14621505 // TODO: RFC: should we keep the original array field when alias is present?
0 commit comments