You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by ta...@apache.org on 2016/05/12 22:09:47 UTC

[12/50] [abbrv] incubator-impala git commit: IMPALA-2805: Order conjuncts based on selectivity and cost

IMPALA-2805: Order conjuncts based on selectivity and cost

Added costs to all Exprs, which estimate the relative cost of evaluating
an expression and all of its children. Costs are calculated during
analysis. For now, these costs are intended as a simple way to order
expressions from cheap to expensive, not necessarily to be a precise
reflection of running times.

In general, expressions that deal with variable length types like strings
will have higher cost than those dealing with fixed length types
like numbers and booleans. Additionally, expressions with complicated
subexpressions will have higher cost than simpler expressions.

Also added PlanNode.orderConjunctsByCost, which takes a list of Exprs and
returns a new list sorted according to an estimate of the cheapest order to
evaulate the conjuncts in, based on their cost and selectivity.

The conjuncts are sorted by repeatedly iterating over them and choosing the
conjunct that would result in the least total estimated work were it to be
applied before the remaining conjuncts. Selectivities are exponentially
backed off, and Exprs without selectivity estimates are given a reasonable
default.

Change-Id: I02279a26fbc6308ac5eb819d78345fc010469034
Reviewed-on: http://gerrit.cloudera.org:8080/2598
Reviewed-by: Thomas Tauber-Marshall <tm...@cloudera.com>
Tested-by: Internal Jenkins


Project: http://git-wip-us.apache.org/repos/asf/incubator-impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-impala/commit/8c2bf976
Tree: http://git-wip-us.apache.org/repos/asf/incubator-impala/tree/8c2bf976
Diff: http://git-wip-us.apache.org/repos/asf/incubator-impala/diff/8c2bf976

Branch: refs/heads/master
Commit: 8c2bf9769aa7c2918468554c18bffaa072d18073
Parents: 6e89f1a
Author: Thomas Tauber-Marshall <tm...@cloudera.com>
Authored: Mon Mar 21 16:53:50 2016 -0700
Committer: Tim Armstrong <ta...@cloudera.com>
Committed: Thu May 12 14:17:53 2016 -0700

----------------------------------------------------------------------
 .../impala/analysis/ArithmeticExpr.java         |   1 +
 .../impala/analysis/BetweenPredicate.java       |   3 +
 .../impala/analysis/BinaryPredicate.java        |  14 ++
 .../cloudera/impala/analysis/BoolLiteral.java   |   2 +
 .../com/cloudera/impala/analysis/CaseExpr.java  |  29 ++++
 .../com/cloudera/impala/analysis/CastExpr.java  |   2 +
 .../impala/analysis/CompoundPredicate.java      |   1 +
 .../java/com/cloudera/impala/analysis/Expr.java |  81 ++++++++++
 .../impala/analysis/FunctionCallExpr.java       |   3 +
 .../cloudera/impala/analysis/InPredicate.java   |   5 +
 .../impala/analysis/IsNotEmptyPredicate.java    |   1 +
 .../impala/analysis/IsNullPredicate.java        |   1 +
 .../cloudera/impala/analysis/LikePredicate.java |  16 ++
 .../cloudera/impala/analysis/NullLiteral.java   |   1 +
 .../impala/analysis/NumericLiteral.java         |   3 +
 .../com/cloudera/impala/analysis/SlotRef.java   |   2 +
 .../cloudera/impala/analysis/StringLiteral.java |   2 +
 .../analysis/TimestampArithmeticExpr.java       |   1 +
 .../impala/analysis/TupleIsNullPredicate.java   |   1 +
 .../impala/planner/AggregationNode.java         |   1 +
 .../impala/planner/AnalyticEvalNode.java        |   1 +
 .../impala/planner/DataSourceScanNode.java      |   1 +
 .../cloudera/impala/planner/EmptySetNode.java   |   1 +
 .../cloudera/impala/planner/ExchangeNode.java   |   7 +
 .../cloudera/impala/planner/HBaseScanNode.java  |   1 +
 .../cloudera/impala/planner/HashJoinNode.java   |   7 +-
 .../cloudera/impala/planner/HdfsScanNode.java   |   1 +
 .../com/cloudera/impala/planner/JoinNode.java   |   6 +
 .../cloudera/impala/planner/KuduScanNode.java   |   1 +
 .../impala/planner/NestedLoopJoinNode.java      |   1 +
 .../com/cloudera/impala/planner/PlanNode.java   |  64 ++++++++
 .../com/cloudera/impala/planner/SelectNode.java |   1 +
 .../com/cloudera/impala/planner/SortNode.java   |   1 +
 .../com/cloudera/impala/planner/UnionNode.java  |   1 +
 .../com/cloudera/impala/planner/UnnestNode.java |   1 +
 .../cloudera/impala/planner/PlannerTest.java    |   5 +
 .../queries/PlannerTest/analytic-fns.test       |   8 +-
 .../queries/PlannerTest/conjunct-ordering.test  | 147 ++++++++++++++++++
 .../queries/PlannerTest/data-source-tables.test |   4 +-
 .../queries/PlannerTest/distinct.test           |   4 +-
 .../queries/PlannerTest/hbase.test              |  10 +-
 .../queries/PlannerTest/inline-view.test        |  12 +-
 .../queries/PlannerTest/join-order.test         |   2 +-
 .../queries/PlannerTest/joins.test              |  62 ++++----
 .../queries/PlannerTest/kudu-selectivity.test   |   4 +-
 .../queries/PlannerTest/kudu.test               |   8 +-
 .../queries/PlannerTest/nested-collections.test |  40 ++---
 .../queries/PlannerTest/outer-joins.test        |  32 ++--
 .../PlannerTest/predicate-propagation.test      | 154 +++++++++----------
 .../PlannerTest/runtime-filter-propagation.test |  24 +--
 .../queries/PlannerTest/subquery-rewrite.test   |   6 +-
 .../queries/PlannerTest/tpcds-all.test          |  70 ++++-----
 .../queries/PlannerTest/tpch-all.test           |  12 +-
 .../queries/PlannerTest/tpch-nested.test        |  36 ++---
 .../queries/PlannerTest/union.test              |   2 +-
 .../queries/conjunct_ordering.test              |  73 +++++++++
 56 files changed, 733 insertions(+), 247 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/ArithmeticExpr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/ArithmeticExpr.java b/fe/src/main/java/com/cloudera/impala/analysis/ArithmeticExpr.java
index a66bcf6..f141637 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/ArithmeticExpr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/ArithmeticExpr.java
@@ -189,6 +189,7 @@ public class ArithmeticExpr extends Expr {
       Preconditions.checkState(children_.size() == 2);
       t1 = getChild(1).getType();
     }
+    if (hasChildCosts()) evalCost_ = getChildCosts() + ARITHMETIC_OP_COST;
 
     String fnName = op_.getName();
     switch (op_) {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/BetweenPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/BetweenPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/BetweenPredicate.java
index f2e109e..23401cf 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/BetweenPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/BetweenPredicate.java
@@ -107,6 +107,9 @@ public class BetweenPredicate extends Predicate {
 
     // Make sure toThrift() picks up the children of the rewritten predicate.
     children_ = rewrittenPredicate_.getChildren();
+    // Since the only child is a CompoundPredicate expressing the comparison,
+    // the cost of the comparison is fully captured by the children's cost.
+    evalCost_ = getChildCosts();
     isAnalyzed_ = true;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/BinaryPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/BinaryPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/BinaryPredicate.java
index 8d034fb..ff3a540 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/BinaryPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/BinaryPredicate.java
@@ -250,6 +250,20 @@ public class BinaryPredicate extends Predicate {
         selectivity_ = Math.max(0, Math.min(1, selectivity_));
       }
     }
+
+    // Compute cost.
+    if (hasChildCosts()) {
+      if (getChild(0).getType().isFixedLengthType()) {
+        evalCost_ = getChildCosts() + BINARY_PREDICATE_COST;
+      } else if (getChild(0).getType().isStringType()) {
+        evalCost_ = getChildCosts() +
+            (float) (getAvgStringLength(getChild(0)) + getAvgStringLength(getChild(1)) *
+            BINARY_PREDICATE_COST);
+      } else {
+        //TODO(tmarshall): Handle other var length types here.
+        evalCost_ = getChildCosts() + VAR_LEN_BINARY_PREDICATE_COST;
+      }
+    }
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/BoolLiteral.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/BoolLiteral.java b/fe/src/main/java/com/cloudera/impala/analysis/BoolLiteral.java
index 9c671a1..70db142 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/BoolLiteral.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/BoolLiteral.java
@@ -27,10 +27,12 @@ public class BoolLiteral extends LiteralExpr {
   public BoolLiteral(boolean value) {
     this.value_ = value;
     type_ = Type.BOOLEAN;
+    evalCost_ = LITERAL_COST;
   }
 
   public BoolLiteral(String value) throws AnalysisException {
     type_ = Type.BOOLEAN;
+    evalCost_ = LITERAL_COST;
     if (value.toLowerCase().equals("true")) {
       this.value_ = true;
     } else if (value.toLowerCase().equals("false")) {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/CaseExpr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/CaseExpr.java b/fe/src/main/java/com/cloudera/impala/analysis/CaseExpr.java
index 0ee66b1..113547c 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/CaseExpr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/CaseExpr.java
@@ -324,6 +324,35 @@ public class CaseExpr extends Expr {
         CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
     Preconditions.checkNotNull(fn_);
     type_ = returnType;
+
+    // Compute cost as the sum of evaluating all of the WHEN exprs, plus
+    // the max of the THEN/ELSE exprs.
+    float maxThenCost = 0;
+    float whenCosts = 0;
+    boolean hasChildCosts = true;
+    for (int i = 0; i < children_.size(); ++i) {
+      if (!getChild(i).hasCost()) {
+        hasChildCosts = false;
+        break;
+      }
+
+      if (hasCaseExpr_ && i % 2 == 1) {
+        // This child is a WHEN expr. BINARY_PREDICATE_COST accounts for the cost of
+        // comparing the CASE expr to the WHEN expr.
+        whenCosts += getChild(0).getCost() + getChild(i).getCost() +
+          BINARY_PREDICATE_COST;
+      } else if (!hasCaseExpr_ && i % 2 == 0) {
+        // This child is a WHEN expr.
+        whenCosts += getChild(i).getCost();
+      } else if (i != 0) {
+        // This child is a THEN or ELSE expr.
+        float thenCost = getChild(i).getCost();
+        if (thenCost > maxThenCost) maxThenCost = thenCost;
+      }
+    }
+    if (hasChildCosts) {
+      evalCost_ =  whenCosts + maxThenCost;
+    }
   }
 
   private boolean isCase() { return !isDecode(); }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/CastExpr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/CastExpr.java b/fe/src/main/java/com/cloudera/impala/analysis/CastExpr.java
index 0db9beb..a24e58a 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/CastExpr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/CastExpr.java
@@ -205,6 +205,8 @@ public class CastExpr extends Expr {
   }
 
   private void analyze() throws AnalysisException {
+    if (getChild(0).hasCost()) evalCost_ = getChild(0).getCost() + CAST_COST;
+
     Preconditions.checkNotNull(type_);
     if (type_.isComplexType()) {
       throw new AnalysisException(

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/CompoundPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/CompoundPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/CompoundPredicate.java
index 9ee3d8a..c33ef17 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/CompoundPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/CompoundPredicate.java
@@ -132,6 +132,7 @@ public class CompoundPredicate extends Predicate {
     Preconditions.checkState(fn_ != null);
     Preconditions.checkState(fn_.getReturnType().isBoolean());
     castForFunctionCall(false);
+    if (hasChildCosts()) evalCost_ = getChildCosts() + COMPOUND_PREDICATE_COST;
 
     if (!getChild(0).hasSelectivity() ||
         (children_.size() == 2 && !getChild(1).hasSelectivity())) {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/Expr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/Expr.java b/fe/src/main/java/com/cloudera/impala/analysis/Expr.java
index 0d8b5bd..489f188 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/Expr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/Expr.java
@@ -16,6 +16,8 @@ package com.cloudera.impala.analysis;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -62,6 +64,27 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
   // To be used where we cannot come up with a better estimate (selectivity_ is -1).
   public static double DEFAULT_SELECTIVITY = 0.1;
 
+  // The relative costs of different Exprs. These numbers are not intended as a precise
+  // reflection of running times, but as simple heuristics for ordering Exprs from cheap
+  // to expensive.
+  // TODO(tmwarshall): Get these costs in a more principled way, eg. with a benchmark.
+  public final static float ARITHMETIC_OP_COST = 1;
+  public final static float BINARY_PREDICATE_COST = 1;
+  public final static float VAR_LEN_BINARY_PREDICATE_COST = 5;
+  public final static float CAST_COST = 1;
+  public final static float COMPOUND_PREDICATE_COST = 1;
+  public final static float FUNCTION_CALL_COST = 10;
+  public final static float IS_NOT_EMPTY_COST = 1;
+  public final static float IS_NULL_COST = 1;
+  public final static float LIKE_COST = 10;
+  public final static float LITERAL_COST = 1;
+  public final static float SLOT_REF_COST = 1;
+  public final static float TIMESTAMP_ARITHMETIC_COST = 5;
+
+  // To be used when estimating the cost of Exprs of type string where we don't otherwise
+  // have an estimate of how long the strings produced by that Expr are.
+  public final static int DEFAULT_AVG_STRING_LENGTH = 5;
+
   // returns true if an Expr is a non-analytic aggregate.
   private final static com.google.common.base.Predicate<Expr> isAggregatePredicate_ =
       new com.google.common.base.Predicate<Expr>() {
@@ -163,6 +186,13 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
   // Between 0 and 1, or set to -1 if the selectivity could not be estimated.
   protected double selectivity_;
 
+  // Estimated relative cost of evaluating this expression, including the costs of
+  // its children. Set during analysis and used to sort conjuncts within a PlanNode.
+  // Has a default value of -1 indicating unknown cost if the cost of this expression
+  // or any of its children was not set, but it is required to be set for any
+  // expression which may be part of a conjunct.
+  protected float evalCost_;
+
   // estimated number of distinct values produced by Expr; invalid: -1
   // set during analysis
   protected long numDistinctValues_;
@@ -175,6 +205,7 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
     super();
     type_ = Type.INVALID;
     selectivity_ = -1.0;
+    evalCost_ = -1.0f;
     numDistinctValues_ = -1;
   }
 
@@ -189,6 +220,7 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
     isOnClauseConjunct_ = other.isOnClauseConjunct_;
     printSqlInParens_ = other.printSqlInParens_;
     selectivity_ = other.selectivity_;
+    evalCost_ = other.evalCost_;
     numDistinctValues_ = other.numDistinctValues_;
     fn_ = other.fn_;
     children_ = Expr.cloneList(other.children_);
@@ -199,6 +231,11 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
   public Type getType() { return type_; }
   public double getSelectivity() { return selectivity_; }
   public boolean hasSelectivity() { return selectivity_ >= 0; }
+  public float getCost() {
+    Preconditions.checkState(isAnalyzed_);
+    return evalCost_;
+  }
+  public boolean hasCost() { return evalCost_ >= 0; }
   public long getNumDistinctValues() { return numDistinctValues_; }
   public void setPrintSqlInParens(boolean b) { printSqlInParens_ = b; }
   public boolean isOnClauseConjunct() { return isOnClauseConjunct_; }
@@ -1072,6 +1109,7 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
         .add("id", id_)
         .add("type", type_)
         .add("sel", selectivity_)
+        .add("evalCost", evalCost_)
         .add("#distinct", numDistinctValues_)
         .toString();
   }
@@ -1185,4 +1223,47 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
     }
     isAnalyzed_ = false;
   }
+
+  /**
+   * Returns true iff all of this Expr's children have their costs set.
+   */
+  protected boolean hasChildCosts() {
+    for (Expr child : children_) {
+      if (!child.hasCost()) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Computes and returns the sum of the costs of all of this Expr's children.
+   */
+  protected float getChildCosts() {
+    float cost = 0;
+    for (Expr child : children_) cost += child.getCost();
+    return cost;
+  }
+
+  /**
+   * Returns the average length of the values produced by an Expr
+   * of type string. Returns a default for unknown lengths.
+   */
+  protected static double getAvgStringLength(Expr e) {
+    Preconditions.checkState(e.getType().isStringType());
+    Preconditions.checkState(e.isAnalyzed_);
+
+    SlotRef ref = e.unwrapSlotRef(false);
+    if (ref != null) {
+      if (ref.getDesc() != null && ref.getDesc().getStats().getAvgSize() > 0) {
+        return ref.getDesc().getStats().getAvgSize();
+      } else {
+        return DEFAULT_AVG_STRING_LENGTH;
+      }
+    } else if (e instanceof StringLiteral) {
+      return ((StringLiteral) e).getValue().length();
+    } else {
+      // TODO(tmarshall): Extend this to support other string Exprs, such as
+      // function calls that return string.
+      return DEFAULT_AVG_STRING_LENGTH;
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/FunctionCallExpr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/FunctionCallExpr.java b/fe/src/main/java/com/cloudera/impala/analysis/FunctionCallExpr.java
index d6d715c..7950d4c 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/FunctionCallExpr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/FunctionCallExpr.java
@@ -483,6 +483,9 @@ public class FunctionCallExpr extends Expr {
     if (type_.isWildcardChar() || type_.isWildcardVarchar()) {
       type_ = ScalarType.STRING;
     }
+
+    // TODO(tmarshall): Differentiate based on the specific function.
+    if (hasChildCosts()) evalCost_ = getChildCosts() + FUNCTION_CALL_COST;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/InPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/InPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/InPredicate.java
index e990630..f617951 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/InPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/InPredicate.java
@@ -179,6 +179,11 @@ public class InPredicate extends Predicate {
           / (double) slotRefRef.getRef().getNumDistinctValues();
       selectivity_ = Math.max(0.0, Math.min(1.0, selectivity_));
     }
+
+    if (hasChildCosts()) {
+      // BINARY_PREDICATE_COST accounts for the cost of performing the comparison.
+      evalCost_ = getChildCosts() + BINARY_PREDICATE_COST * (children_.size() - 1);
+    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/IsNotEmptyPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/IsNotEmptyPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/IsNotEmptyPredicate.java
index 43e545d..d8f868c 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/IsNotEmptyPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/IsNotEmptyPredicate.java
@@ -46,6 +46,7 @@ public class IsNotEmptyPredicate extends Predicate {
     }
     // Avoid influencing cardinality estimates.
     selectivity_ = 1.0;
+    if (getChild(0).hasCost()) evalCost_ = getChild(0).getCost() + IS_NOT_EMPTY_COST;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/IsNullPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/IsNullPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/IsNullPredicate.java
index 1fe87d5..e4906ac 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/IsNullPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/IsNullPredicate.java
@@ -133,6 +133,7 @@ public class IsNullPredicate extends Predicate {
       fn_ = getBuiltinFunction(
           analyzer, IS_NULL, collectChildReturnTypes(), CompareMode.IS_IDENTICAL);
     }
+    if (getChild(0).hasCost()) evalCost_ = getChild(0).getCost() + IS_NULL_COST;
 
     // determine selectivity
     // TODO: increase this to make sure we don't end up favoring broadcast joins

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/LikePredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/LikePredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/LikePredicate.java
index 5f2b127..8821009 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/LikePredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/LikePredicate.java
@@ -141,6 +141,22 @@ public class LikePredicate extends Predicate {
       }
     }
     castForFunctionCall(false);
+
+    if (hasChildCosts()) {
+      if (getChild(1).isLiteral() && !getChild(1).isNullLiteral() &&
+          Pattern.matches("[%_]*[^%_]*[%_]*", ((StringLiteral) getChild(1)).getValue())) {
+        // This pattern only has wildcards as leading or trailing character,
+        // so it is linear.
+        evalCost_ = getChildCosts() +
+            (float) (getAvgStringLength(getChild(0)) + getAvgStringLength(getChild(1)) *
+            BINARY_PREDICATE_COST) + LIKE_COST;
+      } else {
+        // This pattern is more expensive, so calculate its cost as quadratic.
+        evalCost_ = getChildCosts() +
+            (float) (getAvgStringLength(getChild(0)) * getAvgStringLength(getChild(1)) *
+            BINARY_PREDICATE_COST) + LIKE_COST;
+      }
+    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/NullLiteral.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/NullLiteral.java b/fe/src/main/java/com/cloudera/impala/analysis/NullLiteral.java
index 306eb53..1f948eb 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/NullLiteral.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/NullLiteral.java
@@ -24,6 +24,7 @@ public class NullLiteral extends LiteralExpr {
 
   public NullLiteral() {
     type_ = Type.NULL;
+    evalCost_ = LITERAL_COST;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/NumericLiteral.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/NumericLiteral.java b/fe/src/main/java/com/cloudera/impala/analysis/NumericLiteral.java
index fa47838..fee35ab 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/NumericLiteral.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/NumericLiteral.java
@@ -85,6 +85,7 @@ public class NumericLiteral extends LiteralExpr {
     isAnalyzed_ = true;
     value_ = new BigDecimal(value);
     type_ = type;
+    evalCost_ = LITERAL_COST;
     explicitlyCast_ = true;
   }
 
@@ -92,6 +93,7 @@ public class NumericLiteral extends LiteralExpr {
     isAnalyzed_ = true;
     value_ = value;
     type_ = type;
+    evalCost_ = LITERAL_COST;
     explicitlyCast_ = true;
   }
 
@@ -204,6 +206,7 @@ public class NumericLiteral extends LiteralExpr {
         }
       }
     }
+    evalCost_ = LITERAL_COST;
     isAnalyzed_ = true;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/SlotRef.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/SlotRef.java b/fe/src/main/java/com/cloudera/impala/analysis/SlotRef.java
index 091d00f..ac0b5e0 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/SlotRef.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/SlotRef.java
@@ -71,6 +71,7 @@ public class SlotRef extends Expr {
     isAnalyzed_ = true;
     desc_ = desc;
     type_ = desc.getType();
+    evalCost_ = SLOT_REF_COST;
     String alias = desc.getParent().getAlias();
     label_ = (alias != null ? alias + "." : "") + desc.getLabel();
     numDistinctValues_ = desc.getStats().getNumDistinctValues();
@@ -112,6 +113,7 @@ public class SlotRef extends Expr {
       // HMS string.
       throw new AnalysisException("Unsupported type in '" + toSql() + "'.");
     }
+    evalCost_ = SLOT_REF_COST;
 
     numDistinctValues_ = desc_.getStats().getNumDistinctValues();
     Table rootTable = resolvedPath.getRootTable();

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/StringLiteral.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/StringLiteral.java b/fe/src/main/java/com/cloudera/impala/analysis/StringLiteral.java
index 2a99aa9..43fad31 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/StringLiteral.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/StringLiteral.java
@@ -37,11 +37,13 @@ public class StringLiteral extends LiteralExpr {
   public StringLiteral(String value) {
     this.value_ = value;
     type_ = ScalarType.STRING;
+    evalCost_ = LITERAL_COST;
   }
 
   public StringLiteral(String value, Type type) {
     this.value_ = value;
     type_ = type;
+    evalCost_ = LITERAL_COST;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/TimestampArithmeticExpr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/TimestampArithmeticExpr.java b/fe/src/main/java/com/cloudera/impala/analysis/TimestampArithmeticExpr.java
index 7f7a904..9e487dd 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/TimestampArithmeticExpr.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/TimestampArithmeticExpr.java
@@ -164,6 +164,7 @@ public class TimestampArithmeticExpr extends Expr {
     Preconditions.checkNotNull(fn_);
     Preconditions.checkState(fn_.getReturnType().isTimestamp());
     type_ = fn_.getReturnType();
+    if (hasChildCosts()) evalCost_ = getChildCosts() + TIMESTAMP_ARITHMETIC_COST;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/analysis/TupleIsNullPredicate.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/TupleIsNullPredicate.java b/fe/src/main/java/com/cloudera/impala/analysis/TupleIsNullPredicate.java
index 10b1125..2267324 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/TupleIsNullPredicate.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/TupleIsNullPredicate.java
@@ -58,6 +58,7 @@ public class TupleIsNullPredicate extends Predicate {
     if (isAnalyzed_) return;
     super.analyze(analyzer);
     analyzer_ = analyzer;
+    evalCost_ = tupleIds_.size() * IS_NULL_COST;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/AggregationNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/AggregationNode.java b/fe/src/main/java/com/cloudera/impala/planner/AggregationNode.java
index 1a96a68..5600bf7 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/AggregationNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/AggregationNode.java
@@ -144,6 +144,7 @@ public class AggregationNode extends PlanNode {
 
       analyzer.createEquivConjuncts(tupleIds_.get(0), conjuncts_, groupBySlots);
     }
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
     // Compute the mem layout for both tuples here for simplicity.
     aggInfo_.getOutputTupleDesc().computeMemLayout();
     aggInfo_.getIntermediateTupleDesc().computeMemLayout();

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/AnalyticEvalNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/AnalyticEvalNode.java b/fe/src/main/java/com/cloudera/impala/planner/AnalyticEvalNode.java
index 569f33c..3ee6be6 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/AnalyticEvalNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/AnalyticEvalNode.java
@@ -96,6 +96,7 @@ public class AnalyticEvalNode extends PlanNode {
 
   @Override
   public void init(Analyzer analyzer) {
+    Preconditions.checkState(conjuncts_.isEmpty());
     computeMemLayout(analyzer);
     intermediateTupleDesc_.computeMemLayout();
 

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/DataSourceScanNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/DataSourceScanNode.java b/fe/src/main/java/com/cloudera/impala/planner/DataSourceScanNode.java
index ecdc5a5..63e3355 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/DataSourceScanNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/DataSourceScanNode.java
@@ -94,6 +94,7 @@ public class DataSourceScanNode extends ScanNode {
     assignConjuncts(analyzer);
     analyzer.createEquivConjuncts(tupleIds_.get(0), conjuncts_);
     prepareDataSource();
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
     computeStats(analyzer);
     // materialize slots in remaining conjuncts_
     analyzer.materializeSlots(conjuncts_);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java b/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java
index c555a42..6a59f6f 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java
@@ -45,6 +45,7 @@ public class EmptySetNode extends PlanNode {
 
   @Override
   public void init(Analyzer analyzer) {
+    Preconditions.checkState(conjuncts_.isEmpty());
     // If the physical output tuple produced by an AnalyticEvalNode wasn't created
     // the logical output tuple is returned by getMaterializedTupleIds(). It needs
     // to be set as materialized (even though it isn't) to avoid failing precondition

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/ExchangeNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/ExchangeNode.java b/fe/src/main/java/com/cloudera/impala/planner/ExchangeNode.java
index e5e3153..22601b5 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/ExchangeNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/ExchangeNode.java
@@ -21,6 +21,7 @@ import com.cloudera.impala.analysis.Analyzer;
 import com.cloudera.impala.analysis.Expr;
 import com.cloudera.impala.analysis.SortInfo;
 import com.cloudera.impala.analysis.TupleId;
+import com.cloudera.impala.common.ImpalaException;
 import com.cloudera.impala.thrift.TExchangeNode;
 import com.cloudera.impala.thrift.TExplainLevel;
 import com.cloudera.impala.thrift.TPlanNode;
@@ -64,6 +65,12 @@ public class ExchangeNode extends PlanNode {
     offset_ = 0;
   }
 
+  @Override
+  public void init(Analyzer analyzer) throws ImpalaException {
+    super.init(analyzer);
+    Preconditions.checkState(conjuncts_.isEmpty());
+  }
+
   public void addChild(PlanNode node, boolean copyConjuncts) {
     // This ExchangeNode 'inherits' several parameters from its children.
     // Ensure that all children agree on them.

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java b/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java
index 1d813ba..aa6516a 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java
@@ -117,6 +117,7 @@ public class HBaseScanNode extends ScanNode {
   public void init(Analyzer analyzer) throws ImpalaException {
     checkForSupportedFileFormats();
     assignConjuncts(analyzer);
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
     setStartStopKey(analyzer);
     // Convert predicates to HBase filters_.
     createHBaseFilters(analyzer);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/HashJoinNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/HashJoinNode.java b/fe/src/main/java/com/cloudera/impala/planner/HashJoinNode.java
index 1caae3b..10e0460 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/HashJoinNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/HashJoinNode.java
@@ -93,10 +93,13 @@ public class HashJoinNode extends JoinNode {
       }
       Preconditions.checkState(
           eqPred.getChild(0).getType().matchesType(eqPred.getChild(1).getType()));
-      newEqJoinConjuncts.add(new BinaryPredicate(eqPred.getOp(),
-          eqPred.getChild(0), eqPred.getChild(1)));
+      BinaryPredicate newEqPred = new BinaryPredicate(eqPred.getOp(),
+          eqPred.getChild(0), eqPred.getChild(1));
+      newEqPred.analyze(analyzer);
+      newEqJoinConjuncts.add(newEqPred);
     }
     eqJoinConjuncts_ = newEqJoinConjuncts;
+    orderJoinConjunctsByCost();
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/HdfsScanNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/HdfsScanNode.java b/fe/src/main/java/com/cloudera/impala/planner/HdfsScanNode.java
index 648bb2b..60dccc5 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/HdfsScanNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/HdfsScanNode.java
@@ -159,6 +159,7 @@ public class HdfsScanNode extends ScanNode {
    */
   @Override
   public void init(Analyzer analyzer) throws ImpalaException {
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
     checkForSupportedFileFormats();
 
     assignCollectionConjuncts(analyzer);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/JoinNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/JoinNode.java b/fe/src/main/java/com/cloudera/impala/planner/JoinNode.java
index 8a7da23..19e4724 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/JoinNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/JoinNode.java
@@ -450,4 +450,10 @@ public abstract class JoinNode extends PlanNode {
     if (distrMode_ != DistributionMode.NONE) output.append(", " + distrMode_.toString());
     return output.toString();
   }
+
+  protected void orderJoinConjunctsByCost() {
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
+    eqJoinConjuncts_ = orderConjunctsByCost(eqJoinConjuncts_);
+    otherJoinConjuncts_ = orderConjunctsByCost(otherJoinConjuncts_);
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/KuduScanNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/KuduScanNode.java b/fe/src/main/java/com/cloudera/impala/planner/KuduScanNode.java
index 99e1a19..eb57b82 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/KuduScanNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/KuduScanNode.java
@@ -83,6 +83,7 @@ public class KuduScanNode extends ScanNode {
   public void init(Analyzer analyzer) throws InternalException {
     assignConjuncts(analyzer);
     analyzer.createEquivConjuncts(tupleIds_.get(0), conjuncts_);
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
 
     // Extract predicates that can be evaluated by Kudu.
     try {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/NestedLoopJoinNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/NestedLoopJoinNode.java b/fe/src/main/java/com/cloudera/impala/planner/NestedLoopJoinNode.java
index 2e86bae..474a60c 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/NestedLoopJoinNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/NestedLoopJoinNode.java
@@ -67,6 +67,7 @@ public class NestedLoopJoinNode extends JoinNode {
       // A cross join with predicates is an inner join.
       joinOp_ = JoinOperator.INNER_JOIN;
     }
+    orderJoinConjunctsByCost();
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/PlanNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/PlanNode.java b/fe/src/main/java/com/cloudera/impala/planner/PlanNode.java
index 643ca01..c393b4f 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/PlanNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/PlanNode.java
@@ -625,4 +625,68 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
     output.append(Joiner.on(", ").join(filtersStr) + "\n");
     return output.toString();
   }
+
+  /**
+   * Sort a list of conjuncts into an estimated cheapest order to evaluate them in, based
+   * on estimates of the cost to evaluate and selectivity of the expressions. Should be
+   * called during PlanNode.init for any PlanNode that could have a conjunct list.
+   *
+   * The conjuncts are sorted by repeatedly iterating over them and choosing the conjunct
+   * that would result in the least total estimated work were it to be applied before the
+   * remaining conjuncts.
+   *
+   * As in computeCombinedSelecivity, the selectivities are exponentially backed off over
+   * the iterations, to reflect the possibility that the conjuncts may be correlated, and
+   * Exprs without selectivity estimates are given a reasonable default.
+   */
+  public static <T extends Expr> List<T> orderConjunctsByCost(List<T> conjuncts) {
+    if (conjuncts.size() <= 1) return conjuncts;
+
+    float totalCost = 0;
+    int numWithoutSel = 0;
+    List<T> remaining = Lists.newArrayListWithCapacity(conjuncts.size());
+    for (T e : conjuncts) {
+      Preconditions.checkState(e.hasCost());
+      totalCost += e.getCost();
+      remaining.add(e);
+      if (!e.hasSelectivity()) {
+        ++numWithoutSel;
+      }
+    }
+
+    // We distribute the DEFAULT_SELECTIVITY over the conjuncts without a selectivity
+    // estimate so that their combined selectivities equal DEFAULT_SELECTIVITY, i.e.
+    // Math.pow(defaultSel, numWithoutSel) = Expr.DEFAULT_SELECTIVITY
+    double defaultSel = Expr.DEFAULT_SELECTIVITY;
+    if (numWithoutSel != 0) {
+      defaultSel = Math.pow(Math.E, Math.log(Expr.DEFAULT_SELECTIVITY) / numWithoutSel);
+    }
+
+    List<T> sortedConjuncts = Lists.newArrayListWithCapacity(conjuncts.size());
+    while (!remaining.isEmpty()) {
+      double smallestCost = Float.MAX_VALUE;
+      T bestConjunct =  null;
+      double backoffExp = 1.0 / (double) (sortedConjuncts.size() + 1);
+      for (T e : remaining) {
+        double sel = Math.pow(e.hasSelectivity() ? e.getSelectivity() : defaultSel,
+            backoffExp);
+
+        // The cost of evaluating this conjunct first is estimated as the cost of
+        // applying this conjunct to all rows plus the cost of applying all the
+        // remaining conjuncts to the number of rows we expect to remain given
+        // this conjunct's selectivity, exponentially backed off.
+        double cost = e.getCost() + (totalCost - e.getCost()) * sel;
+        if (cost < smallestCost) {
+          smallestCost = cost;
+          bestConjunct = e;
+        }
+      }
+
+      sortedConjuncts.add(bestConjunct);
+      remaining.remove(bestConjunct);
+      totalCost -= bestConjunct.getCost();
+    }
+
+    return sortedConjuncts;
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/SelectNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/SelectNode.java b/fe/src/main/java/com/cloudera/impala/planner/SelectNode.java
index 098091b..e6d7bf1 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/SelectNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/SelectNode.java
@@ -48,6 +48,7 @@ public class SelectNode extends PlanNode {
   @Override
   public void init(Analyzer analyzer) {
     analyzer.markConjunctsAssigned(conjuncts_);
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
     computeStats(analyzer);
     createDefaultSmap(analyzer);
   }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/SortNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/SortNode.java b/fe/src/main/java/com/cloudera/impala/planner/SortNode.java
index 78794c2..cd5f58c 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/SortNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/SortNode.java
@@ -90,6 +90,7 @@ public class SortNode extends PlanNode {
   @Override
   public void init(Analyzer analyzer) throws InternalException {
     assignConjuncts(analyzer);
+    Preconditions.checkState(conjuncts_.isEmpty());
     // Compute the memory layout for the generated tuple.
     computeMemLayout(analyzer);
     computeStats(analyzer);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java b/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java
index 7f05c40..171451f 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java
@@ -162,6 +162,7 @@ public class UnionNode extends PlanNode {
    */
   @Override
   public void init(Analyzer analyzer) {
+    Preconditions.checkState(conjuncts_.isEmpty());
     computeMemLayout(analyzer);
     computeStats(analyzer);
 

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/main/java/com/cloudera/impala/planner/UnnestNode.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/planner/UnnestNode.java b/fe/src/main/java/com/cloudera/impala/planner/UnnestNode.java
index 91bcc92..a817b03 100644
--- a/fe/src/main/java/com/cloudera/impala/planner/UnnestNode.java
+++ b/fe/src/main/java/com/cloudera/impala/planner/UnnestNode.java
@@ -53,6 +53,7 @@ public class UnnestNode extends PlanNode {
     // because they must have been assigned in the scan node materializing the
     // collection-typed slot.
     super.init(analyzer);
+    conjuncts_ = orderConjunctsByCost(conjuncts_);
 
     // Unnest is like a scan and must materialize the slots of its conjuncts.
     analyzer.materializeSlots(conjuncts_);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java b/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java
index 30eff54..c5df28b 100644
--- a/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java
+++ b/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java
@@ -222,4 +222,9 @@ public class PlannerTest extends PlannerTestBase {
     options.setRuntime_filter_mode(TRuntimeFilterMode.GLOBAL);
     runPlannerTestFile("runtime-filter-propagation", options);
   }
+
+  @Test
+  public void testConjunctOrdering() {
+    runPlannerTestFile("conjunct-ordering");
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/analytic-fns.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/analytic-fns.test b/testdata/workloads/functional-planner/queries/PlannerTest/analytic-fns.test
index dcd6b0b..bbabef0 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/analytic-fns.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/analytic-fns.test
@@ -1248,7 +1248,7 @@ where
   v.b != v.c
 ---- PLAN
 07:SELECT
-|  predicates: bigint_col > 10, min(int_col) < 1, min(int_col) < bigint_col + 1, max(int_col) < 2, max(int_col) < bigint_col + 2, count(int_col) < 3, count(int_col) < bigint_col + 3, sum(int_col) < 4, sum(int_col) < bigint_col + 4, avg(int_col) < 5, avg(int_col) < bigint_col + 5, min(int_col) != count(int_col), min(int_col) != avg(int_col), max(int_col) != count(int_col)
+|  predicates: bigint_col > 10, min(int_col) < 1, max(int_col) < 2, count(int_col) < 3, sum(int_col) < 4, avg(int_col) < 5, min(int_col) != count(int_col), min(int_col) != avg(int_col), max(int_col) != count(int_col), count(int_col) < bigint_col + 3, sum(int_col) < bigint_col + 4, min(int_col) < bigint_col + 1, max(int_col) < bigint_col + 2, avg(int_col) < bigint_col + 5
 |
 06:ANALYTIC
 |  functions: min(int_col)
@@ -1280,7 +1280,7 @@ where
    predicates: int_col >= 5, int_col <= 10
 ---- DISTRIBUTEDPLAN
 07:SELECT
-|  predicates: bigint_col > 10, min(int_col) < 1, min(int_col) < bigint_col + 1, max(int_col) < 2, max(int_col) < bigint_col + 2, count(int_col) < 3, count(int_col) < bigint_col + 3, sum(int_col) < 4, sum(int_col) < bigint_col + 4, avg(int_col) < 5, avg(int_col) < bigint_col + 5, min(int_col) != count(int_col), min(int_col) != avg(int_col), max(int_col) != count(int_col)
+|  predicates: bigint_col > 10, min(int_col) < 1, max(int_col) < 2, count(int_col) < 3, sum(int_col) < 4, avg(int_col) < 5, min(int_col) != count(int_col), min(int_col) != avg(int_col), max(int_col) != count(int_col), count(int_col) < bigint_col + 3, sum(int_col) < bigint_col + 4, min(int_col) < bigint_col + 1, max(int_col) < bigint_col + 2, avg(int_col) < bigint_col + 5
 |
 06:ANALYTIC
 |  functions: min(int_col)
@@ -1818,7 +1818,7 @@ select * from
 where year = 2009 and tinyint_col = 1
 ---- PLAN
 07:SELECT
-|  predicates: year = 2009, tinyint_col = 1
+|  predicates: tinyint_col = 1, year = 2009
 |
 06:ANALYTIC
 |  functions: lead(int_col, 1, NULL)
@@ -1871,7 +1871,7 @@ where month = int_col and int_col = 1 and tinyint_col = 1
 |
 00:SCAN HDFS [functional.alltypestiny]
    partitions=1/4 files=1 size=115B
-   predicates: id = tinyint_col, functional.alltypestiny.tinyint_col = 1, functional.alltypestiny.id = 1
+   predicates: functional.alltypestiny.id = 1, id = tinyint_col, functional.alltypestiny.tinyint_col = 1
 ====
 # Don't propagate predicates through an inline view with an analytic
 # function that has a complex (non SlotRef) partition by clause for consistency with

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/conjunct-ordering.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/conjunct-ordering.test b/testdata/workloads/functional-planner/queries/PlannerTest/conjunct-ordering.test
new file mode 100644
index 0000000..1dc6a3e
--- /dev/null
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/conjunct-ordering.test
@@ -0,0 +1,147 @@
+# Check that checking a bool value costs less than a numeric comparison.
+select *
+from functional.alltypes a
+where a.int_col = a.tinyint_col and
+      a.bool_col
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.bool_col, a.int_col = a.tinyint_col
+====
+# Check that numeric comparison costs less than LIKE.
+select *
+from functional.alltypes a
+where a.string_col LIKE '%a%' and
+      a.int_col = a.tinyint_col
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = a.tinyint_col, a.string_col LIKE '%a%'
+====
+# Check that single numeric comparison costs less than compound numeric comparison.
+select *
+from functional.alltypes a
+where (a.int_col = a.tinyint_col or a.int_col = a.smallint_col) and
+      a.int_col = a.bigint_col
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = a.bigint_col, (a.int_col = a.tinyint_col OR a.int_col = a.smallint_col)
+====
+# Check that a simple numeric comparison costs less than one with arithmetic.
+select *
+from functional.alltypes a
+where a.int_col + 5 = a.bigint_col - 10 and a.int_col = a.tinyint_col
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = a.tinyint_col, a.int_col + 5 = a.bigint_col - 10
+====
+# Check that large CASE costs more than numeric comparison.
+select *
+from functional.alltypes a
+where a.int_col = a.tinyint_col and
+      (case a.int_col when 0 then true when 1 then true when 2 then true else false end)
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = a.tinyint_col, (CASE a.tinyint_col WHEN 0 THEN TRUE WHEN 1 THEN TRUE WHEN 2 THEN TRUE ELSE FALSE END), (CASE a.int_col WHEN 0 THEN TRUE WHEN 1 THEN TRUE WHEN 2 THEN TRUE ELSE FALSE END)
+====
+# Check that a LIKE with only leading/trailing wildcards costs less then LIKE with
+# non-leading/trailing wildcards.
+select *
+from functional.alltypes a
+where a.date_string_col LIKE 'a%a' and a.date_string_col LIKE '%a%'
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.date_string_col LIKE '%a%', a.date_string_col LIKE 'a%a'
+====
+# Check that an IN predicate costs more than a single numeric comparison.
+select *
+from functional.alltypes a
+where a.int_col IN (1, 2, 3, 4, 5, 6, 7, 8, 9) and a.int_col = 1
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = 1, a.int_col IN (1, 2, 3, 4, 5, 6, 7, 8, 9)
+====
+# Check that a timestamp comparison costs more than a numeric comparison.
+select *
+from functional.alltypes a
+where a.timestamp_col > '2000-01-01' and a.int_col = 0
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col = 0, a.timestamp_col > '2000-01-01'
+====
+# Check that string comparisons are ordered by string length.
+select *
+from functional.alltypes a
+where a.string_col = "looooooooooooooooong string" and
+      a.string_col = "medium string" and
+      a.string_col = "a"
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.string_col = 'a', a.string_col = 'medium string', a.string_col = 'looooooooooooooooong string'
+====
+# Check that timestamp arithmetic adds cost.
+select *
+from functional.alltypes a
+where a.timestamp_col - interval 1 day > '2000-01-01' and
+      a.timestamp_col < '2020-01-01'
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.timestamp_col < '2020-01-01', a.timestamp_col - INTERVAL 1 day > '2000-01-01'
+====
+# Check that a function call adds cost.
+select *
+from functional.alltypes a
+where ceil(a.double_col) > 0 and a.double_col > 0
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.double_col > 0, ceil(a.double_col) > 0
+====
+# Check that a cast adds cost.
+select *
+from functional.alltypes a
+where cast(a.int_col as double) > 0 and a.int_col > 0
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col > 0, CAST(a.int_col AS DOUBLE) > 0
+====
+# Check that is null costs less than string comparison.
+select *
+from functional.alltypes a
+where a.string_col = "string" and a.int_col is null
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.int_col IS NULL, a.string_col = 'string'
+====
+# Check that long list of predicates is sorted correctly.
+select *
+from functional.alltypes a
+where a.string_col LIKE '%a%' and
+      a.bool_col and
+      (a.int_col = a.tinyint_col or a.int_col = a.smallint_col) and
+      a.int_col = a.bigint_col
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.bool_col, a.int_col = a.bigint_col, (a.int_col = a.tinyint_col OR a.int_col = a.smallint_col), a.string_col LIKE '%a%'
+====
+# Check that for two equal cost conjuncts, the one with the higher selectivity goes first.
+# There are more distinct id values, so it is more selective.
+select *
+from functional.alltypes a
+where a.int_col = 0 and a.id = 0
+---- PLAN
+00:SCAN HDFS [functional.alltypes a]
+   partitions=24/24 files=24 size=478.45KB
+   predicates: a.id = 0, a.int_col = 0
+====

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/data-source-tables.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/data-source-tables.test b/testdata/workloads/functional-planner/queries/PlannerTest/data-source-tables.test
index 5d07883..270c8f6 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/data-source-tables.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/data-source-tables.test
@@ -24,7 +24,7 @@ where 10 > int_col and
 ---- PLAN
 00:SCAN DATA SOURCE [functional.alltypes_datasource]
 data source predicates: 10 > int_col, string_col != 'Foo'
-predicates: 5 > double_col, string_col != 'Bar', NOT TRUE = bool_col, NOT 5.0 = double_col
+predicates: 5 > double_col, NOT TRUE = bool_col, NOT 5.0 = double_col, string_col != 'Bar'
 ====
 # The 3rd predicate is not in a form that can be offered to the data source so
 # the 4th will be offered and accepted instead.
@@ -58,7 +58,7 @@ where a.tinyint_col = a.smallint_col and a.int_col = a.bigint_col
 |--predicates: b.id = b.int_col, b.id = b.bigint_col
 |
 00:SCAN DATA SOURCE [functional.alltypes_datasource a]
-predicates: a.tinyint_col = a.smallint_col, a.int_col = a.bigint_col, a.id = a.tinyint_col, a.id = a.int_col
+predicates: a.id = a.int_col, a.tinyint_col = a.smallint_col, a.int_col = a.bigint_col, a.id = a.tinyint_col
 ====
 # Tests that <=>, IS DISTINCT FROM, and IS NOT DISTINCT FROM all can be offered to the
 # data source.

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/distinct.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/distinct.test b/testdata/workloads/functional-planner/queries/PlannerTest/distinct.test
index 540ccf1..15f1d59 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/distinct.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/distinct.test
@@ -400,7 +400,7 @@ where t.x + t.y > 10 and t.x > 0 and t.y > 1
 ---- PLAN
 02:AGGREGATE [FINALIZE]
 |  output: count(1), count:merge(1)
-|  having: count(1) > 0, count(1) + zeroifnull(count(1)) > 10, zeroifnull(count(1)) > 1
+|  having: count(1) > 0, zeroifnull(count(1)) > 1, count(1) + zeroifnull(count(1)) > 10
 |
 01:AGGREGATE
 |  output: count(1)
@@ -411,7 +411,7 @@ where t.x + t.y > 10 and t.x > 0 and t.y > 1
 ---- DISTRIBUTEDPLAN
 06:AGGREGATE [FINALIZE]
 |  output: count:merge(1), count:merge(1)
-|  having: count(1) > 0, count(1) + zeroifnull(count(1)) > 10, zeroifnull(count(1)) > 1
+|  having: count(1) > 0, zeroifnull(count(1)) > 1, count(1) + zeroifnull(count(1)) > 10
 |
 05:EXCHANGE [UNPARTITIONED]
 |

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/hbase.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/hbase.test b/testdata/workloads/functional-planner/queries/PlannerTest/hbase.test
index 28bf1f2..1f9e7ca 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/hbase.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/hbase.test
@@ -306,7 +306,7 @@ select * from functional_hbase.alltypessmall where string_col = '4' and tinyint_
 ---- PLAN
 00:SCAN HBASE [functional_hbase.alltypessmall]
    hbase filters: d:string_col EQUAL '4'
-   predicates: string_col = '4', tinyint_col = 5
+   predicates: tinyint_col = 5, string_col = '4'
 ====
 # mix of predicates, functional_hbase. filters and start/stop keys
 select * from functional_hbase.stringids
@@ -316,7 +316,7 @@ where string_col = '4' and tinyint_col = 5 and id >= '4' and id <= '5'
    start key: 4
    stop key: 5\0
    hbase filters: d:string_col EQUAL '4'
-   predicates: string_col = '4', tinyint_col = 5
+   predicates: tinyint_col = 5, string_col = '4'
 ---- SCANRANGELOCATIONS
 NODE 0:
   HBASE KEYRANGE port=16202 4:5\0
@@ -327,7 +327,7 @@ NODE 0:
    start key: 4
    stop key: 5\0
    hbase filters: d:string_col EQUAL '4'
-   predicates: string_col = '4', tinyint_col = 5
+   predicates: tinyint_col = 5, string_col = '4'
 ====
 # predicates involving casts (ie, non-string comparisons) cannot be turned into filters
 select * from functional_hbase.alltypessmall where cast(string_col as int) >= 4
@@ -360,7 +360,7 @@ where string_col = '4' and tinyint_col = 5
    start key: 4
    stop key: 5\0
    hbase filters: d:string_col EQUAL '4'
-   predicates: string_col = '4', tinyint_col = 5
+   predicates: tinyint_col = 5, string_col = '4'
 ---- SCANRANGELOCATIONS
 NODE 0:
   HBASE KEYRANGE port=16202 4:5\0
@@ -371,7 +371,7 @@ NODE 0:
    start key: 4
    stop key: 5\0
    hbase filters: d:string_col EQUAL '4'
-   predicates: string_col = '4', tinyint_col = 5
+   predicates: tinyint_col = 5, string_col = '4'
 ====
 # IMP-1188 - row key predicate is null.
 select * from functional_hbase.stringids where id = null

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/inline-view.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/inline-view.test b/testdata/workloads/functional-planner/queries/PlannerTest/inline-view.test
index 43537d4..2332d4e 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/inline-view.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/inline-view.test
@@ -123,7 +123,7 @@ from
 ---- PLAN
 02:HASH JOIN [RIGHT OUTER JOIN]
 |  hash predicates: a.id = b.id, a.int_col = b.int_col
-|  other predicates: a.day >= 6, a.tinyint_col = 15, a.tinyint_col + b.tinyint_col < 15
+|  other predicates: a.tinyint_col = 15, a.day >= 6, a.tinyint_col + b.tinyint_col < 15
 |  runtime filters: RF000 <- b.id, RF001 <- b.int_col
 |
 |--01:SCAN HDFS [functional.alltypessmall b]
@@ -149,7 +149,7 @@ NODE 1:
 |
 02:HASH JOIN [RIGHT OUTER JOIN, PARTITIONED]
 |  hash predicates: a.id = b.id, a.int_col = b.int_col
-|  other predicates: a.day >= 6, a.tinyint_col = 15, a.tinyint_col + b.tinyint_col < 15
+|  other predicates: a.tinyint_col = 15, a.day >= 6, a.tinyint_col + b.tinyint_col < 15
 |  runtime filters: RF000 <- b.id, RF001 <- b.int_col
 |
 |--04:EXCHANGE [HASH(b.id,b.int_col)]
@@ -190,7 +190,7 @@ and b.id + 15 = 27
 ---- PLAN
 02:HASH JOIN [RIGHT OUTER JOIN]
 |  hash predicates: id = id, int_col = int_col
-|  other predicates: day >= 6, tinyint_col = 15, tinyint_col + tinyint_col < 15
+|  other predicates: tinyint_col = 15, day >= 6, tinyint_col + tinyint_col < 15
 |  runtime filters: RF000 <- id, RF001 <- int_col
 |
 |--01:SCAN HDFS [functional.alltypessmall]
@@ -206,7 +206,7 @@ and b.id + 15 = 27
 |
 02:HASH JOIN [RIGHT OUTER JOIN, PARTITIONED]
 |  hash predicates: id = id, int_col = int_col
-|  other predicates: day >= 6, tinyint_col = 15, tinyint_col + tinyint_col < 15
+|  other predicates: tinyint_col = 15, day >= 6, tinyint_col + tinyint_col < 15
 |  runtime filters: RF000 <- id, RF001 <- int_col
 |
 |--04:EXCHANGE [HASH(id,int_col)]
@@ -579,7 +579,7 @@ and   c2 > 10
 01:AGGREGATE [FINALIZE]
 |  output: count(*), avg(functional.alltypesagg.int_col)
 |  group by: functional.alltypesagg.int_col % 7
-|  having: avg(int_col) > 500 OR count(*) = 10, int_col % 7 IS NOT NULL, count(*) > 10
+|  having: int_col % 7 IS NOT NULL, count(*) > 10, avg(int_col) > 500 OR count(*) = 10
 |
 00:SCAN HDFS [functional.alltypesagg]
    partitions=11/11 files=11 size=814.73KB
@@ -589,7 +589,7 @@ and   c2 > 10
 03:AGGREGATE [FINALIZE]
 |  output: count:merge(*), avg:merge(int_col)
 |  group by: int_col % 7
-|  having: avg(int_col) > 500 OR count(*) = 10, int_col % 7 IS NOT NULL, count(*) > 10
+|  having: int_col % 7 IS NOT NULL, count(*) > 10, avg(int_col) > 500 OR count(*) = 10
 |
 02:EXCHANGE [HASH(int_col % 7)]
 |

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/join-order.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/join-order.test b/testdata/workloads/functional-planner/queries/PlannerTest/join-order.test
index 022938e..ff10ac7 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/join-order.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/join-order.test
@@ -1320,7 +1320,7 @@ where a.int_col = b.int_col and b.bigint_col < a.tinyint_col
 ---- PLAN
 04:HASH JOIN [LEFT OUTER JOIN]
 |  hash predicates: c.id = b.id
-|  other predicates: a.int_col = b.int_col, b.bigint_col < a.tinyint_col, b.tinyint_col = c.tinyint_col, b.bool_col != c.bool_col
+|  other predicates: a.int_col = b.int_col, b.tinyint_col = c.tinyint_col, b.bool_col != c.bool_col, b.bigint_col < a.tinyint_col
 |  runtime filters: RF000 <- b.tinyint_col
 |
 |--03:HASH JOIN [RIGHT OUTER JOIN]

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/joins.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/joins.test b/testdata/workloads/functional-planner/queries/PlannerTest/joins.test
index ff0a349..2605470 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/joins.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/joins.test
@@ -122,7 +122,7 @@ and a.tinyint_col + b.tinyint_col < 15
 ---- PLAN
 02:HASH JOIN [RIGHT OUTER JOIN]
 |  hash predicates: a.id = b.id, a.int_col = b.int_col
-|  other predicates: a.day >= 6, a.tinyint_col = 15, a.tinyint_col + b.tinyint_col < 15
+|  other predicates: a.tinyint_col = 15, a.day >= 6, a.tinyint_col + b.tinyint_col < 15
 |  runtime filters: RF000 <- b.id, RF001 <- b.int_col
 |
 |--01:SCAN HDFS [functional.alltypessmall b]
@@ -138,7 +138,7 @@ and a.tinyint_col + b.tinyint_col < 15
 |
 02:HASH JOIN [RIGHT OUTER JOIN, PARTITIONED]
 |  hash predicates: a.id = b.id, a.int_col = b.int_col
-|  other predicates: a.day >= 6, a.tinyint_col = 15, a.tinyint_col + b.tinyint_col < 15
+|  other predicates: a.tinyint_col = 15, a.day >= 6, a.tinyint_col + b.tinyint_col < 15
 |  runtime filters: RF000 <- b.id, RF001 <- b.int_col
 |
 |--04:EXCHANGE [HASH(b.id,b.int_col)]
@@ -171,7 +171,7 @@ and (b.double_col * c.tinyint_col > 1000 or c.tinyint_col < 1000)
 ---- PLAN
 04:HASH JOIN [LEFT OUTER JOIN]
 |  hash predicates: c.id = a.id, c.string_col = b.string_col
-|  other predicates: a.day >= 6, b.month > 2, a.tinyint_col = 15, b.string_col = '15', a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
+|  other predicates: a.tinyint_col = 15, b.string_col = '15', a.day >= 6, b.month > 2, a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
 |
 |--03:HASH JOIN [FULL OUTER JOIN]
 |  |  hash predicates: a.id = b.id, a.int_col = b.int_col
@@ -191,7 +191,7 @@ and (b.double_col * c.tinyint_col > 1000 or c.tinyint_col < 1000)
 |
 04:HASH JOIN [LEFT OUTER JOIN, BROADCAST]
 |  hash predicates: c.id = a.id, c.string_col = b.string_col
-|  other predicates: a.day >= 6, b.month > 2, a.tinyint_col = 15, b.string_col = '15', a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
+|  other predicates: a.tinyint_col = 15, b.string_col = '15', a.day >= 6, b.month > 2, a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
 |
 |--07:EXCHANGE [BROADCAST]
 |  |
@@ -299,19 +299,19 @@ and b.tinyint_col > 123
 and a.tinyint_col + b.tinyint_col < 15
 ---- PLAN
 02:HASH JOIN [INNER JOIN]
-|  hash predicates: a.id = CAST(b.id AS INT), a.int_col = b.int_col
+|  hash predicates: a.int_col = b.int_col, a.id = CAST(b.id AS INT)
 |  other predicates: a.tinyint_col + b.tinyint_col < 15
-|  runtime filters: RF000 <- CAST(b.id AS INT), RF001 <- b.int_col
+|  runtime filters: RF000 <- b.int_col, RF001 <- CAST(b.id AS INT)
 |
 |--01:SCAN HBASE [functional_hbase.stringids b]
 |     start key: 5
 |     stop key: 5\0
-|     predicates: b.tinyint_col = 5, b.tinyint_col > 123
+|     predicates: b.tinyint_col > 123, b.tinyint_col = 5
 |
 00:SCAN HDFS [functional.alltypesagg a]
    partitions=5/11 files=5 size=372.38KB
    predicates: a.tinyint_col = 15
-   runtime filters: RF000 -> a.id, RF001 -> a.int_col
+   runtime filters: RF000 -> a.int_col, RF001 -> a.id
 ---- SCANRANGELOCATIONS
 NODE 0:
   HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=10/100110.txt 0:76263
@@ -325,21 +325,21 @@ NODE 1:
 04:EXCHANGE [UNPARTITIONED]
 |
 02:HASH JOIN [INNER JOIN, BROADCAST]
-|  hash predicates: a.id = CAST(b.id AS INT), a.int_col = b.int_col
+|  hash predicates: a.int_col = b.int_col, a.id = CAST(b.id AS INT)
 |  other predicates: a.tinyint_col + b.tinyint_col < 15
-|  runtime filters: RF000 <- CAST(b.id AS INT), RF001 <- b.int_col
+|  runtime filters: RF000 <- b.int_col, RF001 <- CAST(b.id AS INT)
 |
 |--03:EXCHANGE [BROADCAST]
 |  |
 |  01:SCAN HBASE [functional_hbase.stringids b]
 |     start key: 5
 |     stop key: 5\0
-|     predicates: b.tinyint_col = 5, b.tinyint_col > 123
+|     predicates: b.tinyint_col > 123, b.tinyint_col = 5
 |
 00:SCAN HDFS [functional.alltypesagg a]
    partitions=5/11 files=5 size=372.38KB
    predicates: a.tinyint_col = 15
-   runtime filters: RF000 -> a.id, RF001 -> a.int_col
+   runtime filters: RF000 -> a.int_col, RF001 -> a.id
 ====
 # left join followed by right join and then aggregate
 select x.tinyint_col, count(x.day)
@@ -793,7 +793,7 @@ where a.tinyint_col = a.smallint_col and a.int_col = a.bigint_col
 |
 |--00:SCAN HDFS [functional.alltypes a]
 |     partitions=24/24 files=24 size=478.45KB
-|     predicates: a.tinyint_col = a.smallint_col, a.int_col = a.bigint_col, a.id = a.tinyint_col, a.id = a.int_col
+|     predicates: a.id = a.int_col, a.tinyint_col = a.smallint_col, a.int_col = a.bigint_col, a.id = a.tinyint_col
 |
 01:SCAN HDFS [functional.alltypes b]
    partitions=24/24 files=24 size=478.45KB
@@ -813,8 +813,8 @@ and b.string_col = a.string_col and b.date_string_col = a.string_col
 where a.tinyint_col = a.smallint_col and a.int_col = a.bigint_col
 ---- PLAN
 02:HASH JOIN [RIGHT OUTER JOIN]
-|  hash predicates: b.id = a.id, b.int_col = a.id, b.bigint_col = a.id, b.id = a.tinyint_col, b.id = a.smallint_col, b.id = a.int_col, b.id = a.bigint_col, b.string_col = a.string_col, b.date_string_col = a.string_col
-|  runtime filters: RF000 <- a.id, RF001 <- a.id, RF002 <- a.id, RF003 <- a.tinyint_col, RF004 <- a.smallint_col, RF005 <- a.int_col, RF006 <- a.bigint_col, RF007 <- a.string_col, RF008 <- a.string_col
+|  hash predicates: b.id = a.id, b.int_col = a.id, b.id = a.int_col, b.bigint_col = a.id, b.id = a.tinyint_col, b.id = a.smallint_col, b.id = a.bigint_col, b.string_col = a.string_col, b.date_string_col = a.string_col
+|  runtime filters: RF000 <- a.id, RF001 <- a.id, RF002 <- a.int_col, RF003 <- a.id, RF004 <- a.tinyint_col, RF005 <- a.smallint_col, RF006 <- a.bigint_col, RF007 <- a.string_col, RF008 <- a.string_col
 |
 |--00:SCAN HDFS [functional.alltypes a]
 |     partitions=24/24 files=24 size=478.45KB
@@ -822,7 +822,7 @@ where a.tinyint_col = a.smallint_col and a.int_col = a.bigint_col
 |
 01:SCAN HDFS [functional.alltypes b]
    partitions=24/24 files=24 size=478.45KB
-   runtime filters: RF000 -> b.id, RF001 -> b.int_col, RF002 -> b.bigint_col, RF003 -> b.id, RF004 -> b.id, RF005 -> b.id, RF006 -> b.id, RF007 -> b.string_col, RF008 -> b.date_string_col
+   runtime filters: RF000 -> b.id, RF001 -> b.int_col, RF002 -> b.id, RF003 -> b.bigint_col, RF004 -> b.id, RF005 -> b.id, RF006 -> b.id, RF007 -> b.string_col, RF008 -> b.date_string_col
 ====
 # Tests elimination of redundant join predicates (IMPALA-912).
 select * from
@@ -994,7 +994,7 @@ on a.id = b.x and a.id = b.tinyint_col and
 |
 |--01:SCAN HDFS [functional.alltypessmall]
 |     partitions=4/4 files=4 size=6.32KB
-|     predicates: id + id = tinyint_col, int_col * int_col = bigint_col
+|     predicates: int_col * int_col = bigint_col, id + id = tinyint_col
 |
 00:SCAN HDFS [functional.alltypes a]
    partitions=24/24 files=24 size=478.45KB
@@ -1018,7 +1018,7 @@ on a.id = b.x and a.id = b.tinyint_col and
 |  runtime filters: RF000 <- id + id, RF001 <- int_col * int_col
 |
 |--03:SELECT
-|  |  predicates: id + id = tinyint_col, int_col * int_col = bigint_col
+|  |  predicates: int_col * int_col = bigint_col, id + id = tinyint_col
 |  |
 |  02:TOP-N [LIMIT=20]
 |  |  order by: tinyint_col ASC
@@ -1334,7 +1334,7 @@ where j.test_id < 10
 |
 |--01:SCAN HDFS [functional.dimtbl d]
 |     partitions=1/1 files=1 size=171B
-|     predicates: d.name = 'Name2', d.id < 10
+|     predicates: d.id < 10, d.name = 'Name2'
 |
 00:SCAN HDFS [functional.jointbl j]
    partitions=1/1 files=1 size=433B
@@ -1357,7 +1357,7 @@ where b.id < 10
 |
 00:SCAN HDFS [functional.alltypes a]
    partitions=24/24 files=24 size=478.45KB
-   predicates: a.bool_col = FALSE, a.id < 10
+   predicates: a.id < 10, a.bool_col = FALSE
    runtime filters: RF000 -> a.id
 ====
 # Test left anti join including correct predicate assignment and propagation
@@ -1645,8 +1645,8 @@ on (v1.string_col = v2.string_col and
 10:EXCHANGE [UNPARTITIONED]
 |
 04:HASH JOIN [INNER JOIN, PARTITIONED]
-|  hash predicates: string_col = string_col, bigint_col = bigint_col
-|  runtime filters: RF000 <- string_col, RF001 <- bigint_col
+|  hash predicates: bigint_col = bigint_col, string_col = string_col
+|  runtime filters: RF000 <- bigint_col, RF001 <- string_col
 |
 |--09:EXCHANGE [HASH(bigint_col,string_col)]
 |  |
@@ -1671,7 +1671,7 @@ on (v1.string_col = v2.string_col and
 |
 00:SCAN HDFS [functional.alltypestiny]
    partitions=4/4 files=4 size=460B
-   runtime filters: RF000 -> functional.alltypestiny.string_col, RF001 -> functional.alltypestiny.bigint_col
+   runtime filters: RF000 -> functional.alltypestiny.bigint_col, RF001 -> functional.alltypestiny.string_col
 ====
 # Test that hash exprs are re-ordered as necessary when placing a join into
 # a fragment with a compatible data partition (IMPALA-1324)
@@ -1685,8 +1685,8 @@ on (a.string_col = b.string_col and a.int_col = b.int_col)
 07:EXCHANGE [UNPARTITIONED]
 |
 03:HASH JOIN [LEFT SEMI JOIN, PARTITIONED]
-|  hash predicates: string_col = b.string_col, int_col = b.int_col
-|  runtime filters: RF000 <- b.string_col, RF001 <- b.int_col
+|  hash predicates: int_col = b.int_col, string_col = b.string_col
+|  runtime filters: RF000 <- b.int_col, RF001 <- b.string_col
 |
 |--06:EXCHANGE [HASH(b.int_col,b.string_col)]
 |  |
@@ -1703,7 +1703,7 @@ on (a.string_col = b.string_col and a.int_col = b.int_col)
 |
 00:SCAN HDFS [functional.alltypestiny]
    partitions=4/4 files=4 size=460B
-   runtime filters: RF000 -> functional.alltypestiny.string_col, RF001 -> functional.alltypestiny.int_col
+   runtime filters: RF000 -> functional.alltypestiny.int_col, RF001 -> functional.alltypestiny.string_col
 ====
 # Same as above but with the join inverted.
 select straight_join * from
@@ -1717,8 +1717,8 @@ on (a.string_col = b.string_col and a.int_col = b.int_col)
 07:EXCHANGE [UNPARTITIONED]
 |
 03:HASH JOIN [RIGHT SEMI JOIN, PARTITIONED]
-|  hash predicates: a.string_col = string_col, a.int_col = int_col
-|  runtime filters: RF000 <- string_col, RF001 <- int_col
+|  hash predicates: a.int_col = int_col, a.string_col = string_col
+|  runtime filters: RF000 <- int_col, RF001 <- string_col
 |
 |--05:AGGREGATE [FINALIZE]
 |  |  group by: int_col, string_col
@@ -1735,7 +1735,7 @@ on (a.string_col = b.string_col and a.int_col = b.int_col)
 |
 00:SCAN HDFS [functional.alltypes a]
    partitions=24/24 files=24 size=478.45KB
-   runtime filters: RF000 -> a.string_col, RF001 -> a.int_col
+   runtime filters: RF000 -> a.int_col, RF001 -> a.string_col
 ====
 # Complex combined regression test for IMPALA-1307 and IMPALA-1324
 select straight_join * from
@@ -1859,7 +1859,7 @@ where a.float_col < b.float_col
 |
 07:HASH JOIN [LEFT ANTI JOIN]
 |  hash predicates: a.int_col = c.int_col
-|  other join predicates: a.tinyint_col = b.tinyint_col, a.float_col = 2.1
+|  other join predicates: a.float_col = 2.1, a.tinyint_col = b.tinyint_col
 |
 |--02:SCAN HDFS [functional.alltypestiny c]
 |     partitions=4/4 files=4 size=460B
@@ -2129,7 +2129,7 @@ and (b.double_col * c.tinyint_col > 1000 or c.tinyint_col < 1000)
 ---- PLAN
 04:HASH JOIN [LEFT OUTER JOIN]
 |  hash predicates: c.id = a.id, c.string_col IS NOT DISTINCT FROM b.string_col
-|  other predicates: a.day >= 6, b.month > 2, a.tinyint_col = 15, b.string_col = '15', a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
+|  other predicates: a.tinyint_col = 15, b.string_col = '15', a.day >= 6, b.month > 2, a.tinyint_col + b.tinyint_col < 15, a.float_col - c.double_col < 0, (b.double_col * c.tinyint_col > 1000 OR c.tinyint_col < 1000)
 |
 |--03:HASH JOIN [FULL OUTER JOIN]
 |  |  hash predicates: a.id IS NOT DISTINCT FROM b.id, a.int_col = b.int_col

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/kudu-selectivity.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/kudu-selectivity.test b/testdata/workloads/functional-planner/queries/PlannerTest/kudu-selectivity.test
index fe1cb29..ead71ef 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/kudu-selectivity.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/kudu-selectivity.test
@@ -45,7 +45,7 @@ select * from functional_kudu.zipcode_incomes where id > '1' and zip > '2'
 ---- PLAN
 F00:PLAN FRAGMENT [UNPARTITIONED]
   00:SCAN KUDU [functional_kudu.zipcode_incomes]
-     predicates: id > '1', zip > '2'
+     predicates: zip > '2', id > '1'
      hosts=3 per-host-mem=unavailable
      tuple-ids=0 row-size=124B cardinality=3317
 ---- DISTRIBUTEDPLAN
@@ -57,7 +57,7 @@ F01:PLAN FRAGMENT [UNPARTITIONED]
 F00:PLAN FRAGMENT [RANDOM]
   DATASTREAM SINK [FRAGMENT=F01, EXCHANGE=01, UNPARTITIONED]
   00:SCAN KUDU [functional_kudu.zipcode_incomes]
-     predicates: id > '1', zip > '2'
+     predicates: zip > '2', id > '1'
      hosts=3 per-host-mem=0B
      tuple-ids=0 row-size=124B cardinality=3317
 ====

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/kudu.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/kudu.test b/testdata/workloads/functional-planner/queries/PlannerTest/kudu.test
index 28cc7af..6abf6b4 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/kudu.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/kudu.test
@@ -92,7 +92,7 @@ where id >= 10 and zip <= 5 and 20 >= id and 'foo' = name and zip >= 0 and 30 >=
 and zip > 1 and zip < 50
 ---- PLAN
 00:SCAN KUDU [functional_kudu.testtbl]
-   kudu predicates: id >= 10, zip <= 5, id <= 20, name = 'foo', zip >= 0, zip <= 30, zip >= 2, zip <= 49
+   kudu predicates: id >= 10, zip <= 5, id <= 20, zip >= 0, zip <= 30, zip >= 2, zip <= 49, name = 'foo'
 ---- SCANRANGELOCATIONS
 NODE 0:
   KUDU KEYRANGE []:[]
@@ -100,7 +100,7 @@ NODE 0:
 01:EXCHANGE [UNPARTITIONED]
 |
 00:SCAN KUDU [functional_kudu.testtbl]
-   kudu predicates: id >= 10, zip <= 5, id <= 20, name = 'foo', zip >= 0, zip <= 30, zip >= 2, zip <= 49
+   kudu predicates: id >= 10, zip <= 5, id <= 20, zip >= 0, zip <= 30, zip >= 2, zip <= 49, name = 'foo'
 ====
 # Test constant folding.
 select * from functional_kudu.testtbl
@@ -142,7 +142,7 @@ select * from functional_kudu.testtbl
 where cast(sin(id) as boolean) = true and name is null
 ---- PLAN
 00:SCAN KUDU [functional_kudu.testtbl]
-   predicates: CAST(sin(id) AS BOOLEAN) = TRUE, name IS NULL
+   predicates: name IS NULL, CAST(sin(id) AS BOOLEAN) = TRUE
 ---- SCANRANGELOCATIONS
 NODE 0:
   KUDU KEYRANGE []:[]
@@ -150,5 +150,5 @@ NODE 0:
 01:EXCHANGE [UNPARTITIONED]
 |
 00:SCAN KUDU [functional_kudu.testtbl]
-   predicates: CAST(sin(id) AS BOOLEAN) = TRUE, name IS NULL
+   predicates: name IS NULL, CAST(sin(id) AS BOOLEAN) = TRUE
 ====
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/8c2bf976/testdata/workloads/functional-planner/queries/PlannerTest/nested-collections.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/nested-collections.test b/testdata/workloads/functional-planner/queries/PlannerTest/nested-collections.test
index b5e8887..a05c91b 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/nested-collections.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/nested-collections.test
@@ -12,7 +12,7 @@ where key = 'test' and value < 10
 ---- PLAN
 00:SCAN HDFS [functional.allcomplextypes.int_map_col]
    partitions=0/0 files=0 size=0B
-   predicates: key = 'test', value < 10
+   predicates: value < 10, key = 'test'
 ====
 # Scan of a deeply nested collection.
 select count(f21) from functional.allcomplextypes.complex_nested_struct_col.f2.f12
@@ -75,15 +75,15 @@ where c_nationkey = n_nationkey and s_nationkey = n_nationkey
 |     predicates: !empty(r.r_nations)
 |
 07:HASH JOIN [INNER JOIN]
-|  hash predicates: c_comment = s_comment, c.c_nationkey = s.s_nationkey
-|  runtime filters: RF002 <- s_comment, RF003 <- s.s_nationkey
+|  hash predicates: c.c_nationkey = s.s_nationkey, c_comment = s_comment
+|  runtime filters: RF002 <- s.s_nationkey, RF003 <- s_comment
 |
 |--06:SCAN HDFS [tpch_nested_parquet.supplier s]
 |     partitions=1/1 files=1 size=111.08MB
 |
 05:SCAN HDFS [tpch_nested_parquet.customer c]
    partitions=1/1 files=4 size=554.13MB
-   runtime filters: RF000 -> c_nationkey, RF001 -> c.c_comment, RF002 -> c_comment, RF003 -> c.c_nationkey
+   runtime filters: RF000 -> c_nationkey, RF001 -> c.c_comment, RF002 -> c.c_nationkey, RF003 -> c_comment
 ====
 # Test subplans: Cross join of parent and relative ref.
 select a.id, b.item from functional.allcomplextypes a cross join a.int_array_col b
@@ -99,7 +99,7 @@ where a.id < 10 and b.item % 2 = 0
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(a.int_array_col), a.id < 10
+   predicates: a.id < 10, !empty(a.int_array_col)
    predicates on b: b.item % 2 = 0
 ====
 # Test subplans: Left semi join of parent and relative ref without On-clause.
@@ -116,7 +116,7 @@ where a.id < 10
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(a.int_array_col), a.id < 10
+   predicates: a.id < 10, !empty(a.int_array_col)
 ====
 # Test subplans: Right semi join of parent and relative ref without On-clause.
 select b.item from functional.allcomplextypes a right semi join a.int_array_col b
@@ -240,7 +240,7 @@ where b.item % 2 = 0 and a.id < 10
 01:SUBPLAN
 |
 |--04:NESTED LOOP JOIN [FULL OUTER JOIN]
-|  |  predicates: b.item % 2 = 0, a.id < 10
+|  |  predicates: a.id < 10, b.item % 2 = 0
 |  |
 |  |--02:SINGULAR ROW SRC
 |  |
@@ -266,7 +266,7 @@ where a.id < 10 and b.item % 2 = 0 and a.id < b.item
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(a.int_array_col), a.id < 10
+   predicates: a.id < 10, !empty(a.int_array_col)
    predicates on b: b.item % 2 = 0
 ====
 # Test subplans: Non-equi left semi join of parent and relative ref.
@@ -285,7 +285,7 @@ where a.id < 10
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(a.int_array_col), a.id < 10
+   predicates: a.id < 10, !empty(a.int_array_col)
    predicates on b: b.item % 2 = 0
 ====
 # Test subplans: Non-equi right semi join of parent and relative ref.
@@ -419,7 +419,7 @@ where a.id < 10 and b.f1 % 2 = 0 and b.f1 = a.id and b.f1 < a.year
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(a.struct_array_col), a.id < 10, a.id % 2 = 0
+   predicates: a.id < 10, a.id % 2 = 0, !empty(a.struct_array_col)
    predicates on b: b.f1 < 10, b.f1 % 2 = 0
 ====
 # Test subplans: Left-semi equi-join of parent and relative ref.
@@ -439,7 +439,7 @@ where a.id < 10
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: a.id % 2 = 0, !empty(a.struct_array_col), a.id < 10
+   predicates: a.id < 10, a.id % 2 = 0, !empty(a.struct_array_col)
    predicates on b: b.f1 % 2 = 0, b.f1 < 10
 ====
 # Test subplans: Right-semi equi-join of parent and relative ref.
@@ -459,7 +459,7 @@ where b.f1 % 2 = 0
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: a.id < 10, !empty(a.struct_array_col), a.id % 2 = 0
+   predicates: a.id < 10, a.id % 2 = 0, !empty(a.struct_array_col)
    predicates on b: b.f1 < 10, b.f1 % 2 = 0
 ====
 # Test subplans: Left-anti equi-join of parent and relative ref.
@@ -499,7 +499,7 @@ where b.f1 % 2 = 0
 |
 00:SCAN HDFS [functional.allcomplextypes a]
    partitions=0/0 files=0 size=0B
-   predicates: a.id % 2 = 0, a.id < 10
+   predicates: a.id < 10, a.id % 2 = 0
    predicates on b: b.f1 % 2 = 0
 ====
 # Test subplans: Left-outer equi-join of parent and relative ref.
@@ -1211,7 +1211,7 @@ limit 10
 |
 00:SCAN HDFS [tpch_nested_parquet.customer c]
    partitions=1/1 files=4 size=554.13MB
-   predicates: !empty(c.c_orders), c_custkey < 10
+   predicates: c_custkey < 10, !empty(c.c_orders)
    predicates on o: !empty(o.o_lineitems), o_orderkey < 5
    predicates on o_lineitems: l_linenumber < 3
 ====
@@ -1308,7 +1308,7 @@ where c.c_custkey = o.o_orderkey and c.c_custkey = o.o_shippriority
 |
 00:SCAN HDFS [tpch_nested_parquet.customer c]
    partitions=1/1 files=4 size=554.13MB
-   predicates: !empty(c.c_orders), c.c_custkey = c.c_nationkey
+   predicates: c.c_custkey = c.c_nationkey, !empty(c.c_orders)
    predicates on o: !empty(o.o_lineitems), o.o_orderkey = o.o_shippriority
    predicates on l: l.l_partkey = l.l_linenumber, l.l_partkey = l.l_suppkey
 ====
@@ -1357,7 +1357,7 @@ where a.item between 10 and 20
 |
 00:SCAN HDFS [functional.allcomplextypes t]
    partitions=0/0 files=0 size=0B
-   predicates: !empty(t.int_array_col), !empty(t.complex_nested_struct_col.f2), t.id < 200
+   predicates: t.id < 200, !empty(t.int_array_col), !empty(t.complex_nested_struct_col.f2)
    predicates on a: a.item >= 10, a.item <= 20, a.item % 2 = 0
    predicates on m: m.key = 'test', m.value != 30
    predicates on c: c.f11 >= 10, c.f11 <= 20, c.f11 % 2 = 0
@@ -1620,7 +1620,7 @@ where t1.id = t2.pos and t1.int_struct_col.f1 = 10 and t2.item = 1
 |
 |--04:HASH JOIN [LEFT OUTER JOIN]
 |  |  hash predicates: t1.id = t2.pos
-|  |  other predicates: t1.id = t2.pos, t2.item = 1
+|  |  other predicates: t2.item = 1, t1.id = t2.pos
 |  |
 |  |--03:UNNEST [t1.int_array_col t2]
 |  |
@@ -1641,7 +1641,7 @@ where t1.id = t2.pos and t1.int_struct_col.f1 = 10 and t2.item = 1
 |
 |--04:HASH JOIN [RIGHT OUTER JOIN]
 |  |  hash predicates: t1.id = t2.pos
-|  |  other predicates: t1.id = t2.pos, t1.int_struct_col.f1 = 10
+|  |  other predicates: t1.int_struct_col.f1 = 10, t1.id = t2.pos
 |  |
 |  |--03:UNNEST [t1.int_array_col t2]
 |  |
@@ -1662,7 +1662,7 @@ where t1.id = t2.pos and t1.int_struct_col.f1 = 10 and t2.item = 1
 |
 |--04:NESTED LOOP JOIN [FULL OUTER JOIN]
 |  |  join predicates: t2.pos = t1.id
-|  |  predicates: t1.id = t2.pos, t1.int_struct_col.f1 = 10, t2.item = 1
+|  |  predicates: t1.int_struct_col.f1 = 10, t2.item = 1, t1.id = t2.pos
 |  |
 |  |--02:SINGULAR ROW SRC
 |  |
@@ -1682,7 +1682,7 @@ select id from functional.allcomplextypes t1 left outer join
 01:SUBPLAN
 |
 |--04:NESTED LOOP JOIN [RIGHT OUTER JOIN]
-|  |  predicates: t1.id > pos, item = 1
+|  |  predicates: item = 1, t1.id > pos
 |  |
 |  |--02:SINGULAR ROW SRC
 |  |