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 2022/11/22 06:06:04 UTC

[calcite] branch main updated: [CALCITE-5336] Infer constants from predicates with IS NOT DISTINCT FROM operator

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 5a9cd9f259 [CALCITE-5336] Infer constants from predicates with IS NOT DISTINCT FROM operator
5a9cd9f259 is described below

commit 5a9cd9f259c4dcc34a1bc1291dfcd188b3128156
Author: jtrada168 <ja...@dremio.com>
AuthorDate: Wed Nov 2 18:30:43 2022 -0700

    [CALCITE-5336] Infer constants from predicates with IS NOT DISTINCT FROM operator
    
    Support DateString in RelBuilder.literal(Object).
    
    Close apache/calcite#2961
---
 .../apache/calcite/prepare/CalcitePrepareImpl.java |  3 ++
 .../main/java/org/apache/calcite/rex/RexUtil.java  | 25 ++++++++------
 .../java/org/apache/calcite/tools/RelBuilder.java  |  3 ++
 .../org/apache/calcite/rex/RexProgramTest.java     | 27 ++++++++++++++-
 .../org/apache/calcite/test/RelOptRulesTest.java   | 40 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 16 +++++++++
 6 files changed, 102 insertions(+), 12 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 6d06705677..ad0fff75f5 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.prepare;
 
+import org.apache.calcite.DataContexts;
 import org.apache.calcite.adapter.enumerable.EnumerableCalc;
 import org.apache.calcite.adapter.enumerable.EnumerableConvention;
 import org.apache.calcite.adapter.enumerable.EnumerableInterpretable;
@@ -72,6 +73,7 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexExecutorImpl;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
@@ -442,6 +444,7 @@ public class CalcitePrepareImpl implements CalcitePrepare {
     }
     final VolcanoPlanner planner =
         new VolcanoPlanner(costFactory, externalContext);
+    planner.setExecutor(new RexExecutorImpl(DataContexts.EMPTY));
     planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
     if (CalciteSystemProperty.ENABLE_COLLATION_TRAIT.value()) {
       planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index b68eb43831..9fb321a905 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -400,24 +400,27 @@ public class RexUtil {
   private static <C extends RexNode> void gatherConstraints(Class<C> clazz,
       RexNode predicate, Map<RexNode, C> map, Set<RexNode> excludeSet,
       RexBuilder rexBuilder) {
-    if (predicate.getKind() != SqlKind.EQUALS
-            && predicate.getKind() != SqlKind.IS_NULL) {
-      decompose(excludeSet, predicate);
-      return;
-    }
-    final List<RexNode> operands = ((RexCall) predicate).getOperands();
     final RexNode left;
     final RexNode right;
-    if (predicate.getKind() == SqlKind.EQUALS) {
-      left = operands.get(0);
-      right = operands.get(1);
-    } else { // is null
-      left = operands.get(0);
+    switch (predicate.getKind()) {
+    case EQUALS:
+    case IS_NOT_DISTINCT_FROM:
+      left = ((RexCall) predicate).getOperands().get(0);
+      right = ((RexCall) predicate).getOperands().get(1);
+      break;
+
+    case IS_NULL:
+      left = ((RexCall) predicate).getOperands().get(0);
       if (!left.getType().isNullable()) {
         // There's no sense in inferring $0=null when $0 is not nullable
         return;
       }
       right = rexBuilder.makeNullLiteral(left.getType());
+      break;
+
+    default:
+      decompose(excludeSet, predicate);
+      return;
     }
     // Note that literals are immutable too, and they can only be compared
     // through values.
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index fdf6f0ab62..f48b14272c 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -99,6 +99,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.sql2rel.SqlToRelConverter;
+import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
@@ -475,6 +476,8 @@ public class RelBuilder {
     } else if (value instanceof Enum) {
       return rexBuilder.makeLiteral(value,
           getTypeFactory().createSqlType(SqlTypeName.SYMBOL));
+    } else if (value instanceof DateString) {
+      return rexBuilder.makeDateLiteral((DateString) value);
     } else {
       throw new IllegalArgumentException("cannot convert " + value
           + " (" + value.getClass() + ") to a constant");
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index 637f83a48b..194869993e 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -2779,6 +2779,30 @@ class RexProgramTest extends RexProgramTestBase {
     assertThat(getString(map7), is("{1=CAST(?0.a):BIGINT NOT NULL, ?0.a=1}"));
   }
 
+  /** Unit test for {@link RexUtil#predicateConstants(Class, RexBuilder, List)}
+   * applied to a predicate with {@code IS NOT DISTINCT FROM}. */
+  @Test void testConstantMapIsNotDistinctFrom() {
+    final RelDataType dateColumnType =
+        typeFactory.createTypeWithNullability(
+            typeFactory.createSqlType(SqlTypeName.DATE), true);
+
+    final RexNode dateLiteral =
+        rexBuilder.makeLiteral(new DateString(2020, 12, 11), dateColumnType,
+            false);
+    final RexNode dateColumn = rexBuilder.makeInputRef(dateColumnType, 0);
+
+    final RexNode call =
+        rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+            dateColumn, dateLiteral);
+    final Map<RexNode, RexNode> map =
+        RexUtil.predicateConstants(RexNode.class, rexBuilder,
+            ImmutableList.of(call));
+
+    assertThat(map.isEmpty(), is(false));
+    assertThat(dateLiteral, is(map.get(dateColumn)));
+    assertThat(getString(map), is("{$0=2020-12-11}"));
+  }
+
   @Test void notDistinct() {
     checkSimplify(
         isFalse(isNotDistinctFrom(vBool(0), vBool(1))),
@@ -2850,7 +2874,7 @@ class RexProgramTest extends RexProgramTestBase {
 
   /** Converts a map to a string, sorting on the string representation of its
    * keys. */
-  private static String getString(ImmutableMap<RexNode, RexNode> map) {
+  private static String getString(Map<RexNode, RexNode> map) {
     final TreeMap<String, RexNode> map2 = new TreeMap<>();
     for (Map.Entry<RexNode, RexNode> entry : map.entrySet()) {
       map2.put(entry.getKey().toString(), entry.getValue());
@@ -3404,4 +3428,5 @@ class RexProgramTest extends RexProgramTestBase {
 
     checkSimplify(add(zero, sub(nullInt, nullInt)), "null:INTEGER");
   }
+
 }
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 a812b27bc5..a1262be913 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -121,6 +121,7 @@ import org.apache.calcite.tools.Programs;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RuleSet;
 import org.apache.calcite.tools.RuleSets;
+import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
@@ -4332,6 +4333,13 @@ class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  /** Tests propagation of a filter derived from an "IS NOT DISTINCT FROM"
+   * predicate.
+   *
+   * <p>By the time the rule sees the predicate, it has been converted to
+   * "deptno = 10 OR (deptno IS NULL AND 10 IS NULL)", so
+   * {@link #testPullConstantIntoProjectWithIsNotDistinctFromDate()} is a purer
+   * test of the functionality. */
   @Test void testPullConstantIntoProjectWithIsNotDistinctFrom() {
     final String sql = "select deptno, deptno + 1, empno + deptno\n"
         + "from sales.emp\n"
@@ -4352,6 +4360,37 @@ class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-5336">[CALCITE-5336]
+   * Infer constants from predicates with IS NOT DISTINCT FROM operator</a>.
+   *
+   * <p>Reduce expression rules should identify constants with
+   * {@code IS NOT DISTINCT FROM} operator as they do for {@code =}. */
+  @Test void testPullConstantIntoProjectWithIsNotDistinctFromDate() {
+    // Build a tree equivalent to the SQL
+    //   SELECT hiredate
+    //   FROM emp
+    //   WHERE hiredate IS NOT DISTINCT FROM DATE '2020-12-11'
+    //
+    // We build a tree because if we used the SQL parser, it would expand
+    //   x IS NOT DISTINCT FROM y
+    // to
+    //   x = y OR (x IS NULL AND y IS NULL)
+    final Function<RelBuilder, RelNode> relFn = b ->
+        b.scan("EMP")
+            .filter(
+                b.getRexBuilder()
+                    .makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+                        b.field(4),
+                        b.literal(new DateString(2020, 12, 11))))
+            .project(b.field(4))
+            .build();
+
+    relFn(relFn)
+        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
+        .check();
+  }
+
   @Test void testPullConstantIntoFilter() {
     final String sql = "select * from (select * from sales.emp where deptno = 10)\n"
         + "where deptno + 5 > empno";
@@ -7448,4 +7487,5 @@ class RelOptRulesTest extends RelOptTestBase {
         .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
         .check();
   }
+
 }
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 bdcb9595e1..27010f6367 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -7259,6 +7259,22 @@ LogicalProject(DEPTNO=[$7], EXPR$1=[+($7, 1)], EXPR$2=[+($0, $7)])
 LogicalProject(DEPTNO=[10], EXPR$1=[11], EXPR$2=[+($0, 10)])
   LogicalFilter(condition=[OR(AND(IS NULL($7), IS NULL(10)), IS TRUE(=($7, 10)))])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testPullConstantIntoProjectWithIsNotDistinctFromDate">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(HIREDATE=[$4])
+  LogicalFilter(condition=[IS NOT DISTINCT FROM($4, 2020-12-11)])
+    LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(HIREDATE=[CAST(2020-12-11):DATE])
+  LogicalFilter(condition=[IS NOT DISTINCT FROM($4, 2020-12-11)])
+    LogicalTableScan(table=[[scott, EMP]])
 ]]>
     </Resource>
   </TestCase>