Skip to content

Commit 9398704

Browse files
committed
[CALCITE-7360] The meaning of negation for unsigned numbers is not defined
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
1 parent dd34021 commit 9398704

File tree

4 files changed

+92
-2
lines changed

4 files changed

+92
-2
lines changed

core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
10231023
80,
10241024
ReturnTypes.ARG0,
10251025
InferTypes.RETURN_TYPE,
1026-
OperandTypes.NUMERIC_OR_INTERVAL);
1026+
OperandTypes.SIGNED_OR_INTERVAL);
10271027

10281028
/**
10291029
* Checked version of prefix arithmetic minus operator, '<code>-</code>'.

core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,70 @@ public static SqlOperandTypeChecker variadic(
402402
public static final SqlSingleOperandTypeChecker INTEGER =
403403
family(SqlTypeFamily.INTEGER);
404404

405+
/** Operand type checker that only allows signed types.
406+
* This is almost like an OR of 4 type families (INTEGER, APPROXIMATE_NUMERIC, DECIMAL)
407+
* but OR allows implicit casts to any of the types, and this checker doesn't. */
408+
public static final SqlSingleOperandTypeChecker SIGNED = new SqlSingleOperandTypeChecker() {
409+
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding, SqlNode operand,
410+
int iFormalOperand, boolean throwOnFailure) {
411+
RelDataType type = SqlTypeUtil.deriveType(callBinding, operand);
412+
SqlTypeName typeName = type.getSqlTypeName();
413+
boolean isLegal = SqlTypeName.INT_TYPES.contains(typeName)
414+
|| SqlTypeName.APPROX_TYPES.contains(typeName)
415+
|| typeName == SqlTypeName.DECIMAL;
416+
417+
if (!isLegal) {
418+
if (throwOnFailure) {
419+
throw callBinding.newValidationSignatureError();
420+
}
421+
return false;
422+
}
423+
return true;
424+
}
425+
426+
@Override public boolean checkOperandTypes(
427+
SqlCallBinding callBinding,
428+
boolean throwOnFailure) {
429+
// This is a specialized implementation of FamilyOperandTypeChecker.checkOperandTypes.
430+
SqlNode op = callBinding.operands().get(0);
431+
if (!checkSingleOperandType(callBinding, op, 0, false)) {
432+
// try to coerce type if it is allowed.
433+
boolean coerced = false;
434+
if (callBinding.isTypeCoercionEnabled()) {
435+
// Also allow expressions that can be coerced to NUMERIC (e.g. type CHAR)
436+
TypeCoercion typeCoercion = callBinding.getValidator().getTypeCoercion();
437+
ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
438+
builder.add(callBinding.getOperandType(0));
439+
ImmutableList<RelDataType> dataTypes = builder.build();
440+
coerced =
441+
typeCoercion.builtinFunctionCoercion(
442+
callBinding, dataTypes, ImmutableList.of(SqlTypeFamily.NUMERIC));
443+
}
444+
// re-validate the new nodes type.
445+
SqlNode op1 = callBinding.operands().get(0);
446+
if (!checkSingleOperandType(
447+
callBinding,
448+
op1,
449+
0,
450+
throwOnFailure)) {
451+
return false;
452+
}
453+
return coerced;
454+
}
455+
return true;
456+
}
457+
458+
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
459+
return SqlUtil.getAliasedSignature(op, opName, ImmutableList.of(SqlTypeFamily.INTEGER)) + "\n"
460+
+ SqlUtil.getAliasedSignature(
461+
op, opName, ImmutableList.of(SqlTypeFamily.APPROXIMATE_NUMERIC)) + "\n"
462+
+ SqlUtil.getAliasedSignature(op, opName, ImmutableList.of(SqlTypeFamily.DECIMAL));
463+
}
464+
};
465+
405466
public static final SqlSingleOperandTypeChecker UNSIGNED_NUMERIC_UNSIGNED_NUMERIC =
406467
family(SqlTypeFamily.UNSIGNED_NUMERIC, SqlTypeFamily.UNSIGNED_NUMERIC);
407468

408-
409469
public static final SqlSingleOperandTypeChecker INTEGER_INTEGER =
410470
family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER);
411471

@@ -1297,6 +1357,9 @@ public static SqlSingleOperandTypeChecker same(int operandCount,
12971357
public static final SqlSingleOperandTypeChecker NUMERIC_OR_INTERVAL =
12981358
NUMERIC.or(INTERVAL);
12991359

1360+
public static final SqlSingleOperandTypeChecker SIGNED_OR_INTERVAL =
1361+
SIGNED.or(INTERVAL);
1362+
13001363
public static final SqlSingleOperandTypeChecker NUMERIC_OR_STRING =
13011364
NUMERIC.or(STRING);
13021365

core/src/test/resources/sql/unsigned.iq

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ EXPR$0
2424
6
2525
!ok
2626

27+
SELECT -CAST(200 AS INT UNSIGNED);
28+
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>'
29+
30+
!error
31+
2732
SELECT CAST(200 AS INT UNSIGNED) - 100;
2833
EXPR$0
2934
100

testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16747,6 +16747,28 @@ private static void checkLogicalOrFunc(SqlOperatorFixture f) {
1674716747
f.checkNull("CAST(NULL AS INTEGER UNSIGNED) ^^ CAST(NULL AS INTEGER UNSIGNED)");
1674816748
}
1674916749

16750+
@Test void testUnsignedArithmetic() {
16751+
final SqlOperatorFixture f = fixture();
16752+
// Test case for [CALCITE-7360] The meaning of negation for unsigned numbers is not defined
16753+
f.checkFails("^-CAST (100 AS INT UNSIGNED)^",
16754+
"Cannot apply '-' to arguments of type '-<INTEGER UNSIGNED>'\\. "
16755+
+ "Supported form\\(s\\): '-<INTEGER>'\\n"
16756+
+ "'-<APPROXIMATE_NUMERIC>'\\n"
16757+
+ "'-<DECIMAL>'\\n"
16758+
+ "'-<DATETIME_INTERVAL>'", false);
16759+
f.checkScalar("CAST(2 AS INT UNSIGNED)", "2", "INTEGER UNSIGNED NOT NULL");
16760+
f.checkScalar("CAST(2 AS INT UNSIGNED) + CAST(2 AS INT UNSIGNED)", "4",
16761+
"INTEGER UNSIGNED NOT NULL");
16762+
f.checkScalar("CAST(2 AS INT UNSIGNED) + CAST(2 AS TINYINT UNSIGNED)", "4",
16763+
"INTEGER UNSIGNED NOT NULL");
16764+
f.checkScalar("CAST(2 AS INT UNSIGNED) + 2", "4", "INTEGER UNSIGNED NOT NULL");
16765+
f.checkScalar("CAST(2 AS INT UNSIGNED) - 2", "0", "INTEGER UNSIGNED NOT NULL");
16766+
f.checkScalar("CAST(2 AS INT UNSIGNED) - CAST(2 AS TINYINT UNSIGNED)", "0",
16767+
"INTEGER UNSIGNED NOT NULL");
16768+
f.checkScalar("CAST(2 AS INT UNSIGNED) * 2", "4", "INTEGER UNSIGNED NOT NULL");
16769+
f.checkScalar("CAST(2 AS INT UNSIGNED) / 2", "1", "INTEGER UNSIGNED NOT NULL");
16770+
}
16771+
1675016772
/**
1675116773
* Test cases for
1675216774
* <a href="https://issues.apache.org/jira/browse/CALCITE-7109">[CALCITE-7109]

0 commit comments

Comments
 (0)