You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2014/09/11 02:54:05 UTC

git commit: [OPTIQ-399] Factorize common AND factors out of OR predicates

Repository: incubator-optiq
Updated Branches:
  refs/heads/master 6fbefcec9 -> fd20d5c41


[OPTIQ-399] Factorize common AND factors out of OR predicates


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

Branch: refs/heads/master
Commit: fd20d5c41d47377aa0e0f592e409f81f5952ac82
Parents: 6fbefce
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Sep 3 18:06:16 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Wed Sep 10 16:53:36 2014 -0700

----------------------------------------------------------------------
 .../main/java/org/eigenbase/rex/RexUtil.java    | 103 +++++-
 .../java/org/eigenbase/test/RexProgramTest.java | 334 +++++++++++--------
 2 files changed, 294 insertions(+), 143 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/fd20d5c4/core/src/main/java/org/eigenbase/rex/RexUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexUtil.java b/core/src/main/java/org/eigenbase/rex/RexUtil.java
index 424ba60..5ed708d 100644
--- a/core/src/main/java/org/eigenbase/rex/RexUtil.java
+++ b/core/src/main/java/org/eigenbase/rex/RexUtil.java
@@ -31,7 +31,9 @@ import net.hydromatic.linq4j.function.*;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 
 /**
  * Utility methods concerning row-expressions.
@@ -52,6 +54,13 @@ public class RexUtil {
         }
       };
 
+  private static final Function<Object, String> TO_STRING =
+      new Function<Object, String>() {
+        public String apply(Object input) {
+          return input.toString();
+        }
+      };
+
   private RexUtil() {
   }
 
@@ -1003,6 +1012,32 @@ public class RexUtil {
         });
   }
 
+  /** Creates an equivalent version of a node where common factors among ORs
+   * are pulled up.
+   *
+   * <p>For example,
+   *
+   * <blockquote>(a AND b) OR (a AND c AND d)</blockquote>
+   *
+   * <p>becomes
+   *
+   * <blockquote>a AND (b OR (c AND d))</blockquote>
+   *
+   * <p>Note that this result is not in CNF
+   * (see {@link #toCnf(RexBuilder, RexNode)}) because there is an AND inside an
+   * OR.
+   *
+   * <p>This form is useful if, say, {@code a} contains columns from only the
+   * left-hand side of a join, and can be pushed to the left input.
+   *
+   * @param rexBuilder Rex builder
+   * @param node Expression to transform
+   * @return Equivalent expression with common factors pulled up
+   */
+  public static RexNode pullFactors(RexBuilder rexBuilder, RexNode node) {
+    return new CnfHelper(rexBuilder).pull(node);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**
@@ -1204,8 +1239,9 @@ public class RexUtil {
         default:
           return rex;
         }
+      default:
+        return rex;
       }
-      return rex;
     }
 
     private List<RexNode> toCnfs(List<RexNode> nodes) {
@@ -1223,6 +1259,71 @@ public class RexUtil {
       return list;
     }
 
+    private RexNode pull(RexNode rex) {
+      final List<RexNode> operands;
+      switch (rex.getKind()) {
+      case AND:
+        operands = flattenAnd(((RexCall) rex).getOperands());
+        return and(pullList(operands));
+      case OR:
+        operands = flattenOr(((RexCall) rex).getOperands());
+        final Map<String, RexNode> factors = commonFactors(operands);
+        if (factors.isEmpty()) {
+          return or(operands);
+        }
+        final List<RexNode> list = Lists.newArrayList();
+        for (RexNode operand : operands) {
+          list.add(removeFactor(factors, operand));
+        }
+        return and(Iterables.concat(factors.values(),
+            ImmutableList.of(or(list))));
+      default:
+        return rex;
+      }
+    }
+
+    private List<RexNode> pullList(List<RexNode> nodes) {
+      final List<RexNode> list = Lists.newArrayList();
+      for (RexNode node : nodes) {
+        RexNode pulled = pull(node);
+        switch (pulled.getKind()) {
+        case AND:
+          list.addAll(((RexCall) pulled).getOperands());
+          break;
+        default:
+          list.add(pulled);
+        }
+      }
+      return list;
+    }
+
+    private Map<String, RexNode> commonFactors(List<RexNode> nodes) {
+      final Map<String, RexNode> map = Maps.newHashMap();
+      int i = 0;
+      for (RexNode node : nodes) {
+        if (i++ == 0) {
+          for (RexNode conjunction : RelOptUtil.conjunctions(node)) {
+            map.put(conjunction.toString(), conjunction);
+          }
+        } else {
+          map.keySet().retainAll(
+              Lists.transform(RelOptUtil.conjunctions(node), TO_STRING));
+        }
+      }
+      return map;
+    }
+
+    private RexNode removeFactor(Map<String, RexNode> factors, RexNode node) {
+      List<RexNode> list = Lists.newArrayList();
+      for (RexNode operand : RelOptUtil.conjunctions(node)) {
+        if (!factors.containsKey(operand.toString())) {
+          list.add(operand);
+        }
+      }
+      return and(list);
+    }
+
+
     private RexNode and(Iterable<? extends RexNode> nodes) {
       return composeConjunction(rexBuilder, nodes, false);
     }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/fd20d5c4/core/src/test/java/org/eigenbase/test/RexProgramTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RexProgramTest.java b/core/src/test/java/org/eigenbase/test/RexProgramTest.java
index 02195f3..184f1e6 100644
--- a/core/src/test/java/org/eigenbase/test/RexProgramTest.java
+++ b/core/src/test/java/org/eigenbase/test/RexProgramTest.java
@@ -65,6 +65,58 @@ public class RexProgramTest {
     rexBuilder = new RexBuilder(typeFactory);
   }
 
+  private void checkCnf(RexNode node, String expected) {
+    assertThat(RexUtil.toCnf(rexBuilder, node).toString(), equalTo(expected));
+  }
+
+  private void checkPullFactorsUnchanged(RexNode node) {
+    checkPullFactors(node, node.toString());
+  }
+
+  private void checkPullFactors(RexNode node, String expected) {
+    assertThat(RexUtil.pullFactors(rexBuilder, node).toString(),
+        equalTo(expected));
+  }
+
+  /** Returns the number of nodes (including leaves) in a Rex tree. */
+  private static int nodeCount(RexNode node) {
+    int n = 1;
+    if (node instanceof RexCall) {
+      for (RexNode operand : ((RexCall) node).getOperands()) {
+        n += nodeCount(operand);
+      }
+    }
+    return n;
+  }
+
+  private RexNode not(RexNode node) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.NOT, node);
+  }
+
+  private RexNode and(RexNode... nodes) {
+    return and(ImmutableList.copyOf(nodes));
+  }
+
+  private RexNode and(Iterable<? extends RexNode> nodes) {
+    // Does not flatten nested ANDs. We want test input to contain nested ANDs.
+    return rexBuilder.makeCall(SqlStdOperatorTable.AND,
+        ImmutableList.copyOf(nodes));
+  }
+
+  private RexNode or(RexNode... nodes) {
+    return or(ImmutableList.copyOf(nodes));
+  }
+
+  private RexNode or(Iterable<? extends RexNode> nodes) {
+    // Does not flatten nested ORs. We want test input to contain nested ORs.
+    return rexBuilder.makeCall(SqlStdOperatorTable.OR,
+        ImmutableList.copyOf(nodes));
+  }
+
+  private RexNode eq(RexNode n1, RexNode n2) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, n1, n2);
+  }
+
   /**
    * Tests construction of a RexProgram.
    */
@@ -101,8 +153,8 @@ public class RexProgramTest {
     final String program = builder.getProgram(true).toString();
     TestUtil.assertEqualsVerbose(
         "(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], "
-        + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], "
-        + "expr#6=[+($t0, $t0)], a=[$t5], b=[$t6])",
+            + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], "
+            + "expr#6=[+($t0, $t0)], a=[$t5], b=[$t6])",
         program);
   }
 
@@ -265,12 +317,6 @@ public class RexProgramTest {
   /** Unit test for {@link org.eigenbase.relopt.Strong}. */
   @Test public void testStrong() {
     final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
-    final RelDataType rowType = typeFactory.builder()
-        .add("a", intType)
-        .add("b", intType)
-        .add("c", intType)
-        .add("d", intType)
-        .build();
 
     final BitSet c = BitSets.of();
     final BitSet c0 = BitSets.of(0);
@@ -280,9 +326,6 @@ public class RexProgramTest {
 
     // input ref
     final RexInputRef aRef = rexBuilder.makeInputRef(intType, 0);
-    final RexInputRef bRef = rexBuilder.makeInputRef(intType, 1);
-    final RexInputRef cRef = rexBuilder.makeInputRef(intType, 2);
-    final RexInputRef dRef = rexBuilder.makeInputRef(intType, 3);
 
     assertThat(strongIf(aRef, c0), is(true));
     assertThat(strongIf(aRef, c1), is(false));
@@ -354,72 +397,45 @@ public class RexProgramTest {
     final RexNode unknownLiteral =
         rexBuilder.makeNullLiteral(SqlTypeName.BOOLEAN);
 
-    assertThat(RexUtil.toCnf(rexBuilder, aRef).toString(), equalTo("?0.a"));
-    assertThat(RexUtil.toCnf(rexBuilder, trueLiteral).toString(),
-        equalTo("true"));
-    assertThat(RexUtil.toCnf(rexBuilder, falseLiteral).toString(),
-        equalTo("false"));
-    assertThat(RexUtil.toCnf(rexBuilder, unknownLiteral).toString(),
-        equalTo("null"));
-    assertThat(RexUtil.toCnf(rexBuilder, and(aRef, bRef)).toString(),
-        equalTo("AND(?0.a, ?0.b)"));
-    assertThat(RexUtil.toCnf(rexBuilder, and(aRef, bRef, cRef)).toString(),
-        equalTo("AND(?0.a, ?0.b, ?0.c)"));
-
-    assertThat(RexUtil.toCnf(rexBuilder, and(or(aRef, bRef), or(cRef, dRef)))
-            .toString(),
-        equalTo("AND(OR(?0.a, ?0.b), OR(?0.c, ?0.d))"));
-    assertThat(RexUtil.toCnf(rexBuilder, or(and(aRef, bRef), and(cRef, dRef)))
-            .toString(),
-        equalTo(
-            "AND(OR(?0.a, ?0.c), OR(?0.a, ?0.d), OR(?0.b, ?0.c), OR(?0.b, ?0.d))"));
+    checkCnf(aRef, "?0.a");
+    checkCnf(trueLiteral, "true");
+    checkCnf(falseLiteral, "false");
+    checkCnf(unknownLiteral, "null");
+    checkCnf(and(aRef, bRef), "AND(?0.a, ?0.b)");
+    checkCnf(and(aRef, bRef, cRef), "AND(?0.a, ?0.b, ?0.c)");
+
+    checkCnf(and(or(aRef, bRef), or(cRef, dRef)),
+        "AND(OR(?0.a, ?0.b), OR(?0.c, ?0.d))");
+    checkCnf(or(and(aRef, bRef), and(cRef, dRef)),
+        "AND(OR(?0.a, ?0.c), OR(?0.a, ?0.d), OR(?0.b, ?0.c), OR(?0.b, ?0.d))");
     // Input has nested ORs, output ORs are flat
-    assertThat(RexUtil.toCnf(rexBuilder, or(and(aRef, bRef), or(cRef, dRef)))
-            .toString(),
-        equalTo("AND(OR(?0.a, ?0.c, ?0.d), OR(?0.b, ?0.c, ?0.d))"));
+    checkCnf(or(and(aRef, bRef), or(cRef, dRef)),
+        "AND(OR(?0.a, ?0.c, ?0.d), OR(?0.b, ?0.c, ?0.d))");
 
-    assertThat(
-        RexUtil.toCnf(rexBuilder, or(aRef, not(and(bRef, not(hEqSeven)))))
-            .toString(),
-        equalTo("OR(?0.a, NOT(?0.b), =(?0.h, 7))"));
+    checkCnf(or(aRef, not(and(bRef, not(hEqSeven)))),
+        "OR(?0.a, NOT(?0.b), =(?0.h, 7))");
 
     // apply de Morgan's theorem
-    assertThat(
-        RexUtil.toCnf(rexBuilder, not(or(aRef, not(bRef))))
-            .toString(),
-        equalTo("AND(NOT(?0.a), ?0.b)"));
+    checkCnf(not(or(aRef, not(bRef))), "AND(NOT(?0.a), ?0.b)");
 
     // apply de Morgan's theorem,
     // filter out 'OR ... FALSE' and 'AND ... TRUE'
-    assertThat(
-        RexUtil.toCnf(rexBuilder,
-            not(or(and(aRef, trueLiteral), not(bRef), falseLiteral)))
-            .toString(),
-        equalTo("AND(NOT(?0.a), ?0.b)"));
-
-    assertThat(
-        RexUtil.toCnf(rexBuilder, and(aRef, or(bRef, and(cRef, dRef))))
-            .toString(),
-        equalTo("AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d))"));
-
-    assertThat(
-        RexUtil.toCnf(rexBuilder,
-            and(aRef, or(bRef,
-                and(cRef, or(dRef,
-                    and(eRef, or(fRef, gRef)))))))
-            .toString(),
-        equalTo(
-            "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))"));
-
-    assertThat(
-        RexUtil.toCnf(rexBuilder,
-            and(aRef, or(bRef,
-                and(cRef, or(dRef,
-                    and(eRef, or(fRef,
-                        and(gRef, or(trueLiteral, falseLiteral)))))))))
-            .toString(),
-        equalTo(
-            "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))"));
+    checkCnf(not(or(and(aRef, trueLiteral), not(bRef), falseLiteral)),
+        "AND(NOT(?0.a), ?0.b)");
+
+    checkCnf(and(aRef, or(bRef, and(cRef, dRef))),
+        "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d))");
+
+    checkCnf(and(aRef, or(bRef,
+            and(cRef, or(dRef,
+                and(eRef, or(fRef, gRef)))))),
+        "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))");
+
+    checkCnf(and(aRef, or(bRef,
+            and(cRef, or(dRef,
+                and(eRef, or(fRef,
+                    and(gRef, or(trueLiteral, falseLiteral)))))))),
+        "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))");
   }
 
   /** Unit test for
@@ -450,46 +466,45 @@ public class RexProgramTest {
     final RexLiteral literal3 =
         rexBuilder.makeExactLiteral(BigDecimal.valueOf(3));
 
-    assertThat(
-        RexUtil.toCnf(rexBuilder,
-            or(
-                and(eq(xRef, literal1),
-                    eq(yRef, literal1),
-                    eq(zRef, literal1)),
-                and(eq(xRef, literal2),
-                    eq(yRef, literal2),
-                    eq(aRef, literal2)),
-                and(eq(xRef, literal3),
-                    eq(aRef, literal3),
-                    eq(bRef, literal3)))).toString(),
-        equalTo("AND("
-            + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.x, 3)), "
-            + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.a, 3)), "
-            + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.b, 3)), "
-            + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.x, 3)), "
-            + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.a, 3)), "
-            + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.b, 3)), "
-            + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.x, 3)), "
-            + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.a, 3)), "
-            + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.b, 3)), "
-            + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.x, 3)), "
-            + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.a, 3)), "
-            + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.b, 3)), "
-            + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.x, 3)), "
-            + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.a, 3)), "
-            + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.b, 3)), "
-            + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.x, 3)), "
-            + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.a, 3)), "
-            + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.b, 3)), "
-            + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.x, 3)), "
-            + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.a, 3)), "
-            + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.b, 3)), "
-            + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.x, 3)), "
-            + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.a, 3)), "
-            + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.b, 3)), "
-            + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.x, 3)), "
-            + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.a, 3)), "
-            + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.b, 3)))"));
+    checkCnf(
+        or(
+            and(eq(xRef, literal1),
+                eq(yRef, literal1),
+                eq(zRef, literal1)),
+            and(eq(xRef, literal2),
+                eq(yRef, literal2),
+                eq(aRef, literal2)),
+            and(eq(xRef, literal3),
+                eq(aRef, literal3),
+                eq(bRef, literal3))),
+        "AND("
+        + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.x, 3)), "
+        + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.a, 3)), "
+        + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.b, 3)), "
+        + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.x, 3)), "
+        + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.a, 3)), "
+        + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.b, 3)), "
+        + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.x, 3)), "
+        + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.a, 3)), "
+        + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.b, 3)), "
+        + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.x, 3)), "
+        + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.a, 3)), "
+        + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.b, 3)), "
+        + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.x, 3)), "
+        + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.a, 3)), "
+        + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.b, 3)), "
+        + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.x, 3)), "
+        + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.a, 3)), "
+        + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.b, 3)), "
+        + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.x, 3)), "
+        + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.a, 3)), "
+        + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.b, 3)), "
+        + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.x, 3)), "
+        + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.a, 3)), "
+        + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.b, 3)), "
+        + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.x, 3)), "
+        + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.a, 3)), "
+        + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.b, 3)))");
   }
 
   /** Tests formulas of various sizes whose size is exponential when converted
@@ -531,43 +546,78 @@ public class RexProgramTest {
     }
   }
 
-  /** Returns the number of nodes (including leaves) in a Rex tree. */
-  private static int nodeCount(RexNode node) {
-    int n = 1;
-    if (node instanceof RexCall) {
-      for (RexNode operand : ((RexCall) node).getOperands()) {
-        n += nodeCount(operand);
-      }
-    }
-    return n;
-  }
-
-  private RexNode not(RexNode node) {
-    return rexBuilder.makeCall(SqlStdOperatorTable.NOT, node);
-  }
+  /** Unit test for {@link org.eigenbase.rex.RexUtil#pullFactors}. */
+  @Test public void testPullFactors() {
+    final RelDataType booleanType =
+        typeFactory.createSqlType(SqlTypeName.BOOLEAN);
+    final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    final RelDataType rowType = typeFactory.builder()
+        .add("a", booleanType)
+        .add("b", booleanType)
+        .add("c", booleanType)
+        .add("d", booleanType)
+        .add("e", booleanType)
+        .add("f", booleanType)
+        .add("g", booleanType)
+        .add("h", intType)
+        .build();
 
-  private RexNode and(RexNode... nodes) {
-    return and(ImmutableList.copyOf(nodes));
-  }
+    final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0);
+    final RexNode aRef = rexBuilder.makeFieldAccess(range, 0);
+    final RexNode bRef = rexBuilder.makeFieldAccess(range, 1);
+    final RexNode cRef = rexBuilder.makeFieldAccess(range, 2);
+    final RexNode dRef = rexBuilder.makeFieldAccess(range, 3);
+    final RexNode eRef = rexBuilder.makeFieldAccess(range, 4);
+    final RexNode fRef = rexBuilder.makeFieldAccess(range, 5);
+    final RexNode gRef = rexBuilder.makeFieldAccess(range, 6);
+    final RexNode hRef = rexBuilder.makeFieldAccess(range, 7);
 
-  private RexNode and(Iterable<? extends RexNode> nodes) {
-    // Does not flatten nested ANDs. We want test input to contain nested ANDs.
-    return rexBuilder.makeCall(SqlStdOperatorTable.AND,
-        ImmutableList.copyOf(nodes));
-  }
+    final RexLiteral sevenLiteral =
+        rexBuilder.makeExactLiteral(BigDecimal.valueOf(7));
+    final RexNode hEqSeven = eq(hRef, sevenLiteral);
 
-  private RexNode or(RexNode... nodes) {
-    return or(ImmutableList.copyOf(nodes));
-  }
+    final RexLiteral trueLiteral = rexBuilder.makeLiteral(true);
+    final RexLiteral falseLiteral = rexBuilder.makeLiteral(false);
+    final RexNode unknownLiteral =
+        rexBuilder.makeNullLiteral(SqlTypeName.BOOLEAN);
 
-  private RexNode or(Iterable<? extends RexNode> nodes) {
-    // Does not flatten nested ORs. We want test input to contain nested ORs.
-    return rexBuilder.makeCall(SqlStdOperatorTable.OR,
-        ImmutableList.copyOf(nodes));
-  }
+    // Most of the expressions in testCnf are unaffected by pullFactors.
+    checkPullFactors(
+        or(and(aRef, bRef),
+            and(cRef, aRef, dRef, aRef)),
+        "AND(?0.a, OR(?0.b, AND(?0.c, ?0.d)))");
+
+    checkPullFactors(aRef, "?0.a");
+    checkPullFactors(trueLiteral, "true");
+    checkPullFactors(falseLiteral, "false");
+    checkPullFactors(unknownLiteral, "null");
+    checkPullFactors(and(aRef, bRef), "AND(?0.a, ?0.b)");
+    checkPullFactors(and(aRef, bRef, cRef), "AND(?0.a, ?0.b, ?0.c)");
+
+    checkPullFactorsUnchanged(and(or(aRef, bRef), or(cRef, dRef)));
+    checkPullFactorsUnchanged(or(and(aRef, bRef), and(cRef, dRef)));
+    // Input has nested ORs, output ORs are flat; different from CNF
+    checkPullFactors(or(and(aRef, bRef), or(cRef, dRef)),
+        "OR(AND(?0.a, ?0.b), ?0.c, ?0.d)");
+
+    checkPullFactorsUnchanged(or(aRef, not(and(bRef, not(hEqSeven)))));
+    checkPullFactorsUnchanged(not(or(aRef, not(bRef))));
+    checkPullFactorsUnchanged(
+        not(or(and(aRef, trueLiteral), not(bRef), falseLiteral)));
+    checkPullFactorsUnchanged(and(aRef, or(bRef, and(cRef, dRef))));
+
+    checkPullFactorsUnchanged(
+        and(aRef,
+            or(bRef,
+                and(cRef, or(dRef,
+                    and(eRef, or(fRef, gRef)))))));
 
-  private RexNode eq(RexNode n1, RexNode n2) {
-    return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, n1, n2);
+    checkPullFactorsUnchanged(
+        and(aRef,
+            or(bRef,
+                and(cRef, or(dRef,
+                    and(eRef, or(fRef,
+                        and(gRef, or(trueLiteral, falseLiteral)))))))));
   }
 }