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

julianhyde 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 4e9460600c [CALCITE-7826] IS NOT DISTINCT FROM gives wrong result in 
Enumerable convention when applied to DECIMAL values of different scales
4e9460600c is described below

commit 4e9460600c0437478878bbd71875f517c9e576fc
Author: Julian Hyde <[email protected]>
AuthorDate: Sat Sep 26 13:09:54 2026 -0700

    [CALCITE-7826] IS NOT DISTINCT FROM gives wrong result in Enumerable 
convention when applied to DECIMAL values of different scales
    
    In the Enumerable convention, IS NOT DISTINCT FROM and IS DISTINCT
    FROM are implemented by RexImpTable.DistinctFromImplementor, which
    compares non-null operands using Objects.equals and does not harmonize
    the operand types. BigDecimal.equals is sensitive to scale, so 1.10
    (DECIMAL(3, 2)) and 1.1 (DECIMAL(2, 1)) are distinct.
    
    The problem can only be reproduced by creating a RexCall by hand. If a
    query is parsed from SQL,
    StandardConvertletTable.convertIsDistinctFrom expands IS NOT DISTINCT
    FROM into IS NULL and '=' on operands that have been cast to a common
    type. RelBuilder.isNotDistinctFrom uses the same expansion.
    
    To fix the problem, DistinctFromImplementor now compares BigDecimal
    values using SqlFunctions.eq(BigDecimal, BigDecimal).
    
    Add BuiltInMethod.EQ_DECIMAL constant to reference the SqlFunctions.eq
    method.
    
    Close apache/calcite#5296
---
 .../calcite/adapter/enumerable/RexImpTable.java    | 20 ++++++++++++--
 .../org/apache/calcite/util/BuiltInMethod.java     |  1 +
 .../org/apache/calcite/test/RelBuilderTest.java    | 32 ++++++++++++++++++++++
 3 files changed, 51 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 6ef0182b30..28083c6616 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -5050,11 +5050,27 @@ protected Expression 
valueExpression(RexToLixTranslator.Result left,
           Expressions.condition(right.isNullVariable, BOXED_TRUE_EXPR, 
BOXED_FALSE_EXPR),
           Expressions.condition(right.isNullVariable, BOXED_FALSE_EXPR,
               Expressions.condition(
-                  Expressions.call(BuiltInMethod.OBJECTS_EQUAL.method,
-                      left.valueVariable, right.valueVariable),
+                  equalExpression(left.valueVariable, right.valueVariable),
                   BOXED_TRUE_EXPR, BOXED_FALSE_EXPR)));
     }
 
+    /** Returns an expression that compares two non-null values for
+     * equality.
+     *
+     * <p>{@link BigDecimal#equals} is sensitive to scale (for example,
+     * {@code 1.10} is not equal to {@code 1.1}), so {@code BigDecimal} values
+     * are compared using {@link SqlFunctions#eq(BigDecimal, BigDecimal)}, as
+     * the {@code =} operator compares them. Other values are compared using
+     * {@link java.util.Objects#equals}. */
+    private static Expression equalExpression(Expression left,
+        Expression right) {
+      if (left.getType() == BigDecimal.class
+          && right.getType() == BigDecimal.class) {
+        return Expressions.call(BuiltInMethod.EQ_DECIMAL.method, left, right);
+      }
+      return Expressions.call(BuiltInMethod.OBJECTS_EQUAL.method, left, right);
+    }
+
     @Override Expression implementSafe(final RexToLixTranslator translator,
         final RexCall call, final List<Expression> argValueList) {
       throw new IllegalStateException("This implementSafe should not be 
called,"
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java 
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index a8c006d61e..788fe26d29 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -719,6 +719,7 @@ public enum BuiltInMethod {
       Comparable.class),
   LT(SqlFunctions.class, "lt", boolean.class, boolean.class),
   GT(SqlFunctions.class, "gt", boolean.class, boolean.class),
+  EQ_DECIMAL(SqlFunctions.class, "eq", BigDecimal.class, BigDecimal.class),
   BIT_AND(SqlFunctions.class, "bitAnd", long.class, long.class),
   BITCOUNT(SqlFunctions.class, "bitCount", BigDecimal.class),
   BIT_OR(SqlFunctions.class, "bitOr", long.class, long.class),
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java 
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index ebe8990c1d..a5c571e523 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -4908,6 +4908,38 @@ private static RelBuilder assertSize(RelBuilder b,
     }
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7826";>[CALCITE-7826]
+   * IS NOT DISTINCT FROM gives wrong result in Enumerable convention when
+   * applied to DECIMAL values of different scales</a>. */
+  @Test void testIsNotDistinctFromDecimal() throws Exception {
+    // Equivalent SQL:
+    //   SELECT a, b, a = b AS eq, a IS NOT DISTINCT FROM b AS indf,
+    //     a IS DISTINCT FROM b AS idf
+    //   FROM (VALUES (1.10, 1.1)) AS t (a, b)
+    // but SqlToRelConverter and RelBuilder.isNotDistinctFrom expand IS [NOT]
+    // DISTINCT FROM, so we call the operators directly. "a" has type
+    // DECIMAL(3, 2) and "b" has type DECIMAL(2, 1).
+    final RelBuilder b = RelBuilder.create(config().build());
+    final RelNode root =
+        b.values(new String[] {"a", "b"},
+                new BigDecimal("1.10"), new BigDecimal("1.1"))
+            .project(b.field("a"), b.field("b"),
+                b.alias(b.equals(b.field("a"), b.field("b")), "eq"),
+                b.alias(
+                    b.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+                        b.field("a"), b.field("b")), "indf"),
+                b.alias(
+                    b.call(SqlStdOperatorTable.IS_DISTINCT_FROM,
+                        b.field("a"), b.field("b")), "idf"))
+            .build();
+    try (PreparedStatement preparedStatement = RelRunners.run(root)) {
+      String s = CalciteAssert.toString(preparedStatement.executeQuery());
+      final String result = "a=1.10; b=1.1; eq=true; indf=true; idf=false\n";
+      assertThat(s, is(result));
+    }
+  }
+
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1595";>[CALCITE-1595]
    * RelBuilder.call throws NullPointerException if argument types are

Reply via email to