You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by hy...@apache.org on 2020/06/13 21:13:35 UTC

[calcite] branch master updated: [CALCITE-4057] Support trait propagation for EnumerableBatchNestedLoopJoin (Rui Wang)

This is an automated email from the ASF dual-hosted git repository.

hyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 978bb7e  [CALCITE-4057] Support trait propagation for EnumerableBatchNestedLoopJoin (Rui Wang)
978bb7e is described below

commit 978bb7ea44969351468d1b5e240e8f57af7e5770
Author: amaliujia <am...@163.com>
AuthorDate: Thu Jun 11 20:38:32 2020 -0700

    [CALCITE-4057] Support trait propagation for EnumerableBatchNestedLoopJoin (Rui Wang)
    
    Close #2023
---
 .../enumerable/EnumerableBatchNestedLoopJoin.java  | 22 +++++++
 .../calcite/adapter/enumerable/EnumerableCalc.java |  4 +-
 .../adapter/enumerable/EnumerableCorrelate.java    | 34 ++---------
 .../adapter/enumerable/EnumerableHashJoin.java     | 38 ++----------
 .../enumerable/EnumerableNestedLoopJoin.java       | 48 +++------------
 .../adapter/enumerable/EnumerableProject.java      |  4 +-
 ...TraitsUtils.java => EnumerableTraitsUtils.java} | 69 +++++++++++++++++++++-
 .../org/apache/calcite/test/TopDownOptTest.java    | 46 ++++++++++-----
 .../org/apache/calcite/test/TopDownOptTest.xml     | 61 +++++++++++++++++++
 9 files changed, 201 insertions(+), 125 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableBatchNestedLoopJoin.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableBatchNestedLoopJoin.java
index 13ff1e1..de727c0 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableBatchNestedLoopJoin.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableBatchNestedLoopJoin.java
@@ -22,6 +22,7 @@ import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.linq4j.tree.Expressions;
 import org.apache.calcite.linq4j.tree.ParameterExpression;
 import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.plan.DeriveMode;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -37,6 +38,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
 
@@ -88,6 +90,26 @@ public class EnumerableBatchNestedLoopJoin extends Join implements EnumerableRel
         joinType);
   }
 
+  @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
+      final RelTraitSet required) {
+    return EnumerableTraitsUtils.passThroughTraitsForJoin(
+        required, joinType, getLeft().getRowType().getFieldCount(), traitSet);
+  }
+
+  @Override public Pair<RelTraitSet, List<RelTraitSet>> deriveTraits(
+      final RelTraitSet childTraits, final int childId) {
+    return EnumerableTraitsUtils.deriveTraitsForJoin(
+        childTraits, childId, joinType, traitSet, right.getTraitSet());
+  }
+
+  @Override public DeriveMode getDeriveMode() {
+    if (joinType == JoinRelType.FULL || joinType == JoinRelType.RIGHT) {
+      return DeriveMode.PROHIBITED;
+    }
+
+    return DeriveMode.LEFT_FIRST;
+  }
+
   @Override public EnumerableBatchNestedLoopJoin copy(RelTraitSet traitSet,
       RexNode condition, RelNode left, RelNode right, JoinRelType joinType,
       boolean semiJoinDone) {
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
index f899624..f987cbf 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
@@ -272,7 +272,7 @@ public class EnumerableCalc extends Calc implements EnumerableRel {
     final List<RexNode> exps = Lists.transform(program.getProjectList(),
         program::expandLocalRef);
 
-    return EnumTraitsUtils.passThroughTraitsForProject(required, exps,
+    return EnumerableTraitsUtils.passThroughTraitsForProject(required, exps,
         input.getRowType(), input.getCluster().getTypeFactory(), traitSet);
   }
 
@@ -281,7 +281,7 @@ public class EnumerableCalc extends Calc implements EnumerableRel {
     final List<RexNode> exps = Lists.transform(program.getProjectList(),
         program::expandLocalRef);
 
-    return EnumTraitsUtils.deriveTraitsForProject(childTraits, childId, exps,
+    return EnumerableTraitsUtils.deriveTraitsForProject(childTraits, childId, exps,
         input.getRowType(), input.getCluster().getTypeFactory(), traitSet);
   }
 
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
index cc1e56c..86bc042 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
@@ -24,10 +24,7 @@ import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.plan.DeriveMode;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationTraitDef;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.CorrelationId;
@@ -89,41 +86,18 @@ public class EnumerableCorrelate extends Correlate
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
-    final RelCollation collation = required.getCollation();
-    if (collation == null || collation == RelCollations.EMPTY) {
-      return null;
-    }
-
     // EnumerableCorrelate traits passdown shall only pass through collation to left input.
     // This is because for EnumerableCorrelate always uses left input as the outer loop,
     // thus only left input can preserve ordering.
-
-    for (RelFieldCollation relFieldCollation : collation.getFieldCollations()) {
-      // If field collation belongs to right input: bail out.
-      if (relFieldCollation.getFieldIndex() >= getLeft().getRowType().getFieldCount()) {
-        return null;
-      }
-    }
-
-    final RelTraitSet passThroughTraitSet = traitSet.replace(collation);
-    return Pair.of(passThroughTraitSet,
-        ImmutableList.of(
-            passThroughTraitSet,
-            passThroughTraitSet.replace(RelCollations.EMPTY)));
+    return EnumerableTraitsUtils.passThroughTraitsForJoin(
+        required, joinType, left.getRowType().getFieldCount(), getTraitSet());
   }
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> deriveTraits(
       final RelTraitSet childTraits, final int childId) {
     // should only derive traits (limited to collation for now) from left input.
-    assert childId == 0;
-
-    final RelCollation collation = childTraits.getCollation();
-    if (collation == null || collation == RelCollations.EMPTY) {
-      return null;
-    }
-
-    final RelTraitSet traits = traitSet.replace(collation);
-    return Pair.of(traits, ImmutableList.of(traits, right.getTraitSet()));
+    return EnumerableTraitsUtils.deriveTraitsForJoin(
+        childTraits, childId, joinType, traitSet, right.getTraitSet());
   }
 
   @Override public DeriveMode getDeriveMode() {
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
index aa2f41f..918919e 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
@@ -24,10 +24,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationTraitDef;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelNodes;
 import org.apache.calcite.rel.core.CorrelationId;
@@ -108,42 +105,15 @@ public class EnumerableHashJoin extends Join implements EnumerableRel {
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
-    RelCollation collation = required.getCollation();
-    if (collation == null
-        || collation == RelCollations.EMPTY
-        || joinType == JoinRelType.FULL
-        || joinType == JoinRelType.RIGHT) {
-      return null;
-    }
-
-    for (RelFieldCollation fc : collation.getFieldCollations()) {
-      // If field collation belongs to right input: cannot push down collation.
-      if (fc.getFieldIndex() >= getLeft().getRowType().getFieldCount()) {
-        return null;
-      }
-    }
-
-    RelTraitSet passthroughTraitSet = traitSet.replace(collation);
-    return Pair.of(passthroughTraitSet,
-        ImmutableList.of(
-            passthroughTraitSet,
-            passthroughTraitSet.replace(RelCollations.EMPTY)));
+    return EnumerableTraitsUtils.passThroughTraitsForJoin(
+        required, joinType, left.getRowType().getFieldCount(), getTraitSet());
   }
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> deriveTraits(
       final RelTraitSet childTraits, final int childId) {
     // should only derive traits (limited to collation for now) from left join input.
-    assert childId == 0;
-
-    RelCollation collation = childTraits.getCollation();
-    if (collation == null || collation == RelCollations.EMPTY) {
-      return null;
-    }
-
-    RelTraitSet derivedTraits = getTraitSet().replace(collation);
-    return Pair.of(
-        derivedTraits,
-        ImmutableList.of(derivedTraits, right.getTraitSet()));
+    return EnumerableTraitsUtils.deriveTraitsForJoin(
+        childTraits, childId, joinType, getTraitSet(), right.getTraitSet());
   }
 
   @Override public DeriveMode getDeriveMode() {
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
index a10866e..1561d8f 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
@@ -24,10 +24,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationTraitDef;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelNodes;
 import org.apache.calcite.rel.core.CorrelationId;
@@ -126,50 +123,21 @@ public class EnumerableNestedLoopJoin extends Join implements EnumerableRel {
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
-    RelCollation collation = required.getCollation();
-    if (collation == null
-        || collation == RelCollations.EMPTY
-        || joinType == JoinRelType.FULL
-        || joinType == JoinRelType.RIGHT) {
-      return null;
-    }
-
-    // EnumerableNestedLoopJoin traits passdown shall only pass through collation to left input.
-    // It is because for EnumerableNestedLoopJoin always uses left input as the outer loop,
-    // thus only left input can preserve ordering.
+    // EnumerableNestedLoopJoin traits passdown shall only pass through collation to
+    // left input. It is because for EnumerableNestedLoopJoin always
+    // uses left input as the outer loop, thus only left input can preserve ordering.
     // Push sort both to left and right inputs does not help right outer join. It's because in
     // implementation, EnumerableNestedLoopJoin produces (null, right_unmatched) all together,
     // which does not preserve ordering from right side.
-
-
-    for (RelFieldCollation fc : collation.getFieldCollations()) {
-      // If field collation belongs to right input: cannot push down collation.
-      if (fc.getFieldIndex() >= getLeft().getRowType().getFieldCount()) {
-        return null;
-      }
-    }
-
-    RelTraitSet passthroughTraitSet = traitSet.replace(collation);
-    return Pair.of(passthroughTraitSet,
-        ImmutableList.of(
-            passthroughTraitSet,
-            passthroughTraitSet.replace(RelCollations.EMPTY)));
+    return EnumerableTraitsUtils.passThroughTraitsForJoin(
+        required, joinType, getLeft().getRowType().getFieldCount(), traitSet);
   }
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> deriveTraits(
       final RelTraitSet childTraits, final int childId) {
-    // should only derive traits (limited to collation for now) from left join input.
-    assert childId == 0;
-
-    RelCollation collation = childTraits.getCollation();
-    if (collation == null || collation == RelCollations.EMPTY) {
-      return null;
-    }
-
-    RelTraitSet derivedTraits = getTraitSet().replace(collation);
-    return Pair.of(
-        derivedTraits,
-        ImmutableList.of(derivedTraits, right.getTraitSet()));
+    return EnumerableTraitsUtils.deriveTraitsForJoin(
+        childTraits, childId, joinType, traitSet, right.getTraitSet()
+    );
   }
 
   @Override public DeriveMode getDeriveMode() {
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
index d940d97..7c62e2f 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
@@ -91,13 +91,13 @@ public class EnumerableProject extends Project implements EnumerableRel {
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       RelTraitSet required) {
-    return EnumTraitsUtils.passThroughTraitsForProject(required, exps,
+    return EnumerableTraitsUtils.passThroughTraitsForProject(required, exps,
         input.getRowType(), input.getCluster().getTypeFactory(), traitSet);
   }
 
   @Override public Pair<RelTraitSet, List<RelTraitSet>> deriveTraits(
       final RelTraitSet childTraits, final int childId) {
-    return EnumTraitsUtils.deriveTraitsForProject(childTraits, childId, exps,
+    return EnumerableTraitsUtils.deriveTraitsForProject(childTraits, childId, exps,
         input.getRowType(), input.getCluster().getTypeFactory(), traitSet);
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumTraitsUtils.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTraitsUtils.java
similarity index 69%
rename from core/src/main/java/org/apache/calcite/adapter/enumerable/EnumTraitsUtils.java
rename to core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTraitsUtils.java
index 2be158a..16dc2aa 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumTraitsUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTraitsUtils.java
@@ -22,6 +22,7 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexCall;
@@ -47,9 +48,9 @@ import java.util.Objects;
  * Utilities for traits propagation.
  */
 @API(since = "1.24", status = API.Status.INTERNAL)
-class EnumTraitsUtils {
+class EnumerableTraitsUtils {
 
-  private EnumTraitsUtils() {}
+  private EnumerableTraitsUtils() {}
 
   /**
    * Determine whether there is mapping between project input and output fields.
@@ -148,4 +149,68 @@ class EnumTraitsUtils {
       return null;
     }
   }
+
+  /**
+   * This function can be reused when a Join's traits pass-down shall only
+   * pass through collation to left input.
+   *
+   * @param required required trait set for the join
+   * @param joinType the join type
+   * @param leftInputFieldCount number of field count of left join input
+   * @param joinTraitSet trait set of the join
+   */
+  static Pair<RelTraitSet, List<RelTraitSet>> passThroughTraitsForJoin(
+      RelTraitSet required, JoinRelType joinType,
+      int leftInputFieldCount, RelTraitSet joinTraitSet) {
+    RelCollation collation = required.getCollation();
+    if (collation == null
+        || collation == RelCollations.EMPTY
+        || joinType == JoinRelType.FULL
+        || joinType == JoinRelType.RIGHT) {
+      return null;
+    }
+
+    for (RelFieldCollation fc : collation.getFieldCollations()) {
+      // If field collation belongs to right input: cannot push down collation.
+      if (fc.getFieldIndex() >= leftInputFieldCount) {
+        return null;
+      }
+    }
+
+    RelTraitSet passthroughTraitSet = joinTraitSet.replace(collation);
+    return Pair.of(passthroughTraitSet,
+        ImmutableList.of(
+            passthroughTraitSet,
+            passthroughTraitSet.replace(RelCollations.EMPTY)));
+  }
+
+  /**
+   * This function can be reused when a Join's traits derivation shall only
+   * derive collation from left input.
+   *
+   * @param childTraits trait set of the child
+   * @param childId id of the child (0 is left join input)
+   * @param joinType the join type
+   * @param joinTraitSet trait set of the join
+   * @param rightTraitSet trait set of the right join input
+   */
+  static Pair<RelTraitSet, List<RelTraitSet>> deriveTraitsForJoin(
+      RelTraitSet childTraits, int childId, JoinRelType joinType,
+      RelTraitSet joinTraitSet, RelTraitSet rightTraitSet) {
+    // should only derive traits (limited to collation for now) from left join input.
+    assert childId == 0;
+
+    RelCollation collation = childTraits.getCollation();
+    if (collation == null
+        || collation == RelCollations.EMPTY
+        || joinType == JoinRelType.FULL
+        || joinType == JoinRelType.RIGHT) {
+      return null;
+    }
+
+    RelTraitSet derivedTraits = joinTraitSet.replace(collation);
+    return Pair.of(
+        derivedTraits,
+        ImmutableList.of(derivedTraits, rightTraitSet));
+  }
 }
diff --git a/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java b/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java
index bac5e4d..20f0f36 100644
--- a/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TopDownOptTest.java
@@ -379,7 +379,6 @@ class TopDownOptTest extends RelOptTestBase {
         + "order by r.job desc nulls last, r.ename nulls first";
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .check();
   }
 
@@ -393,7 +392,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -408,7 +406,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -423,7 +420,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -438,7 +434,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -453,7 +448,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -468,7 +462,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -483,7 +476,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -499,7 +491,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -513,7 +504,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -527,7 +517,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -541,7 +530,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -555,7 +543,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -569,7 +556,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -583,7 +569,6 @@ class TopDownOptTest extends RelOptTestBase {
 
     Query.create(sql)
         .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
-        .removeRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
         .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
         .check();
   }
@@ -693,6 +678,37 @@ class TopDownOptTest extends RelOptTestBase {
         .removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
         .check();
   }
+
+  // push sort to left input
+  @Test void testBatchNestedLoopJoinLeftOuterJoinPushDownSort() {
+    final String sql = "select * from\n"
+        + " customer.contact_peek r left outer join\n"
+        + "customer.account s\n"
+        + "on r.contactno>s.acctno and r.email<s.type\n"
+        + "order by r.contactno desc, r.email desc";
+
+    Query.create(sql)
+        .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
+        .removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
+        .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
+        .addRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
+        .check();
+  }
+
+  // Collation can be derived from left input so that top Sort is removed.
+  @Test void testBatchNestedLoopJoinTraitDerivation() {
+    final String sql = "select * from\n"
+        + "(select ename, job, mgr from sales.emp order by ename desc, job desc, mgr limit 10) r\n"
+        + "join sales.bonus s on r.ename>s.ename and r.job<s.job\n"
+        + "order by r.ename desc, r.job desc";
+
+    Query.create(sql)
+        .removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
+        .removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
+        .removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
+        .addRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
+        .check();
+  }
 }
 
 /**
diff --git a/core/src/test/resources/org/apache/calcite/test/TopDownOptTest.xml b/core/src/test/resources/org/apache/calcite/test/TopDownOptTest.xml
index 2f4fa70..3442857 100644
--- a/core/src/test/resources/org/apache/calcite/test/TopDownOptTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/TopDownOptTest.xml
@@ -1192,6 +1192,67 @@ EnumerableCorrelate(correlation=[$cor2], joinType=[semi], requiredColumns=[{0}])
 ]]>
     </Resource>
   </TestCase>
+  <TestCase name="testBatchNestedLoopJoinLeftOuterJoinPushDownSort">
+    <Resource name="sql">
+      <![CDATA[select * from
+ customer.contact_peek r left outer join
+customer.account s
+on r.contactno>s.acctno and r.email<s.type
+order by r.contactno desc, r.email desc]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], X=[$4], Y=[$5], unit=[$6], COORD_NE=[ROW($7, ROW($8, $9))], ACCTNO=[$10], TYPE=[$11], BALANCE=[$12])
+  LogicalSort(sort0=[$0], sort1=[$3], dir0=[DESC], dir1=[DESC])
+    LogicalProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], X=[$4], Y=[$5], unit=[$6], COORD_NE=[$7], COORD_NE8=[$8], COORD_NE9=[$9], ACCTNO=[$10], TYPE=[$11], BALANCE=[$12])
+      LogicalJoin(condition=[AND(>($0, $10), <($3, $11))], joinType=[left])
+        LogicalProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], X=[$4.X], Y=[$4.Y], unit=[$4.unit], M=[$5.M], A=[$5.SUB.A], B=[$5.SUB.B])
+          LogicalTableScan(table=[[CATALOG, CUSTOMER, CONTACT_PEEK]])
+        LogicalTableScan(table=[[CATALOG, CUSTOMER, ACCOUNT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+EnumerableProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], X=[$4], Y=[$5], unit=[$6], COORD_NE=[ROW($7, ROW($8, $9))], ACCTNO=[$10], TYPE=[$11], BALANCE=[$12])
+  EnumerableBatchNestedLoopJoin(condition=[AND(>($0, $10), <($3, $11))], joinType=[left], batchSize=[100])
+    EnumerableProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], X=[$4.X], Y=[$4.Y], unit=[$4.unit], M=[$5.M], A=[$5.SUB.A], B=[$5.SUB.B])
+      EnumerableSort(sort0=[$0], sort1=[$3], dir0=[DESC], dir1=[DESC])
+        EnumerableTableScan(table=[[CATALOG, CUSTOMER, CONTACT_PEEK]])
+    EnumerableFilter(condition=[OR(AND(>($cor0.CONTACTNO, $0), <($cor0.EMAIL, $1)), AND(>($cor1.CONTACTNO, $0), <($cor1.EMAIL, $1)), AND(>($cor2.CONTACTNO, $0), <($cor2.EMAIL, $1)), AND(>($cor3.CONTACTNO, $0), <($cor3.EMAIL, $1)), AND(>($cor4.CONTACTNO, $0), <($cor4.EMAIL, $1)), AND(>($cor5.CONTACTNO, $0), <($cor5.EMAIL, $1)), AND(>($cor6.CONTACTNO, $0), <($cor6.EMAIL, $1)), AND(>($cor7.CONTACTNO, $0), <($cor7.EMAIL, $1)), AND(>($cor8.CONTACTNO, $0), <($cor8.EMAIL, $1)), AND(>($cor9.CONT [...]
+      EnumerableTableScan(table=[[CATALOG, CUSTOMER, ACCOUNT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testBatchNestedLoopJoinTraitDerivation">
+    <Resource name="sql">
+      <![CDATA[select * from
+(select ename, job, mgr from sales.emp order by ename desc, job desc, mgr limit 10) r
+join sales.bonus s on r.ename>s.ename and r.job<s.job
+order by r.ename desc, r.job desc]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalSort(sort0=[$0], sort1=[$1], dir0=[DESC], dir1=[DESC])
+  LogicalProject(ENAME=[$0], JOB=[$1], MGR=[$2], ENAME0=[$3], JOB0=[$4], SAL=[$5], COMM=[$6])
+    LogicalJoin(condition=[AND(>($0, $3), <($1, $4))], joinType=[inner])
+      LogicalSort(sort0=[$0], sort1=[$1], sort2=[$2], dir0=[DESC], dir1=[DESC], dir2=[ASC], fetch=[10])
+        LogicalProject(ENAME=[$1], JOB=[$2], MGR=[$3])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalTableScan(table=[[CATALOG, SALES, BONUS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+EnumerableBatchNestedLoopJoin(condition=[AND(>($0, $3), <($1, $4))], joinType=[inner], batchSize=[100])
+  EnumerableLimit(fetch=[10])
+    EnumerableProject(ENAME=[$1], JOB=[$2], MGR=[$3])
+      EnumerableSort(sort0=[$1], sort1=[$2], sort2=[$3], dir0=[DESC], dir1=[DESC], dir2=[ASC])
+        EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+  EnumerableFilter(condition=[OR(AND(>($cor0.ENAME, $0), <($cor0.JOB, $1)), AND(>($cor1.ENAME, $0), <($cor1.JOB, $1)), AND(>($cor2.ENAME, $0), <($cor2.JOB, $1)), AND(>($cor3.ENAME, $0), <($cor3.JOB, $1)), AND(>($cor4.ENAME, $0), <($cor4.JOB, $1)), AND(>($cor5.ENAME, $0), <($cor5.JOB, $1)), AND(>($cor6.ENAME, $0), <($cor6.JOB, $1)), AND(>($cor7.ENAME, $0), <($cor7.JOB, $1)), AND(>($cor8.ENAME, $0), <($cor8.JOB, $1)), AND(>($cor9.ENAME, $0), <($cor9.JOB, $1)), AND(>($cor10.ENAME, $0), <($c [...]
+    EnumerableTableScan(table=[[CATALOG, SALES, BONUS]])
+]]>
+    </Resource>
+  </TestCase>
   <TestCase name="testValuesTraitRequest">
     <Resource name="sql">
       <![CDATA[SELECT * from (values (1, 1), (2, 1), (1, 2), (2, 2))