[CALCITE-935] Improve how ReduceExpressionsRule handles duplicate constraints 
(Pengcheng Xiong)

Add a test case making sure that non-equi constraints and identical constraints
do not prevent constant reduction.

Some fix up (Julian Hyde)


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/690faa55
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/690faa55
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/690faa55

Branch: refs/heads/branch-release
Commit: 690faa55f553fdaa86aa91a3f2c731d6c16007ca
Parents: c5f2599
Author: Pengcheng Xiong <[email protected]>
Authored: Sat Oct 24 08:53:57 2015 -0700
Committer: Julian Hyde <[email protected]>
Committed: Mon Oct 26 10:20:09 2015 -0700

----------------------------------------------------------------------
 .../rel/rules/ReduceExpressionsRule.java        | 92 ++++++++++++++++++--
 .../apache/calcite/test/RelOptRulesTest.java    | 17 ++++
 .../org/apache/calcite/test/RelOptRulesTest.xml | 26 +++++-
 3 files changed, 125 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/690faa55/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
index 4e2d890..75943b8 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
@@ -62,12 +62,14 @@ import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -510,17 +512,91 @@ public abstract class ReduceExpressionsRule extends 
RelOptRule {
     // We cannot use an ImmutableMap.Builder here. If there are multiple 
entries
     // with the same key (e.g. "WHERE deptno = 1 AND deptno = 2"), it doesn't
     // matter which we take, so the latter will replace the former.
-    final Map<RexNode, RexLiteral> builder = Maps.newHashMap();
+    // The basic idea is to find all the pairs of RexNode = RexLiteral
+    // (1) If 'predicates' contain a non-EQUALS, we bail out.
+    // (2) It is OK if a RexNode is equal to the same RexLiteral several times,
+    // (e.g. "WHERE deptno = 1 AND deptno = 1")
+    // (3) It will return false if there are inconsistent constraints (e.g.
+    // "WHERE deptno = 1 AND deptno = 2")
+    final Map<RexNode, RexLiteral> map = new HashMap<>();
+    final Set<RexNode> excludeSet = new HashSet<>();
     for (RexNode predicate : predicates.pulledUpPredicates) {
-      switch (predicate.getKind()) {
-      case EQUALS:
-        final List<RexNode> operands = ((RexCall) predicate).getOperands();
-        if (operands.get(1) instanceof RexLiteral) {
-          builder.put(operands.get(0), (RexLiteral) operands.get(1));
+      gatherConstraints(map, predicate, excludeSet);
+    }
+    final ImmutableMap.Builder<RexNode, RexLiteral> builder =
+        ImmutableMap.builder();
+    for (Map.Entry<RexNode, RexLiteral> entry : map.entrySet()) {
+      RexNode rexNode = entry.getKey();
+      if (!overlap(rexNode, excludeSet)) {
+        builder.put(rexNode, entry.getValue());
+      }
+    }
+    return builder.build();
+  }
+
+  private static boolean overlap(RexNode rexNode, Set<RexNode> set) {
+    if (rexNode instanceof RexCall) {
+      for (RexNode r : ((RexCall) rexNode).getOperands()) {
+        if (overlap(r, set)) {
+          return true;
+        }
+      }
+      return false;
+    } else {
+      return set.contains(rexNode);
+    }
+  }
+
+  /** Tries to decompose the RexNode which is a RexCall into non-literal
+   * RexNodes. */
+  private static void decompose(Set<RexNode> set, RexNode rexNode) {
+    if (rexNode instanceof RexCall) {
+      for (RexNode r : ((RexCall) rexNode).getOperands()) {
+        decompose(set, r);
+      }
+    } else if (!(rexNode instanceof RexLiteral)) {
+      set.add(rexNode);
+    }
+  }
+
+  private static void gatherConstraints(Map<RexNode, RexLiteral> map,
+      RexNode predicate, Set<RexNode> excludeSet) {
+    if (predicate.getKind() != SqlKind.EQUALS) {
+      decompose(excludeSet, predicate);
+      return;
+    }
+    final List<RexNode> operands = ((RexCall) predicate).getOperands();
+    if (operands.size() != 2) {
+      decompose(excludeSet, predicate);
+      return;
+    }
+    // if it reaches here, we have rexNode equals rexNode
+    final RexNode left = operands.get(0);
+    final RexNode right = operands.get(1);
+    // note that literals are immutable too and they can only be compared 
through
+    // values.
+    if (right instanceof RexLiteral && !excludeSet.contains(left)) {
+      RexLiteral existedValue = map.get(left);
+      if (existedValue == null) {
+        map.put(left, (RexLiteral) right);
+      } else {
+        if (!existedValue.getValue().equals(((RexLiteral) right).getValue())) {
+          // we found conflict values.
+          map.remove(left);
+          excludeSet.add(left);
+        }
+      }
+    } else if (left instanceof RexLiteral && !excludeSet.contains(right)) {
+      RexLiteral existedValue = map.get(right);
+      if (existedValue == null) {
+        map.put(right, (RexLiteral) left);
+      } else {
+        if (!existedValue.getValue().equals(((RexLiteral) left).getValue())) {
+          map.remove(right);
+          excludeSet.add(right);
         }
       }
     }
-    return ImmutableMap.copyOf(builder);
   }
 
   /** Pushes predicates into a CASE.

http://git-wip-us.apache.org/repos/asf/calcite/blob/690faa55/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index cce90b8..c237ece 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -817,6 +817,23 @@ public class RelOptRulesTest extends RelOptTestBase {
             + " where d.deptno=7 and d.deptno=8");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-935";>[CALCITE-935]
+   * Improve how ReduceExpressionsRule handles duplicate constraints</a>. */
+  @Test public void testReduceConstantsDup2() throws Exception {
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
+        .addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
+        .addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
+        .build();
+
+    final String sql = "select *\n"
+        + "from emp\n"
+        + "where deptno=7 and deptno=8\n"
+        + "and empno = 10 and mgr is null and empno = 10";
+    checkPlanning(program, sql);
+  }
+
   @Test public void testReduceConstants2() throws Exception {
     HepProgram program = new HepProgramBuilder()
         .addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)

http://git-wip-us.apache.org/repos/asf/calcite/blob/690faa55/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index ddfefbb..f51e4e9 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -646,7 +646,7 @@ LogicalProject(DEPTNO=[$0])
         </Resource>
         <Resource name="planAfter">
             <![CDATA[
-LogicalProject(DEPTNO=[8])
+LogicalProject(DEPTNO=[$0])
   LogicalFilter(condition=[AND(=($0, 7), =($0, 8))])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
@@ -2909,7 +2909,7 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], 
MGR=[$3], HIREDATE=[$4], SAL=[$
         <Resource name="planAfter">
             <![CDATA[
 LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
-  LogicalProject(EMPNO=[10], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$10], NAME=[$11])
+  LogicalProject(EMPNO=[10], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[10], NAME=[$11])
     LogicalJoin(condition=[AND(=(10, $10), =($9, $12))], joinType=[inner])
       LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[+($7, $0)])
         LogicalProject(EMPNO=[10], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
@@ -4213,4 +4213,26 @@ LogicalSort(sort0=[$0], dir0=[ASC], fetch=[10])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testReduceConstantsDup2">
+        <Resource name="sql">
+            <![CDATA[select *
+from emp
+where deptno=7 and deptno=8
+and empno = 10 and mgr is null and empno = 10]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+  LogicalFilter(condition=[AND(=($7, 7), =($7, 8), =($0, 10), IS NULL($3), 
=($0, 10))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(EMPNO=[10], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+  LogicalFilter(condition=[AND(=($7, 7), =($7, 8), =($0, 10), IS NULL($3), 
=($0, 10))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

Reply via email to