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

mihaibudiu 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 b008df9a71 [CALCITE-7360] The meaning of negation for unsigned numbers 
is not defined
b008df9a71 is described below

commit b008df9a71cea5da44674c07e8599a0836ad1aad
Author: Mihai Budiu <[email protected]>
AuthorDate: Wed Jan 14 21:47:50 2026 -0800

    [CALCITE-7360] The meaning of negation for unsigned numbers is not defined
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../calcite/sql/fun/SqlStdOperatorTable.java       |  2 +-
 .../org/apache/calcite/sql/type/OperandTypes.java  | 65 +++++++++++++++++++++-
 core/src/test/resources/sql/unsigned.iq            |  5 ++
 .../org/apache/calcite/test/SqlOperatorTest.java   | 22 ++++++++
 4 files changed, 92 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 2a26a78929..1137539fdc 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1023,7 +1023,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           80,
           ReturnTypes.ARG0,
           InferTypes.RETURN_TYPE,
-          OperandTypes.NUMERIC_OR_INTERVAL);
+          OperandTypes.SIGNED_OR_INTERVAL);
 
   /**
    * Checked version of prefix arithmetic minus operator, '<code>-</code>'.
diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java 
b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
index 66b4aab9b6..f3c848d34e 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
@@ -404,10 +404,70 @@ public static SqlOperandTypeChecker variadic(
   public static final SqlSingleOperandTypeChecker INTEGER =
       family(SqlTypeFamily.INTEGER);
 
+  /** Operand type checker that only allows signed types.
+   * This is almost like an OR of 4 type families (INTEGER, 
APPROXIMATE_NUMERIC, DECIMAL)
+   * but OR allows implicit casts to any of the types, and this checker 
doesn't. */
+  public static final SqlSingleOperandTypeChecker SIGNED = new 
SqlSingleOperandTypeChecker() {
+    @Override public boolean checkSingleOperandType(SqlCallBinding 
callBinding, SqlNode operand,
+        int iFormalOperand, boolean throwOnFailure) {
+      RelDataType type = SqlTypeUtil.deriveType(callBinding, operand);
+      SqlTypeName typeName = type.getSqlTypeName();
+      boolean isLegal = SqlTypeName.INT_TYPES.contains(typeName)
+          || SqlTypeName.APPROX_TYPES.contains(typeName)
+          || typeName == SqlTypeName.DECIMAL;
+
+      if (!isLegal) {
+        if (throwOnFailure) {
+          throw callBinding.newValidationSignatureError();
+        }
+        return false;
+      }
+      return true;
+    }
+
+    @Override public boolean checkOperandTypes(
+        SqlCallBinding callBinding,
+        boolean throwOnFailure) {
+      // This is a specialized implementation of 
FamilyOperandTypeChecker.checkOperandTypes.
+      SqlNode op = callBinding.operands().get(0);
+      if (!checkSingleOperandType(callBinding, op, 0, false)) {
+        // try to coerce type if it is allowed.
+        boolean coerced = false;
+        if (callBinding.isTypeCoercionEnabled()) {
+          // Also allow expressions that can be coerced to NUMERIC (e.g. type 
CHAR)
+          TypeCoercion typeCoercion = 
callBinding.getValidator().getTypeCoercion();
+          ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
+          builder.add(callBinding.getOperandType(0));
+          ImmutableList<RelDataType> dataTypes = builder.build();
+          coerced =
+              typeCoercion.builtinFunctionCoercion(
+                  callBinding, dataTypes, 
ImmutableList.of(SqlTypeFamily.NUMERIC));
+        }
+        // re-validate the new nodes type.
+        SqlNode op1 = callBinding.operands().get(0);
+        if (!checkSingleOperandType(
+            callBinding,
+            op1,
+            0,
+            throwOnFailure)) {
+          return false;
+        }
+        return coerced;
+      }
+      return true;
+    }
+
+    @Override public String getAllowedSignatures(SqlOperator op, String 
opName) {
+      return SqlUtil.getAliasedSignature(op, opName, 
ImmutableList.of(SqlTypeFamily.INTEGER)) + "\n"
+          + SqlUtil.getAliasedSignature(
+              op, opName, ImmutableList.of(SqlTypeFamily.APPROXIMATE_NUMERIC)) 
+ "\n"
+          + SqlUtil.getAliasedSignature(op, opName, 
ImmutableList.of(SqlTypeFamily.DECIMAL));
+    }
+  };
+
   public static final SqlSingleOperandTypeChecker 
UNSIGNED_NUMERIC_UNSIGNED_NUMERIC =
       family(SqlTypeFamily.UNSIGNED_NUMERIC, SqlTypeFamily.UNSIGNED_NUMERIC);
 
-
   public static final SqlSingleOperandTypeChecker INTEGER_INTEGER =
       family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER);
 
@@ -1299,6 +1359,9 @@ public static SqlSingleOperandTypeChecker same(int 
operandCount,
   public static final SqlSingleOperandTypeChecker NUMERIC_OR_INTERVAL =
       NUMERIC.or(INTERVAL);
 
+  public static final SqlSingleOperandTypeChecker SIGNED_OR_INTERVAL =
+      SIGNED.or(INTERVAL);
+
   public static final SqlSingleOperandTypeChecker NUMERIC_OR_STRING =
       NUMERIC.or(STRING);
 
diff --git a/core/src/test/resources/sql/unsigned.iq 
b/core/src/test/resources/sql/unsigned.iq
index f496dbb220..83112fe87e 100644
--- a/core/src/test/resources/sql/unsigned.iq
+++ b/core/src/test/resources/sql/unsigned.iq
@@ -24,6 +24,11 @@ EXPR$0
 6
 !ok
 
+SELECT -CAST(200 AS INT UNSIGNED);
+java.sql.SQLException: Error while executing SQL "SELECT -CAST(200 AS INT 
UNSIGNED)": From line 1, column 8 to line 1, column 33: Cannot apply '-' to 
arguments of type '-<INTEGER UNSIGNED>'. Supported form(s): '-<INTEGER>'
+
+!error
+
 SELECT CAST(200 AS INT UNSIGNED) - 100;
 EXPR$0
 100
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index e79bbe197b..367fa73068 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -16866,6 +16866,28 @@ private static void 
checkLogicalOrFunc(SqlOperatorFixture f) {
     f.checkNull("CAST(NULL AS INTEGER UNSIGNED) ^^ CAST(NULL AS INTEGER 
UNSIGNED)");
   }
 
+  @Test void testUnsignedArithmetic() {
+    final SqlOperatorFixture f = fixture();
+    // Test case for [CALCITE-7360] The meaning of negation for unsigned 
numbers is not defined
+    f.checkFails("^-CAST (100 AS INT UNSIGNED)^",
+        "Cannot apply '-' to arguments of type '-<INTEGER UNSIGNED>'\\. "
+            + "Supported form\\(s\\): '-<INTEGER>'\\n"
+            + "'-<APPROXIMATE_NUMERIC>'\\n"
+            + "'-<DECIMAL>'\\n"
+            + "'-<DATETIME_INTERVAL>'", false);
+    f.checkScalar("CAST(2 AS INT UNSIGNED)", "2", "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) + CAST(2 AS INT UNSIGNED)", "4",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) + CAST(2 AS TINYINT UNSIGNED)", "4",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) + 2", "4", "INTEGER UNSIGNED NOT 
NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) - 2", "0", "INTEGER UNSIGNED NOT 
NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) - CAST(2 AS TINYINT UNSIGNED)", "0",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) * 2", "4", "INTEGER UNSIGNED NOT 
NULL");
+    f.checkScalar("CAST(2 AS INT UNSIGNED) / 2", "1", "INTEGER UNSIGNED NOT 
NULL");
+  }
+
   /**
    * Test cases for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7109";>[CALCITE-7109]

Reply via email to