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]