You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by st...@apache.org on 2022/05/25 12:53:34 UTC

[impala] 01/02: IMPALA-11274: CNF Rewrite causes a regress in join node performance

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

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

commit 2243f331cb73670f6745b2b5d1268668de2e8e90
Author: Michael Smith <mi...@cloudera.com>
AuthorDate: Thu May 12 17:01:21 2022 -0700

    IMPALA-11274: CNF Rewrite causes a regress in join node performance
    
    This patch defines a subset of all predicates that are common and
    relatively inexpensive to compute. Such predicates must involve
    columns, constants, simple math or cast functions only.
    
    Examples of the subset of the predicates allowed:
    
      1. (a = 1 AND cast(b as int) = 2) OR (c = d AND e = f)
      2. a in ('1', '2', '3') OR ((b = 'abc') AND (c = d))
      3. (a between 1 and 100) OR ((b is null) AND (c = d))
    
    Examples of the predicates not allowed:
    
      1. (upper(a) != 'Y') AND b = 2) OR (c = d AND e = f)
      2. (coalesce(CAST(a AS string), '') = '') AND b = 2) OR
         (c = d AND e = f)
    
    This patch further restricts the predicates to be converted to
    conjunctive normal form (CNF) to be such a subset, with the aim to
    reduce the run-time evaluation overhead of CNFs in which some
    of the predicates can be duplicated.
    
    Uses a cache in branching expressions to avoid visiting the entire
    subtree on each call to applyRuleBottomUp. Skips cache complexity on
    casts as they don't branch and are unlikely to be deeply nested.
    
    Testing:
    - New expression writer tests
    - New planner tests
    
    Change-Id: I326406c6b004fe31ec0e2a2f390a3845b8925aa9
    Reviewed-on: http://gerrit.cloudera.org:8080/18458
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 common/function-registry/impala_functions.py       |  2 +
 .../org/apache/impala/analysis/ArithmeticExpr.java | 24 ++++++
 .../java/org/apache/impala/analysis/CastExpr.java  |  6 ++
 .../apache/impala/analysis/ExistsPredicate.java    |  4 +
 .../main/java/org/apache/impala/analysis/Expr.java |  7 ++
 .../apache/impala/analysis/FunctionCallExpr.java   | 44 +++++++++++
 .../impala/analysis/IsNotEmptyPredicate.java       |  4 +
 .../org/apache/impala/analysis/LikePredicate.java  |  4 +
 .../java/org/apache/impala/analysis/Predicate.java | 25 ++++++
 .../java/org/apache/impala/analysis/SlotRef.java   | 10 ++-
 .../impala/analysis/TupleIsNullPredicate.java      |  4 +
 .../apache/impala/rewrite/ConvertToCNFRule.java    |  9 +++
 .../apache/impala/analysis/ExprRewriterTest.java   | 51 +++++++++++++
 .../queries/PlannerTest/convert-to-cnf.test        | 88 ++++++++++++++++++++++
 14 files changed, 279 insertions(+), 3 deletions(-)

diff --git a/common/function-registry/impala_functions.py b/common/function-registry/impala_functions.py
index 30fb3c4e8..dd81a692d 100644
--- a/common/function-registry/impala_functions.py
+++ b/common/function-registry/impala_functions.py
@@ -323,6 +323,8 @@ visible_functions = [
   [['months_between'], 'DOUBLE', ['DATE', 'DATE'], "_ZN6impala13DateFunctions13MonthsBetweenEPN10impala_udf15FunctionContextERKNS1_7DateValES6_"],
 
   # Math builtin functions
+  # Add new math builtins that operate on scalar values to
+  # org.apache.impala.analysis.FunctionCallExpr::builtinMathScalarFunctionNames_.
   [['pi'], 'DOUBLE', [], 'impala::MathFunctions::Pi'],
   [['e'], 'DOUBLE', [], 'impala::MathFunctions::E'],
   [['abs'], 'BIGINT', ['BIGINT'], 'impala::MathFunctions::Abs'],
diff --git a/fe/src/main/java/org/apache/impala/analysis/ArithmeticExpr.java b/fe/src/main/java/org/apache/impala/analysis/ArithmeticExpr.java
index 663291d25..0edd21339 100644
--- a/fe/src/main/java/org/apache/impala/analysis/ArithmeticExpr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/ArithmeticExpr.java
@@ -17,6 +17,7 @@
 
 package org.apache.impala.analysis;
 
+import java.util.Optional;
 import org.apache.impala.catalog.Db;
 import org.apache.impala.catalog.Function.CompareMode;
 import org.apache.impala.catalog.ScalarFunction;
@@ -75,6 +76,9 @@ public class ArithmeticExpr extends Expr {
   }
 
   private final Operator op_;
+  // cache prior shouldConvertToCNF checks to avoid repeat tree walking
+  // omitted from clone in case cloner plans to mutate the expr
+  protected Optional<Boolean> shouldConvertToCNF_ = Optional.empty();
 
   public Operator getOp() { return op_; }
 
@@ -267,6 +271,26 @@ public class ArithmeticExpr extends Expr {
     return hasChildCosts() ? getChildCosts() + ARITHMETIC_OP_COST : UNKNOWN_COST;
   }
 
+  private boolean lookupShouldConvertToCNF() {
+    for (int i = 0; i < children_.size(); ++i) {
+      if (!getChild(i).shouldConvertToCNF()) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Return true if this expression's children should be converted to CNF.
+   */
+  @Override
+  public boolean shouldConvertToCNF() {
+    if (shouldConvertToCNF_.isPresent()) {
+      return shouldConvertToCNF_.get();
+    }
+    boolean result = lookupShouldConvertToCNF();
+    shouldConvertToCNF_ = Optional.of(result);
+    return result;
+  }
+
   @Override
   public Expr clone() { return new ArithmeticExpr(this); }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/CastExpr.java b/fe/src/main/java/org/apache/impala/analysis/CastExpr.java
index ba395ab13..771560d82 100644
--- a/fe/src/main/java/org/apache/impala/analysis/CastExpr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/CastExpr.java
@@ -409,6 +409,12 @@ public class CastExpr extends Expr {
         && type_.equals(other.type_);
   }
 
+  // Pass through since cast's are cheap.
+  @Override
+  public boolean shouldConvertToCNF() {
+    return getChild(0).shouldConvertToCNF();
+  }
+
   @Override
   public Expr clone() { return new CastExpr(this); }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/ExistsPredicate.java b/fe/src/main/java/org/apache/impala/analysis/ExistsPredicate.java
index c55be82f4..235e56398 100644
--- a/fe/src/main/java/org/apache/impala/analysis/ExistsPredicate.java
+++ b/fe/src/main/java/org/apache/impala/analysis/ExistsPredicate.java
@@ -83,4 +83,8 @@ public class ExistsPredicate extends Predicate {
     strBuilder.append(getChild(0).toSql(options));
     return strBuilder.toString();
   }
+
+  // Return false since existence can be expensive to determine.
+  @Override
+  public boolean shouldConvertToCNF() { return false; }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/Expr.java b/fe/src/main/java/org/apache/impala/analysis/Expr.java
index 3401aed3f..76175d51c 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Expr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Expr.java
@@ -1883,4 +1883,11 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
     }
     return null;
   }
+
+  /**
+   * Returns true if 'this' is a constant.
+   */
+  public boolean shouldConvertToCNF() {
+    return isConstant();
+  }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
index eb1225f7c..ec1c98a98 100644
--- a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
@@ -17,7 +17,12 @@
 
 package org.apache.impala.analysis;
 
+import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
 
 import org.apache.impala.authorization.Privilege;
 import org.apache.impala.catalog.AggregateFunction;
@@ -52,6 +57,17 @@ public class FunctionCallExpr extends Expr {
   private boolean isAnalyticFnCall_ = false;
   private boolean isInternalFnCall_ = false;
 
+  // cache prior shouldConvertToCNF checks to avoid repeat tree walking
+  // omitted from clone in case cloner plans to mutate the expr
+  protected Optional<Boolean> shouldConvertToCNF_ = Optional.empty();
+  private static Set<String> builtinMathScalarFunctionNames_ =
+      new HashSet<String>(Arrays.asList("abs", "acos", "asin", "atan", "atan2", "bin",
+          "ceil", "ceiling", "conv", "cos", "cosh", "cot", "dceil", "degrees", "dexp",
+          "dfloor", "dlog1", "dlog10", "dpow", "dround", "dsqrt", "dtrunc", "e", "exp",
+          "floor", "fmod", "fpow", "hex", "ln", "log", "log10", "log2", "mod", "pi",
+          "pmod", "pow", "power", "quotient", "radians", "rand", "random", "round",
+          "sign", "sin", "sinh", "sqrt", "tan", "tanh", "trunc", "truncate", "unhex"));
+
   // Non-null iff this is an aggregation function that executes the Merge() step. This
   // is an analyzed clone of the FunctionCallExpr that executes the Update() function
   // feeding into this Merge(). This is stored so that we can access the types of the
@@ -803,4 +819,32 @@ public class FunctionCallExpr extends Expr {
     return e;
   }
 
+  public boolean isBuiltinMathScalarFunction() {
+    return (fnName_.isBuiltin()
+        && builtinMathScalarFunctionNames_.contains(fnName_.getFunction()));
+  }
+
+  private boolean lookupShouldConvertToCNF() {
+    if (isBuiltinCastFunction() || isBuiltinMathScalarFunction()) {
+      for (int i = 0; i < children_.size(); ++i) {
+        if (!getChild(i).shouldConvertToCNF()) return false;
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the function call is considered inexpensive to duplicate
+   * and the arguments should also be converted.
+   */
+  @Override
+  public boolean shouldConvertToCNF() {
+    if (shouldConvertToCNF_.isPresent()) {
+      return shouldConvertToCNF_.get();
+    }
+    boolean result = lookupShouldConvertToCNF();
+    shouldConvertToCNF_ = Optional.of(result);
+    return result;
+  }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/IsNotEmptyPredicate.java b/fe/src/main/java/org/apache/impala/analysis/IsNotEmptyPredicate.java
index 0185c56e7..7a2118f54 100644
--- a/fe/src/main/java/org/apache/impala/analysis/IsNotEmptyPredicate.java
+++ b/fe/src/main/java/org/apache/impala/analysis/IsNotEmptyPredicate.java
@@ -68,4 +68,8 @@ public class IsNotEmptyPredicate extends Predicate {
 
   @Override
   public Expr clone() { return new IsNotEmptyPredicate(getChild(0).clone()); }
+
+  // Return false since emptiness can be expensive to determine.
+  @Override
+  public boolean shouldConvertToCNF() { return false; }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/LikePredicate.java b/fe/src/main/java/org/apache/impala/analysis/LikePredicate.java
index c3d256ad9..5223f4de3 100644
--- a/fe/src/main/java/org/apache/impala/analysis/LikePredicate.java
+++ b/fe/src/main/java/org/apache/impala/analysis/LikePredicate.java
@@ -168,4 +168,8 @@ public class LikePredicate extends Predicate {
   public Expr clone() { return new LikePredicate(this); }
 
   public Operator getOp() { return op_; }
+
+  // Return false since comparison can be expensive.
+  @Override
+  public boolean shouldConvertToCNF() { return false; }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/Predicate.java b/fe/src/main/java/org/apache/impala/analysis/Predicate.java
index 314e50db8..64ee408e9 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Predicate.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Predicate.java
@@ -17,6 +17,7 @@
 
 package org.apache.impala.analysis;
 
+import java.util.Optional;
 import org.apache.impala.catalog.Type;
 import org.apache.impala.common.AnalysisException;
 import org.apache.impala.common.Pair;
@@ -26,6 +27,9 @@ public abstract class Predicate extends Expr {
   protected boolean isEqJoinConjunct_;
   // true if this predicate has an always_true hint
   protected boolean hasAlwaysTrueHint_;
+  // cache prior shouldConvertToCNF checks to avoid repeat tree walking
+  // omitted from clone in case cloner plans to mutate the expr
+  protected Optional<Boolean> shouldConvertToCNF_ = Optional.empty();
 
   public Predicate() {
     super();
@@ -82,6 +86,27 @@ public abstract class Predicate extends Expr {
     return true;
   }
 
+  private boolean lookupShouldConvertToCNF() {
+    for (int i = 0; i < children_.size(); ++i) {
+      if (!getChild(i).shouldConvertToCNF()) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Return true if this predicate's children should be converted to CNF.
+   * Predicates that are considered expensive can override to return false.
+   */
+  @Override
+  public boolean shouldConvertToCNF() {
+    if (shouldConvertToCNF_.isPresent()) {
+      return shouldConvertToCNF_.get();
+    }
+    boolean result = lookupShouldConvertToCNF();
+    shouldConvertToCNF_ = Optional.of(result);
+    return result;
+  }
+
   public static boolean isEquivalencePredicate(Expr expr) {
     return (expr instanceof BinaryPredicate)
         && ((BinaryPredicate) expr).getOp().isEquivalence();
diff --git a/fe/src/main/java/org/apache/impala/analysis/SlotRef.java b/fe/src/main/java/org/apache/impala/analysis/SlotRef.java
index 836109f63..266e4da1c 100644
--- a/fe/src/main/java/org/apache/impala/analysis/SlotRef.java
+++ b/fe/src/main/java/org/apache/impala/analysis/SlotRef.java
@@ -50,9 +50,9 @@ public class SlotRef extends Expr {
   // The resolved path after resolving 'rawPath_'.
   protected Path resolvedPath_ = null;
 
-  // Indicates if this SlotRef is coming from zipping unnest where the unest is given in
-  // the FROM clause. Note, when the unnest in in the select list then an UnnestExpr would
-  // be used instead of a SlotRef.
+  // Indicates if this SlotRef is coming from zipping unnest where the unnest is given in
+  // the FROM clause. Note, when the unnest is in the select list then an UnnestExpr
+  // would be used instead of a SlotRef.
   protected boolean isZippingUnnest_ = false;
 
   public SlotRef(List<String> rawPath) {
@@ -446,4 +446,8 @@ public class SlotRef extends Expr {
       return super.uncheckedCastTo(targetType);
     }
   }
+
+  // Return true since SlotRefs should be easy to access.
+  @Override
+  public boolean shouldConvertToCNF() { return true; }
 }
diff --git a/fe/src/main/java/org/apache/impala/analysis/TupleIsNullPredicate.java b/fe/src/main/java/org/apache/impala/analysis/TupleIsNullPredicate.java
index 5fb2c0461..da78e6dc3 100644
--- a/fe/src/main/java/org/apache/impala/analysis/TupleIsNullPredicate.java
+++ b/fe/src/main/java/org/apache/impala/analysis/TupleIsNullPredicate.java
@@ -197,4 +197,8 @@ public class TupleIsNullPredicate extends Predicate {
 
   @Override
   public Expr clone() { return new TupleIsNullPredicate(this); }
+
+  // Return true since only tuples are involved during evaluation.
+  @Override
+  public boolean shouldConvertToCNF() { return true; }
 }
diff --git a/fe/src/main/java/org/apache/impala/rewrite/ConvertToCNFRule.java b/fe/src/main/java/org/apache/impala/rewrite/ConvertToCNFRule.java
index 527084a5e..1d04cb76d 100644
--- a/fe/src/main/java/org/apache/impala/rewrite/ConvertToCNFRule.java
+++ b/fe/src/main/java/org/apache/impala/rewrite/ConvertToCNFRule.java
@@ -26,6 +26,9 @@ import org.apache.impala.analysis.TableRef;
 import org.apache.impala.analysis.TupleId;
 import org.apache.impala.common.AnalysisException;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -73,6 +76,7 @@ import java.util.List;
  *   a AND b
  */
 public class ConvertToCNFRule implements ExprRewriteRule {
+  private final static Logger LOG = LoggerFactory.getLogger(ConvertToCNFRule.class);
 
   // maximum number of CNF exprs (each AND is counted as 1) allowed
   private final int maxCnfExprs_;
@@ -94,6 +98,11 @@ public class ConvertToCNFRule implements ExprRewriteRule {
       return pred;
     }
 
+    if (!((CompoundPredicate)pred).shouldConvertToCNF()) {
+      LOG.debug("It is not feasible to rewrite predicate " + pred.toSql() + " to CNF.");
+      return pred;
+    }
+
     if (maxCnfExprs_ > 0 && numCnfExprs_ >= maxCnfExprs_) {
       // max allowed CNF exprs has been reached .. in this case we
       // return the supplied predicate (also see related comments
diff --git a/fe/src/test/java/org/apache/impala/analysis/ExprRewriterTest.java b/fe/src/test/java/org/apache/impala/analysis/ExprRewriterTest.java
index 9fa7fc6a6..15f34a534 100644
--- a/fe/src/test/java/org/apache/impala/analysis/ExprRewriterTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/ExprRewriterTest.java
@@ -17,6 +17,10 @@
 
 package org.apache.impala.analysis;
 
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.impala.analysis.AnalysisContext.AnalysisResult;
 import org.apache.impala.common.AnalysisException;
 import org.apache.impala.common.ImpalaException;
@@ -33,6 +37,8 @@ import com.google.common.base.Preconditions;
 import static org.apache.impala.analysis.ToSqlOptions.DEFAULT;
 import static org.apache.impala.analysis.ToSqlOptions.REWRITTEN;
 import static org.apache.impala.analysis.ToSqlOptions.SHOW_IMPLICIT_CASTS;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Tests that the ExprRewriter framework covers all clauses as well as nested statements.
@@ -112,6 +118,14 @@ public class ExprRewriterTest extends AnalyzerTest {
     Assert.assertEquals(0, exprToTrue_.getNumChanges());
   }
 
+  private Expr analyze(String query) {
+    AnalysisContext ctx = createAnalysisCtx();
+    ctx.getQueryOptions().setDecimal_v2(true);
+    ctx.getQueryOptions().setEnable_expr_rewrites(false);
+    return ((SelectStmt) AnalyzesOk(query, ctx)).getSelectList()
+        .getItems().get(0).getExpr();
+  }
+
   // Select statement with all clauses that has 11 rewritable Expr trees.
   // We expect a total of 23 exprs to be changed.
   private final String stmt_ =
@@ -586,4 +600,41 @@ public class ExprRewriterTest extends AnalyzerTest {
             "SELECT ndv(id), ndv(id, 5), count(DISTINCT id) FROM functional.alltypes");
 
   }
+
+  @Test
+  public void TestShouldConvertToCNF() {
+    TQueryOptions options = new TQueryOptions();
+    options.setEnable_expr_rewrites(false);
+    AnalysisContext ctx = createAnalysisCtx(options);
+
+    // Positive tests
+    List<String> convertablePredicates = Arrays.asList("select (1=cast(1 as int))",
+        "select (cast(d_date_sk as int) = 10) from tpcds_parquet.date_dim",
+        "select (d_date_sk = d_year) from tpcds_parquet.date_dim",
+        "select (d_date_sk between 1 and 10) from tpcds_parquet.date_dim",
+        "select (d_date_sk in (1,2,10)) from tpcds_parquet.date_dim",
+        "select (d_date_sk is null) from tpcds_parquet.date_dim", "select (cos(1) = 1.1)",
+        "select (cast(d_date_sk as int) * 2 = 10) from tpcds_parquet.date_dim",
+        "select ((2 = cast(1 as int)) and (cos(1) = 1))",
+        "select ((2 = cast(1 as int)) or (cast(0 as int) is not null))",
+        "select (sin(cos(2*pi())))");
+
+    for (String query: convertablePredicates) {
+      Expr expr = analyze(query);
+      assertTrue("Should convert to CNF: "+query, expr.shouldConvertToCNF());
+    }
+
+    // Negative tests
+    List<String> inconvertablePredicates = Arrays.asList(
+        "select (upper(d_day_name) = 'A') from tpcds_parquet.date_dim",
+        "select (d_day_name like '%A') from tpcds_parquet.date_dim",
+        "select (coalesce(d_date_sk, -1) = d_year) from tpcds_parquet.date_dim",
+        "select (log10(cast(1 + length(upper(d_day_name)) as double)) > 1.0) from "
+         + "tpcds_parquet.date_dim");
+
+    for (String query: inconvertablePredicates) {
+      Expr expr = analyze(query);
+      assertFalse("Should not convert to CNF: "+query, expr.shouldConvertToCNF());
+    }
+  }
 }
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/convert-to-cnf.test b/testdata/workloads/functional-planner/queries/PlannerTest/convert-to-cnf.test
index 2a1042103..73f82467c 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/convert-to-cnf.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/convert-to-cnf.test
@@ -373,3 +373,91 @@ PLAN-ROOT SINK
    HDFS partitions=1/1 files=1 size=718.94MB
    row-size=8B cardinality=6.00M
 ====
+
+# IMPALA-11274: Test with string functions in the disjunctive predicate.
+# In this case the predicate is not converted to CNF
+select count(*) from lineitem, orders
+ where l_orderkey = o_orderkey and
+  ((upper(l_returnflag) = 'Y' and upper(o_orderpriority) = 'HIGH')
+    or (upper(l_returnflag) = 'N' and upper(o_orderpriority) = 'LOW'))
+  and l_partkey > 0;
+---- PLAN
+PLAN-ROOT SINK
+|
+03:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  row-size=8B cardinality=1
+|
+02:HASH JOIN [INNER JOIN]
+|  hash predicates: o_orderkey = l_orderkey
+|  other predicates: ((upper(l_returnflag) = 'Y' AND upper(o_orderpriority) = 'HIGH') OR (upper(l_returnflag) = 'N' AND upper(o_orderpriority) = 'LOW'))
+|  runtime filters: RF000 <- l_orderkey
+|  row-size=57B cardinality=600.12K
+|
+|--00:SCAN HDFS [tpch_parquet.lineitem]
+|     HDFS partitions=1/1 files=3 size=193.99MB
+|     predicates: l_partkey > 0
+|     row-size=29B cardinality=600.12K
+|
+01:SCAN HDFS [tpch_parquet.orders]
+   HDFS partitions=1/1 files=2 size=54.21MB
+   runtime filters: RF000 -> o_orderkey
+   row-size=28B cardinality=1.50M
+====
+
+# IMPALA-11274: Functions like CAST should still be eligible for CNF
+select count(*) from lineitem, orders
+ where l_orderkey = o_orderkey and
+  ((cast(l_returnflag as varchar(2)) = 'Y' and cast(o_orderpriority as varchar(5)) = 'HIGH')
+    or (cast(l_returnflag as varchar(2)) = 'N' and cast(o_orderpriority as varchar(5)) = 'LOW'))
+  and l_partkey > 0;
+---- PLAN
+PLAN-ROOT SINK
+|
+03:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  row-size=8B cardinality=1
+|
+02:HASH JOIN [INNER JOIN]
+|  hash predicates: l_orderkey = o_orderkey
+|  other predicates: CAST(l_returnflag AS VARCHAR(2)) = 'Y' OR CAST(o_orderpriority AS VARCHAR(5)) = 'LOW', CAST(o_orderpriority AS VARCHAR(5)) = 'HIGH' OR CAST(l_returnflag AS VARCHAR(2)) = 'N'
+|  runtime filters: RF000 <- o_orderkey
+|  row-size=57B cardinality=57.58K
+|
+|--01:SCAN HDFS [tpch_parquet.orders]
+|     HDFS partitions=1/1 files=2 size=54.21MB
+|     predicates: CAST(o_orderpriority AS VARCHAR(5)) IN ('HIGH', 'LOW')
+|     row-size=28B cardinality=150.00K
+|
+00:SCAN HDFS [tpch_parquet.lineitem]
+   HDFS partitions=1/1 files=3 size=193.99MB
+   predicates: l_partkey > 0, CAST(l_returnflag AS VARCHAR(2)) IN ('Y', 'N')
+   runtime filters: RF000 -> l_orderkey
+   row-size=29B cardinality=600.12K
+====
+
+# IMPALA-11274: Simple arithmetic expressions should still be eligible for CNF
+select count(*) from lineitem, orders
+ where l_orderkey = o_orderkey and
+  (2 * log10(l_quantity) < 3 and cast(l_returnflag as varchar(2)) = 'Y')
+   or l_quantity >= 50;
+---- PLAN
+PLAN-ROOT SINK
+|
+03:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  row-size=8B cardinality=1
+|
+02:NESTED LOOP JOIN [INNER JOIN]
+|  predicates: l_orderkey = o_orderkey OR l_quantity >= 50
+|  row-size=37B cardinality=600.12K
+|
+|--01:SCAN HDFS [tpch_parquet.orders]
+|     HDFS partitions=1/1 files=2 size=54.21MB
+|     row-size=8B cardinality=1.50M
+|
+00:SCAN HDFS [tpch_parquet.lineitem]
+   HDFS partitions=1/1 files=3 size=193.99MB
+   predicates: (2 * log10(l_quantity) < 3 AND CAST(l_returnflag AS VARCHAR(2)) = 'Y') OR l_quantity >= 50
+   row-size=29B cardinality=600.12K
+====