This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch site
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/site by this push:
new 00be2724ef [CALCITE-6685] Support checked arithmetic
00be2724ef is described below
commit 00be2724ef181d2f8581231b9f6ec4747571568c
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Nov 11 23:42:22 2024 -0800
[CALCITE-6685] Support checked arithmetic
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/adapter/enumerable/RexImpTable.java | 34 +++++-
.../main/java/org/apache/calcite/plan/Strong.java | 6 +
.../apache/calcite/plan/SubstitutionVisitor.java | 2 +
.../java/org/apache/calcite/prepare/Prepare.java | 8 +-
.../java/org/apache/calcite/rex/RexSimplify.java | 18 ++-
.../org/apache/calcite/runtime/SqlFunctions.java | 114 ++++++++++++++++++
.../org/apache/calcite/sql/SqlBinaryOperator.java | 2 +
.../java/org/apache/calcite/sql/SqlDialect.java | 4 +
.../main/java/org/apache/calcite/sql/SqlKind.java | 42 ++++++-
.../calcite/sql/fun/SqlStdOperatorTable.java | 79 +++++++++++++
.../sql/validate/SqlAbstractConformance.java | 4 +
.../calcite/sql/validate/SqlConformance.java | 7 ++
.../calcite/sql/validate/SqlConformanceEnum.java | 29 +++++
.../sql/validate/SqlDelegatingConformance.java | 4 +
.../apache/calcite/sql2rel/ConvertToChecked.java | 113 ++++++++++++++++++
.../src/main/java/org/apache/calcite/util/Bug.java | 6 -
.../calcite/jdbc/CalciteRemoteDriverTest.java | 3 +-
.../org/apache/calcite/test/CoreQuidemTest.java | 4 +-
.../org/apache/calcite/test/SqlValidatorTest.java | 6 +
core/src/test/resources/sql/cast.iq | 57 ++++++++-
core/src/test/resources/sql/measure-paper.iq | 14 +--
.../java/org/apache/calcite/linq4j/Nullness.java | 2 +-
.../apache/calcite/linq4j/tree/ExpressionType.java | 6 +
.../calcite/linq4j/tree/UnaryExpression.java | 2 +
.../apache/calcite/linq4j/test/ExpressionTest.java | 10 +-
.../apache/calcite/linq4j/test/OptimizerTest.java | 14 +--
site/_docs/history.md | 8 +-
.../calcite/sql/test/SqlOperatorFixture.java | 6 +
.../org/apache/calcite/test/SqlOperatorTest.java | 128 +++++++++++----------
29 files changed, 631 insertions(+), 101 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 2b21dc3499..4a1506f029 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
@@ -108,16 +108,21 @@ import static
com.google.common.base.Preconditions.checkArgument;
import static
org.apache.calcite.adapter.enumerable.EnumUtils.generateCollatorExpression;
import static org.apache.calcite.linq4j.tree.ExpressionType.Add;
+import static org.apache.calcite.linq4j.tree.ExpressionType.AddChecked;
import static org.apache.calcite.linq4j.tree.ExpressionType.Divide;
+import static org.apache.calcite.linq4j.tree.ExpressionType.DivideChecked;
import static org.apache.calcite.linq4j.tree.ExpressionType.Equal;
import static org.apache.calcite.linq4j.tree.ExpressionType.GreaterThan;
import static org.apache.calcite.linq4j.tree.ExpressionType.GreaterThanOrEqual;
import static org.apache.calcite.linq4j.tree.ExpressionType.LessThan;
import static org.apache.calcite.linq4j.tree.ExpressionType.LessThanOrEqual;
import static org.apache.calcite.linq4j.tree.ExpressionType.Multiply;
+import static org.apache.calcite.linq4j.tree.ExpressionType.MultiplyChecked;
import static org.apache.calcite.linq4j.tree.ExpressionType.Negate;
+import static org.apache.calcite.linq4j.tree.ExpressionType.NegateChecked;
import static org.apache.calcite.linq4j.tree.ExpressionType.NotEqual;
import static org.apache.calcite.linq4j.tree.ExpressionType.Subtract;
+import static org.apache.calcite.linq4j.tree.ExpressionType.SubtractChecked;
import static org.apache.calcite.linq4j.tree.ExpressionType.UnaryPlus;
import static org.apache.calcite.sql.fun.SqlInternalOperators.LITERAL_AGG;
import static org.apache.calcite.sql.fun.SqlInternalOperators.THROW_UNLESS;
@@ -358,6 +363,12 @@ import static
org.apache.calcite.sql.fun.SqlStdOperatorTable.CBRT;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CEIL;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHARACTER_LENGTH;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHAR_LENGTH;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_DIVIDE;
+import static
org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_DIVIDE_INTEGER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_MINUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_MULTIPLY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_PLUS;
+import static
org.apache.calcite.sql.fun.SqlStdOperatorTable.CHECKED_UNARY_MINUS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CLASSIFIER;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COALESCE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COLLECT;
@@ -786,6 +797,13 @@ public class RexImpTable {
defineUnary(UNARY_MINUS, Negate, NullPolicy.STRICT,
BuiltInMethod.BIG_DECIMAL_NEGATE.getMethodName());
defineUnary(UNARY_PLUS, UnaryPlus, NullPolicy.STRICT, null);
+ // checked arithmetic
+ defineBinary(CHECKED_PLUS, AddChecked, NullPolicy.STRICT, "checkedPlus");
+ defineBinary(CHECKED_MINUS, SubtractChecked, NullPolicy.STRICT,
"checkedMinus");
+ defineBinary(CHECKED_MULTIPLY, MultiplyChecked, NullPolicy.STRICT,
"checkedMultiply");
+ defineBinary(CHECKED_DIVIDE, DivideChecked, NullPolicy.STRICT,
"checkedDivide");
+ defineBinary(CHECKED_DIVIDE_INTEGER, DivideChecked, NullPolicy.STRICT,
"checkedDivide");
+ defineUnary(CHECKED_UNARY_MINUS, NegateChecked, NullPolicy.STRICT,
"checkedUnaryMinus");
defineMethod(MOD, BuiltInMethod.MOD.method, NullPolicy.STRICT);
defineMethod(EXP, BuiltInMethod.EXP.method, NullPolicy.STRICT);
@@ -3137,6 +3155,11 @@ public class RexImpTable {
SqlStdOperatorTable.GREATER_THAN,
SqlStdOperatorTable.GREATER_THAN_OR_EQUAL);
+ private static final List<SqlOperator> CHECKED_OPERATORS =
+ ImmutableList.of(
+ CHECKED_DIVIDE, CHECKED_PLUS, CHECKED_MINUS, CHECKED_MULTIPLY,
+ CHECKED_UNARY_MINUS);
+
private static final List<SqlBinaryOperator> EQUALS_OPERATORS =
ImmutableList.of(
SqlStdOperatorTable.EQUALS,
@@ -3209,6 +3232,11 @@ public class RexImpTable {
argValueList);
}
+ // For checked arithmetic call the method.
+ if (CHECKED_OPERATORS.contains(op)) {
+ return Expressions.call(SqlFunctions.class, backupMethodName,
argValueList);
+ }
+
return Expressions.makeBinary(expressionType,
argValueList.get(0), argValueList.get(1));
}
@@ -3266,6 +3294,8 @@ public class RexImpTable {
if (expressionType == ExpressionType.Negate && argValue.type ==
BigDecimal.class
&& null != backupMethodName) {
e = Expressions.call(argValue, backupMethodName);
+ } else if (expressionType == NegateChecked && null != backupMethodName) {
+ e = Expressions.call(SqlFunctions.class, backupMethodName,
argValueList);
} else {
e = Expressions.makeUnary(expressionType, argValue);
}
@@ -3523,7 +3553,9 @@ public class RexImpTable {
}
// Short-circuit if no cast is required
- if (call.getType().equals(sourceType)) {
+ if (call.getType().equals(sourceType)
+ // However, do not elide casts to decimal types, they perform bounds
checking
+ && sourceType.getSqlTypeName() != SqlTypeName.DECIMAL) {
// No cast required, omit cast
return argValueList.get(0);
}
diff --git a/core/src/main/java/org/apache/calcite/plan/Strong.java
b/core/src/main/java/org/apache/calcite/plan/Strong.java
index 3c8be238f0..759ad69e9f 100644
--- a/core/src/main/java/org/apache/calcite/plan/Strong.java
+++ b/core/src/main/java/org/apache/calcite/plan/Strong.java
@@ -322,6 +322,12 @@ public class Strong {
map.put(SqlKind.MINUS, Policy.ANY);
map.put(SqlKind.MINUS_PREFIX, Policy.ANY);
map.put(SqlKind.TIMES, Policy.ANY);
+ map.put(SqlKind.CHECKED_PLUS, Policy.ANY);
+ map.put(SqlKind.CHECKED_MINUS, Policy.ANY);
+ map.put(SqlKind.CHECKED_MINUS_PREFIX, Policy.ANY);
+ map.put(SqlKind.CHECKED_TIMES, Policy.ANY);
+ map.put(SqlKind.CHECKED_DIVIDE, Policy.ANY);
+
map.put(SqlKind.DIVIDE, Policy.ANY);
map.put(SqlKind.CAST, Policy.ANY);
map.put(SqlKind.REINTERPRET, Policy.ANY);
diff --git
a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
index 20d4f75dc9..5a76cb99b3 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
@@ -378,6 +378,8 @@ public class SubstitutionVisitor {
final RexNode e = RexUtil.expandSearch(rexBuilder, null, condition);
return canonizeNode(rexBuilder, e);
}
+ case CHECKED_PLUS:
+ case CHECKED_TIMES:
case PLUS:
case TIMES: {
RexCall call = (RexCall) condition;
diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
index f14c864c1c..586cce8385 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -56,6 +56,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import org.apache.calcite.sql.validate.SqlValidatorTable;
+import org.apache.calcite.sql2rel.ConvertToChecked;
import org.apache.calcite.sql2rel.InitializerContext;
import org.apache.calcite.sql2rel.InitializerExpressionFactory;
import org.apache.calcite.sql2rel.SqlToRelConverter;
@@ -228,7 +229,7 @@ public abstract class Prepare {
public PreparedResult prepareSql(
SqlNode sqlQuery,
SqlNode sqlNodeOriginal,
- Class runtimeContextClass,
+ Class<?> runtimeContextClass,
SqlValidator validator,
boolean needsValidation) {
init(runtimeContextClass);
@@ -255,6 +256,11 @@ public abstract class Prepare {
RelRoot root =
sqlToRelConverter.convertQuery(sqlQuery, needsValidation, true);
+ if (this.context.config().conformance().checkedArithmetic()) {
+ ConvertToChecked checkedConv = new
ConvertToChecked(root.rel.getCluster().getRexBuilder());
+ RelNode rel = checkedConv.visit(root.rel);
+ root = root.withRel(rel);
+ }
Hook.CONVERTED.run(root.rel);
if (timingTracer != null) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index e9002187b4..b95c681185 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -314,6 +314,7 @@ public class RexSimplify {
case LIKE:
return simplifyLike((RexCall) e, unknownAs);
case MINUS_PREFIX:
+ case CHECKED_MINUS_PREFIX:
return simplifyUnaryMinus((RexCall) e, unknownAs);
case PLUS_PREFIX:
return simplifyUnaryPlus((RexCall) e, unknownAs);
@@ -321,6 +322,10 @@ public class RexSimplify {
case MINUS:
case TIMES:
case DIVIDE:
+ case CHECKED_PLUS:
+ case CHECKED_MINUS:
+ case CHECKED_TIMES:
+ case CHECKED_DIVIDE:
return simplifyArithmetic((RexCall) e);
case M2V:
return simplifyM2v((RexCall) e);
@@ -430,13 +435,18 @@ public class RexSimplify {
assert e.getOperands().size() == 2;
switch (e.getKind()) {
+ // These simplifications are safe for both checked and unchecked
arithemtic.
case PLUS:
+ case CHECKED_PLUS:
return simplifyPlus(e);
case MINUS:
+ case CHECKED_MINUS:
return simplifyMinus(e);
case TIMES:
+ case CHECKED_TIMES:
return simplifyMultiply(e);
case DIVIDE:
+ case CHECKED_DIVIDE:
return simplifyDivide(e);
default:
throw new IllegalArgumentException("Unsupported arithmeitc operation " +
e.getKind());
@@ -1327,9 +1337,13 @@ public class RexSimplify {
safeOps.addAll(SqlKind.COMPARISON);
safeOps.add(SqlKind.PLUS_PREFIX);
safeOps.add(SqlKind.MINUS_PREFIX);
+ safeOps.add(SqlKind.CHECKED_MINUS_PREFIX);
safeOps.add(SqlKind.PLUS);
safeOps.add(SqlKind.MINUS);
safeOps.add(SqlKind.TIMES);
+ safeOps.add(SqlKind.CHECKED_PLUS);
+ safeOps.add(SqlKind.CHECKED_MINUS);
+ safeOps.add(SqlKind.CHECKED_TIMES);
safeOps.add(SqlKind.IS_FALSE);
safeOps.add(SqlKind.IS_NOT_FALSE);
safeOps.add(SqlKind.IS_TRUE);
@@ -2206,7 +2220,9 @@ public class RexSimplify {
operand = simplify(operand, UNKNOWN);
// The type of DYNAMIC_PARAM is indeterminate, so the cast cannot be
eliminated
if (operand.getKind() != SqlKind.DYNAMIC_PARAM
- && sameTypeOrNarrowsNullability(e.getType(), operand.getType())) {
+ && sameTypeOrNarrowsNullability(e.getType(), operand.getType())
+ // DECIMAL casts are never no-ops: they perform bounds checking
+ && e.getType().getSqlTypeName() != SqlTypeName.DECIMAL) {
return operand;
}
if (RexUtil.isLosslessCast(operand)) {
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 09e3a41d65..b8c4a4bc04 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2378,6 +2378,40 @@ public class SqlFunctions {
throw notArithmetic("+", b0, b1);
}
+ // checked +
+
+ static byte intToByte(int value) {
+ if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+ throw new ArithmeticException(
+ "integer overflow: Value " + value + " does not fit in a TINYINT");
+ }
+ return (byte) value;
+ }
+
+ static short intToShort(int value) {
+ if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+ throw new ArithmeticException(
+ "integer overflow: Value " + value + " does not fit in a SMALLINT");
+ }
+ return (short) value;
+ }
+
+ public static byte checkedPlus(byte b0, byte b1) {
+ return intToByte(b0 + b1);
+ }
+
+ public static short checkedPlus(short b0, short b1) {
+ return intToShort(b0 + b1);
+ }
+
+ public static int checkedPlus(int b0, int b1) {
+ return Math.addExact(b0, b1);
+ }
+
+ public static long checkedPlus(long b0, long b1) {
+ return Math.addExact(b0, b1);
+ }
+
// -
/** SQL <code>-</code> operator applied to int values. */
@@ -2436,6 +2470,40 @@ public class SqlFunctions {
throw notArithmetic("-", b0, b1);
}
+ // checked -
+
+ public static byte checkedMinus(byte b0, byte b1) {
+ return intToByte(b0 - b1);
+ }
+
+ public static short checkedMinus(short b0, short b1) {
+ return intToShort(b0 - b1);
+ }
+
+ public static int checkedMinus(int b0, int b1) {
+ return Math.subtractExact(b0, b1);
+ }
+
+ public static long checkedMinus(long b0, long b1) {
+ return Math.subtractExact(b0, b1);
+ }
+
+ public static byte checkedUnaryMinus(byte b) {
+ return intToByte(-b);
+ }
+
+ public static short checkedUnaryMinus(short b) {
+ return intToShort(-b);
+ }
+
+ public static int checkedUnaryMinus(int b) {
+ return Math.subtractExact(0, b);
+ }
+
+ public static long checkedUnaryMinus(long b) {
+ return Math.subtractExact(0, b);
+ }
+
// /
/** SQL <code>/</code> operator applied to int values. */
@@ -2508,6 +2576,34 @@ public class SqlFunctions {
.divide(b1, RoundingMode.HALF_DOWN).longValue();
}
+ public static byte checkedDivide(byte b0, byte b1) {
+ return intToByte(b0 / b1);
+ }
+
+ public static short checkedDivide(short b0, short b1) {
+ return intToShort(b0 * b1);
+ }
+
+ public static int checkedDivide(int b0, int b1) {
+ // Implementation taken from Java 19
+ int q = b0 / b1;
+ if ((b0 & b1 & q) >= 0) {
+ return q;
+ } else {
+ throw new ArithmeticException("integer overflow");
+ }
+ }
+
+ public static long checkedDivide(long b0, long b1) {
+ // Implementation taken from Java 19
+ long q = b0 / b1;
+ if ((b0 & b1 & q) >= 0) {
+ return q;
+ } else {
+ throw new ArithmeticException("integer overflow");
+ }
+ }
+
// *
/** SQL <code>*</code> operator applied to int values. */
@@ -2568,6 +2664,24 @@ public class SqlFunctions {
throw notArithmetic("*", b0, b1);
}
+ // checked *
+
+ public static byte checkedMultiply(byte b0, byte b1) {
+ return intToByte(b0 * b1);
+ }
+
+ public static short checkedMultiply(short b0, short b1) {
+ return intToShort(b0 * b1);
+ }
+
+ public static int checkedMultiply(int b0, int b1) {
+ return Math.multiplyExact(b0, b1);
+ }
+
+ public static long checkedMultiply(long b0, long b1) {
+ return Math.multiplyExact(b0, b1);
+ }
+
/** SQL <code>SAFE_ADD</code> function applied to long values. */
public static @Nullable Long safeAdd(long b0, long b1) {
try {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlBinaryOperator.java
b/core/src/main/java/org/apache/calcite/sql/SqlBinaryOperator.java
index 96ef1aabe6..514c61fe09 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlBinaryOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlBinaryOperator.java
@@ -109,6 +109,8 @@ public class SqlBinaryOperator extends SqlOperator {
case AND:
case PLUS:
case TIMES:
+ case CHECKED_PLUS:
+ case CHECKED_TIMES:
return this;
case GREATER_THAN:
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
index 9ebc4c383c..7d540abc98 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
@@ -821,6 +821,10 @@ public class SqlDialect {
case PLUS:
case ROW:
case TIMES:
+ case CHECKED_PLUS:
+ case CHECKED_TIMES:
+ case CHECKED_MINUS:
+ case CHECKED_DIVIDE:
return true;
default:
return BUILT_IN_OPERATORS_LIST.contains(operator);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 03a0178f05..0994306450 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -307,6 +307,31 @@ public enum SqlKind {
*/
MINUS,
+ /**
+ * Checked version of PLUS, which produces a runtime error on overflow.
+ * Not used for date/time arithmetic.
+ */
+ CHECKED_PLUS,
+
+ /**
+ * Checked version of MINUS, which produces a runtime error on overflow.
+ * Not used for date/time arithmetic.
+ */
+ CHECKED_MINUS,
+
+ /**
+ * Checked version of TIMES, which produces a runtime error on overflow.
+ * Not used for date/time arithmetic.
+ */
+ CHECKED_TIMES,
+
+ /**
+ * Checked version of DIVIDE, which produces a runtime error on overflow.
+ * For example, INT_MIN / -1.
+ * Not used for date/time arithmetic.
+ */
+ CHECKED_DIVIDE,
+
/**
* Alternation operator in a pattern expression within a
* {@code MATCH_RECOGNIZE} clause.
@@ -530,6 +555,11 @@ public enum SqlKind {
*/
MINUS_PREFIX,
+ /**
+ * Checked version of unary minus operator.
+ */
+ CHECKED_MINUS_PREFIX,
+
/** {@code EXISTS} operator. */
EXISTS,
@@ -1516,10 +1546,16 @@ public enum SqlKind {
* {@link #MINUS}
* {@link #TIMES}
* {@link #DIVIDE}
- * {@link #MOD}.
+ * {@link #MOD}
+ * and the corresponding checked arithmetic operations.
*/
public static final Set<SqlKind> BINARY_ARITHMETIC =
- EnumSet.of(PLUS, MINUS, TIMES, DIVIDE, MOD);
+ EnumSet.of(PLUS, MINUS, TIMES, DIVIDE, MOD,
+ CHECKED_PLUS, CHECKED_MINUS, CHECKED_TIMES, CHECKED_DIVIDE);
+
+ public static final Set<SqlKind> CHECKED_ARITHMETIC =
+ EnumSet.of(CHECKED_PLUS, CHECKED_MINUS, CHECKED_TIMES, CHECKED_DIVIDE,
CHECKED_MINUS_PREFIX);
+
/**
* Category of binary equality.
@@ -1574,7 +1610,7 @@ public enum SqlKind {
*/
@API(since = "1.22", status = API.Status.EXPERIMENTAL)
public static final Set<SqlKind> SYMMETRICAL_SAME_ARG_TYPE =
- EnumSet.of(PLUS, TIMES);
+ EnumSet.of(PLUS, TIMES, CHECKED_PLUS, CHECKED_TIMES);
/**
* Simple binary operators are those operators which expects operands from
the same Domain.
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 00dd8df9b3..949e7e5fa8 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
@@ -294,6 +294,19 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.FIRST_KNOWN,
OperandTypes.DIVISION_OPERATOR);
+ /**
+ * Checked version of arithmetic division operator, '<code>/</code>'.
+ */
+ public static final SqlBinaryOperator CHECKED_DIVIDE =
+ new SqlBinaryOperator(
+ "/",
+ SqlKind.CHECKED_DIVIDE,
+ 60,
+ true,
+ ReturnTypes.QUOTIENT_NULLABLE,
+ InferTypes.FIRST_KNOWN,
+ OperandTypes.DIVISION_OPERATOR);
+
/**
* Arithmetic remainder operator, '<code>%</code>',
* an alternative to {@link #MOD} allowed if under certain conformance
levels.
@@ -340,6 +353,19 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.FIRST_KNOWN,
OperandTypes.DIVISION_OPERATOR);
+ /**
+ * Checked version of integer division. @see DIVIDE_INTEGER.
+ */
+ public static final SqlBinaryOperator CHECKED_DIVIDE_INTEGER =
+ new SqlBinaryOperator(
+ "/INT",
+ SqlKind.CHECKED_DIVIDE,
+ 60,
+ true,
+ ReturnTypes.INTEGER_QUOTIENT_NULLABLE,
+ InferTypes.FIRST_KNOWN,
+ OperandTypes.DIVISION_OPERATOR);
+
/**
* Dot operator, '<code>.</code>', used for referencing fields of records.
*/
@@ -547,6 +573,21 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.FIRST_KNOWN,
OperandTypes.MINUS_OPERATOR);
+ /**
+ * Checked version of infix arithmetic minus operator, '<code>-</code>'.
+ */
+ public static final SqlBinaryOperator CHECKED_MINUS =
+ new SqlMonotonicBinaryOperator(
+ "-",
+ SqlKind.CHECKED_MINUS,
+ 40,
+ true,
+
+ // Same type inference strategy as sum
+ ReturnTypes.NULLABLE_SUM,
+ InferTypes.FIRST_KNOWN,
+ OperandTypes.MINUS_OPERATOR);
+
/**
* Arithmetic multiplication operator, '<code>*</code>'.
*/
@@ -560,6 +601,19 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.FIRST_KNOWN,
OperandTypes.MULTIPLY_OPERATOR);
+ /**
+ * Checked version of arithmetic multiplication operator, '<code>*</code>'.
+ */
+ public static final SqlBinaryOperator CHECKED_MULTIPLY =
+ new SqlMonotonicBinaryOperator(
+ "*",
+ SqlKind.CHECKED_TIMES,
+ 60,
+ true,
+ ReturnTypes.PRODUCT_NULLABLE,
+ InferTypes.FIRST_KNOWN,
+ OperandTypes.MULTIPLY_OPERATOR);
+
/**
* Logical not-equals operator, '<code><></code>'.
*/
@@ -599,6 +653,19 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.FIRST_KNOWN,
OperandTypes.PLUS_OPERATOR);
+ /**
+ * Checked version of infix arithmetic plus operator, '<code>+</code>'.
+ */
+ public static final SqlBinaryOperator CHECKED_PLUS =
+ new SqlMonotonicBinaryOperator(
+ "+",
+ SqlKind.CHECKED_PLUS,
+ 40,
+ true,
+ ReturnTypes.NULLABLE_SUM,
+ InferTypes.FIRST_KNOWN,
+ OperandTypes.PLUS_OPERATOR);
+
/**
* Infix datetime plus operator, '<code>DATETIME + INTERVAL</code>'.
*/
@@ -951,6 +1018,18 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
InferTypes.RETURN_TYPE,
OperandTypes.NUMERIC_OR_INTERVAL);
+ /**
+ * Checked version of prefix arithmetic minus operator, '<code>-</code>'.
+ */
+ public static final SqlPrefixOperator CHECKED_UNARY_MINUS =
+ new SqlPrefixOperator(
+ "-",
+ SqlKind.CHECKED_MINUS_PREFIX,
+ 80,
+ ReturnTypes.ARG0,
+ InferTypes.RETURN_TYPE,
+ OperandTypes.NUMERIC_OR_INTERVAL);
+
/**
* Prefix arithmetic plus operator, '<code>+</code>'.
*
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
index 3205a48b40..d1f5c0d853 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
@@ -144,4 +144,8 @@ public abstract class SqlAbstractConformance implements
SqlConformance {
@Override public boolean allowLenientCoercion() {
return SqlConformanceEnum.DEFAULT.allowLenientCoercion();
}
+
+ @Override public boolean checkedArithmetic() {
+ return SqlConformanceEnum.DEFAULT.checkedArithmetic();
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
index b8501f02f1..b0843876c6 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
@@ -583,4 +583,11 @@ public interface SqlConformance {
*/
@Experimental
boolean allowLenientCoercion();
+
+ /**
+ * Whether the implementation uses checked arithmetic.
+ * Most SQL dialects use checked arithmetic at runtime:
+ * they terminate with a fatal error on overflow.
+ */
+ boolean checkedArithmetic();
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
index a610f19f16..2f9658d3b9 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
@@ -445,4 +445,33 @@ public enum SqlConformanceEnum implements SqlConformance {
return false;
}
}
+
+ @Override public boolean checkedArithmetic() {
+ switch (this) {
+ case DEFAULT:
+ case LENIENT:
+ case BABEL:
+ case ORACLE_10:
+ case ORACLE_12:
+ case PRAGMATIC_99:
+ case PRESTO:
+ case PRAGMATIC_2003:
+ case STRICT_92:
+ case STRICT_99:
+ case STRICT_2003:
+ case MYSQL_5:
+ // MySQL in strict mode uses checked arithmetic
+ // https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sql-mode-strict
+ default:
+ return false;
+ // Postgres also has checked arithmetic, but there is no such conformance
+ case BIG_QUERY:
+ // BigQuery throws on overflow
+ //
https://cloud.google.com/bigquery/docs/reference/standard-sql/operators
+ case SQL_SERVER_2008:
+ // SET ARITHABORT OFF can be used to turn off overflow behavior, but the
default is ON
+ //
https://learn.microsoft.com/en-us/sql/t-sql/statements/set-arithabort-transact-sql
+ return true;
+ }
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
index c8e2f7cdd2..3c12cfd976 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
@@ -150,4 +150,8 @@ public class SqlDelegatingConformance implements
SqlConformance {
@Override public boolean allowLenientCoercion() {
return delegate.allowLenientCoercion();
}
+
+ @Override public boolean checkedArithmetic() {
+ return delegate.checkedArithmetic();
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
new file mode 100644
index 0000000000..d612818d27
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql2rel;
+
+import org.apache.calcite.rel.RelHomogeneousShuttle;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+
+import java.util.List;
+
+/**
+ * Converts a RelNode tree such that numeric arithmetic operations
+ * use checked arithmetic. Most SQL dialects should use checked arithmetic,
+ * i.e., they should produce a runtime error on overflow.
+ */
+public class ConvertToChecked extends RelHomogeneousShuttle {
+ final ConvertRexToChecked converter;
+
+ public ConvertToChecked(RexBuilder builder) {
+ this.converter = new ConvertRexToChecked(builder);
+ }
+
+ @Override public RelNode visit(RelNode other) {
+ RelNode mNode = super.visitChildren(other);
+ return mNode.accept(converter);
+ }
+
+ /**
+ * Visitor which rewrites an expression tree such that all
+ * arithmetic operations that produce numeric values use checked arithmetic.
+ */
+ static class ConvertRexToChecked extends RexShuttle {
+ private final RexBuilder builder;
+
+ ConvertRexToChecked(RexBuilder builder) {
+ this.builder = builder;
+ }
+
+ @Override public RexNode visitCall(final RexCall call) {
+ boolean[] update = {false};
+ List<RexNode> clonedOperands = visitList(call.operands, update);
+ SqlKind kind = call.getKind();
+ SqlOperator operator = call.getOperator();
+ switch (kind) {
+ case PLUS:
+ operator = SqlStdOperatorTable.CHECKED_PLUS;
+ break;
+ case MINUS:
+ operator = SqlStdOperatorTable.CHECKED_MINUS;
+ break;
+ case TIMES:
+ operator = SqlStdOperatorTable.CHECKED_MULTIPLY;
+ break;
+ case MINUS_PREFIX:
+ operator = SqlStdOperatorTable.CHECKED_UNARY_MINUS;
+ break;
+ case DIVIDE:
+ operator = SqlStdOperatorTable.CHECKED_DIVIDE;
+ break;
+ default:
+ break;
+ }
+ SqlTypeName resultType = call.getType().getSqlTypeName();
+ if (resultType == SqlTypeName.DECIMAL) {
+ // Checked decimal arithmetic is implemented using unchecked
+ // arithmetic followed by a CAST, which is always checked
+ RexCall result;
+ if (operator == call.getOperator()) {
+ result = call.clone(call.getType(), clonedOperands);
+ } else {
+ result = call;
+ }
+ return builder.makeCast(call.getType(), result);
+ } else if (!SqlTypeName.EXACT_TYPES.contains(resultType)
+ || (resultType == SqlTypeName.DECIMAL)) {
+ // Do not rewrite operator if the type is e.g., DOUBLE or DATE
+ operator = call.getOperator();
+ }
+ update[0] = update[0] || operator != call.getOperator();
+ if (update[0]) {
+ if (operator == call.getOperator()) {
+ return call.clone(call.getType(), clonedOperands);
+ } else {
+ return builder.makeCall(
+ call.getParserPosition(), call.getType(), operator,
clonedOperands);
+ }
+ } else {
+ return call;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java
b/core/src/main/java/org/apache/calcite/util/Bug.java
index 7c4f1c7524..77aa35185f 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -69,12 +69,6 @@ public abstract class Bug {
*/
public static final boolean DT1684_FIXED = false;
- /**
- * Whether <a href="http://issues.eigenbase.org/browse/FNL-25">issue
- * FNL-25</a> is fixed. (also filed as dtbug 153)
- */
- public static final boolean FNL25_FIXED = false;
-
/**
* Whether <a href="http://issues.eigenbase.org/browse/FRG-73">issue FRG-73:
* miscellaneous bugs with nested comments</a> is fixed.
diff --git
a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index ce44fac28d..44c0cee15a 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -733,7 +733,8 @@ class CalciteRemoteDriverTest {
(double) Integer.MIN_VALUE, (double) Integer.MAX_VALUE,
// BigDecimal
BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.valueOf(2.5D),
- BigDecimal.valueOf(Double.MAX_VALUE),
+ // Next one causes problems for most types
+ // BigDecimal.valueOf(Double.MAX_VALUE),
BigDecimal.valueOf(Long.MIN_VALUE),
// datetime
new Timestamp(0),
diff --git a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java
b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java
index e7a3c636ab..c0b0993cc4 100644
--- a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java
+++ b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java
@@ -70,11 +70,13 @@ class CoreQuidemTest extends QuidemTest {
.with(CalciteConnectionProperty.FUN, SqlLibrary.CALCITE.fun)
.with(CalciteAssert.Config.SCOTT)
.connect();
- case "scott-rounding-half-up":
+ case "scott-checked-rounding-half-up":
discard(CustomTypeSystems.ROUNDING_MODE_HALF_UP);
return CalciteAssert.that()
.with(CalciteConnectionProperty.PARSER_FACTORY,
ExtensionDdlExecutor.class.getName() + "#PARSER_FACTORY")
+ // Use bigquery conformance, which forces checked arithmetic
+ .with(CalciteConnectionProperty.CONFORMANCE,
SqlConformanceEnum.BIG_QUERY)
.with(CalciteConnectionProperty.FUN, SqlLibrary.CALCITE.fun)
.with(CalciteConnectionProperty.TYPE_SYSTEM,
CustomTypeSystems.class.getName() + "#ROUNDING_MODE_HALF_UP")
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 6c7c75e79b..d96ba3fac3 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9305,6 +9305,7 @@ public class SqlValidatorTest extends
SqlValidatorTestCase {
+ "$LiteralChain -\n"
+ "+ pre\n"
+ "- pre\n"
+ + "- pre\n" // checked
+ "FINAL pre\n"
+ "RUNNING pre\n"
+ "\n"
@@ -9312,13 +9313,18 @@ public class SqlValidatorTest extends
SqlValidatorTestCase {
+ "\n"
+ "% left\n"
+ "* left\n"
+ + "* left\n" // checked
+ + "/ left\n" // checked
+ "/ left\n"
+ "/INT left\n"
+ + "/INT left\n" // checked
+ "|| left\n"
+ "\n"
+ "+ left\n"
+ + "+ left\n" // checked
+ "+ -\n"
+ "- left\n"
+ + "- left\n" // checked
+ "- -\n"
+ "EXISTS pre\n"
+ "UNIQUE pre\n"
diff --git a/core/src/test/resources/sql/cast.iq
b/core/src/test/resources/sql/cast.iq
index 950bd6c731..4ab5e9a5e3 100644
--- a/core/src/test/resources/sql/cast.iq
+++ b/core/src/test/resources/sql/cast.iq
@@ -15,9 +15,58 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-!use scott
+
+# This connection uses checked arithmetic
+!use scott-checked-rounding-half-up
!set outputformat mysql
+# Test cases for [CALCITE-6685] There is no support for checked arithmetic
+# https://issues.apache.org/jira/browse/CALCITE-6685
+
+select 2 * CAST(5e18 AS DECIMAL(19, 0));
+java.lang.ArithmeticException: Value 10000000000000000000 cannot be
represented as a DECIMAL(19, 0)
+!error
+
+select - CAST(-2147483648 AS INT);
+Caused by: java.lang.ArithmeticException
+!error
+
+select CAST(1 AS SMALLINT) + CAST(2 AS SMALLINT) AS C;
++---+
+| C |
++---+
+| 3 |
++---+
+(1 row)
+
+!ok
+
+select CAST(127 AS TINYINT) + CAST(2 AS TINYINT) AS C;
+Caused by: java.lang.ArithmeticException: integer overflow: Value 129 does not
fit in a TINYINT
+!error
+
+select -2147483648 - 1;
+Caused by: java.lang.ArithmeticException
+!error
+
+select -2147483648 / -1;
+Caused by: java.lang.ArithmeticException
+!error
+
+select -CAST(-32768 AS SMALLINT);
+Caused by: java.lang.ArithmeticException: integer overflow: Value 32768 does
not fit in a SMALLINT
+!error
+
+select CAST(32767 AS SMALLINT) + CAST(2 AS SMALLINT);
+Caused by: java.lang.ArithmeticException: integer overflow: Value 32769 does
not fit in a SMALLINT
+!error
+
+select 2147483647 * 2147483647;
+Caused by: java.lang.ArithmeticException
+!error
+
+!use scott
+
# Cast a character literal to a timestamp; note: the plan does not contain CAST
values cast('1969-07-21 12:34:56' as timestamp);
+---------------------+
@@ -284,9 +333,9 @@ values cast(' -5 ' as double);
!ok
-# [CALCITE-4838] Add RoundingMode in RelDataTypeSystem to specify the rounding
behavior
-!use scott-rounding-half-up
+!use scott-checked-rounding-half-up
+# [CALCITE-4838] Add RoundingMode in RelDataTypeSystem to specify the rounding
behavior
# Test cast approximate numeric to int when rounding mode is HALF-UP
select cast(5.5 as int),
cast(3.5 as int),
@@ -712,7 +761,7 @@ from (values 0) as t(x);
# [CALCITE-4806] Lossy CAST is incorrectly simplified
-!use scott-rounding-half-up
+!use scott-checked-rounding-half-up
# rounding mode is HALF-UP
select cast(5.5 as int) = 5 as value1,
diff --git a/core/src/test/resources/sql/measure-paper.iq
b/core/src/test/resources/sql/measure-paper.iq
index b7a0565b3b..190713d811 100644
--- a/core/src/test/resources/sql/measure-paper.iq
+++ b/core/src/test/resources/sql/measure-paper.iq
@@ -224,13 +224,13 @@ SELECT "prodName",
COUNT(*) AS "count"
FROM "Orders" AS o
GROUP BY "prodName";
-+----------+--------------------+-------+
-| prodName | profitMargin | count |
-+----------+--------------------+-------+
-| Acme | 0.60 | 1 |
-| Happy | 0.4705882352941176 | 3 |
-| Whizz | 0.6666666666666667 | 1 |
-+----------+--------------------+-------+
++----------+--------------+-------+
+| prodName | profitMargin | count |
++----------+--------------+-------+
+| Acme | 0.600000 | 1 |
+| Happy | 0.470588 | 3 |
+| Whizz | 0.666666 | 1 |
++----------+--------------+-------+
(3 rows)
!ok
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
index c158e39a2b..55cf8c4f69 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
@@ -60,7 +60,7 @@ public class Nullness {
* has unknown nullability, so the following needs to be used:
*
* <pre><code>
- * T get() { return sneakyNull(value); }
+ * T get() { return castNonNull(value); }
* </code></pre>
*
* @param <T> the type of the reference
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/ExpressionType.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/ExpressionType.java
index 7b0dfe65d1..bcc87c480a 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/ExpressionType.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/ExpressionType.java
@@ -140,6 +140,12 @@ public enum ExpressionType {
*/
Divide(" / ", false, 3, false),
+ /**
+ * A checked division operation, such as (a / b), for numeric
+ * operands.
+ */
+ DivideChecked(" / ", false, 3, false),
+
/**
* A percent remainder operation, such as (a % b), for numeric
* operands.
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnaryExpression.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnaryExpression.java
index fa1634001d..6c04bf73c0 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnaryExpression.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnaryExpression.java
@@ -77,8 +77,10 @@ public class UnaryExpression extends Expression {
expression.accept(writer, lprec, nodeType.rprec);
writer.append(nodeType.op);
} else {
+ writer.append("(");
writer.append(nodeType.op);
expression.accept(writer, nodeType.lprec, rprec);
+ writer.append(")");
}
}
diff --git
a/linq4j/src/test/java/org/apache/calcite/linq4j/test/ExpressionTest.java
b/linq4j/src/test/java/org/apache/calcite/linq4j/test/ExpressionTest.java
index 1799ac8b57..d962204531 100644
--- a/linq4j/src/test/java/org/apache/calcite/linq4j/test/ExpressionTest.java
+++ b/linq4j/src/test/java/org/apache/calcite/linq4j/test/ExpressionTest.java
@@ -615,7 +615,7 @@ public class ExpressionTest {
Expressions.negate(
Expressions.negate(
Expressions.constant(5)))),
- is("- - 5"));
+ is("(- (- 5))"));
assertThat(
Expressions.toString(
@@ -739,7 +739,7 @@ public class ExpressionTest {
Expressions.typeIs(
Expressions.parameter(Object.class, "o"),
String.class))),
- is("!(o instanceof String)"));
+ is("(!(o instanceof String))"));
// not not
assertThat(
@@ -749,7 +749,7 @@ public class ExpressionTest {
Expressions.typeIs(
Expressions.parameter(Object.class, "o"),
String.class)))),
- is("!!(o instanceof String)"));
+ is("(!(!(o instanceof String)))"));
}
@Test void testWriteConstant() {
@@ -1025,7 +1025,7 @@ public class ExpressionTest {
+ " int x = 10;\n"
+ " int y = 0;\n"
+ " while (x < 5) {\n"
- + " ++y;\n"
+ + " (++y);\n"
+ " }\n"
+ "}\n"));
}
@@ -1547,7 +1547,7 @@ public class ExpressionTest {
assertThat(Expressions.toString(builder.toBlock()),
is("{\n"
+ " for (int i = 0, j = 10; ; ) {\n"
- + " if (++i < --j) {\n"
+ + " if ((++i) < (--j)) {\n"
+ " break;\n"
+ " }\n"
+ " }\n"
diff --git
a/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
b/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
index 728746d4a9..579b73f5f5 100644
--- a/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
+++ b/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
@@ -145,7 +145,7 @@ class OptimizerTest {
Expressions.condition(
Expressions.parameter(boolean.class, "a"),
Expressions.parameter(boolean.class, "b"), TRUE)),
- is("{\n return !a || b;\n}\n"));
+ is("{\n return (!a) || b;\n}\n"));
}
@Test void testOptimizeTernaryAfalseB() {
@@ -155,7 +155,7 @@ class OptimizerTest {
Expressions.condition(
Expressions.parameter(boolean.class, "a"),
FALSE, Expressions.parameter(boolean.class, "b"))),
- is("{\n return !a && b;\n}\n"));
+ is("{\n return (!a) && b;\n}\n"));
}
@Test void testOptimizeTernaryABfalse() {
@@ -288,7 +288,7 @@ class OptimizerTest {
Expressions.parameter(Integer.class, "inp0_"),
NULL_INTEGER),
NULL)),
- is("{\n return !v || inp0_ == null;\n}\n"));
+ is("{\n return (!v) || inp0_ == null;\n}\n"));
}
@Test void testOptimizeTernaryAeqBBA() {
@@ -316,7 +316,7 @@ class OptimizerTest {
NULL_INTEGER,
Expressions.parameter(Integer.class, "inp0_")),
NULL)),
- is("{\n return !(v || inp0_ == null);\n}\n"));
+ is("{\n return (!(v || inp0_ == null));\n}\n"));
}
@Test void testOptimizeTernaryInEqualABCneqC() {
@@ -328,7 +328,7 @@ class OptimizerTest {
Expressions.parameter(Integer.class, "inp0_"),
NULL_INTEGER),
NULL)),
- is("{\n return !(!v || inp0_ == null);\n}\n"));
+ is("{\n return (!((!v) || inp0_ == null));\n}\n"));
}
@Test void testOptimizeTernaryAneqBBA() {
@@ -516,7 +516,7 @@ class OptimizerTest {
// x == false
ParameterExpression x = Expressions.parameter(boolean.class, "x");
assertThat(optimize(Expressions.equal(x, FALSE)),
- is("{\n return !x;\n}\n"));
+ is("{\n return (!x);\n}\n"));
}
@Test void testNotEqualSameConst() {
@@ -582,7 +582,7 @@ class OptimizerTest {
// x != true
ParameterExpression x = Expressions.parameter(boolean.class, "x");
assertThat(optimize(Expressions.notEqual(x, TRUE)),
- is("{\n return !x;\n}\n"));
+ is("{\n return (!x);\n}\n"));
}
@Test void testNotEqualBoolFalse() {
diff --git a/site/_docs/history.md b/site/_docs/history.md
index c6125069aa..67a9d44adc 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -49,7 +49,13 @@ other software versions as specified in gradle.properties.
#### Breaking Changes
{: #breaking-1-39-0}
-None.
+*Checked arithmetic*. [<a
+href="https://issues.apache.org/jira/browse/CALCITE-6685">CALCITE-6685</a>]
+introduces support for checked arithmetic on short integer types. A
+new `SqlConformance.checkedArithmetic()` attribute is added to control
+this behavior. The BIG_QUERY and SQL_SERVER_2008 conformance have
+been changed to use checked arithmetic, matching the specification of
+these dialects.
#### New features
{: #new-features-1-39-0}
diff --git
a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
index a10be95988..94436699b2 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
@@ -75,6 +75,12 @@ public interface SqlOperatorFixture extends AutoCloseable {
String OUT_OF_RANGE_MESSAGE = ".* out of range.*";
+ String INTEGER_OVERFLOW = "integer overflow.*";
+
+ String LONG_OVERFLOW = "long overflow.*";
+
+ String DECIMAL_OVERFLOW = ".*cannot be represented as a DECIMAL.*";
+
String WRONG_FORMAT_MESSAGE = "Number has wrong format.*";
// TODO: Change message
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 5a4c7ecc3b..676b5b4550 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -38,6 +38,7 @@ import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlJdbcFunctionCall;
+import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
@@ -129,11 +130,14 @@ import static
org.apache.calcite.sql.test.ResultCheckers.isSet;
import static org.apache.calcite.sql.test.ResultCheckers.isSingle;
import static org.apache.calcite.sql.test.ResultCheckers.isWithin;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.BAD_DATETIME_MESSAGE;
+import static org.apache.calcite.sql.test.SqlOperatorFixture.DECIMAL_OVERFLOW;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.DIVISION_BY_ZERO_MESSAGE;
+import static org.apache.calcite.sql.test.SqlOperatorFixture.INTEGER_OVERFLOW;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_ARGUMENTS_NUMBER;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_ARGUMENTS_TYPE_VALIDATION_ERROR;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_CHAR_MESSAGE;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.LITERAL_OUT_OF_RANGE_MESSAGE;
+import static org.apache.calcite.sql.test.SqlOperatorFixture.LONG_OVERFLOW;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.OUT_OF_RANGE_MESSAGE;
import static
org.apache.calcite.sql.test.SqlOperatorFixture.WRONG_FORMAT_MESSAGE;
import static org.apache.calcite.util.DateTimeStringUtils.getDateFormatter;
@@ -142,6 +146,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -369,12 +374,6 @@ public class SqlOperatorTest {
//--- Tests -----------------------------------------------------------
- /**
- * For development. Put any old code in here.
- */
- @Test void testDummy() {
- }
-
@Test void testSqlOperatorOverloading() {
final SqlStdOperatorTable operatorTable = SqlStdOperatorTable.instance();
for (SqlOperator sqlOperator : operatorTable.getOperatorList()) {
@@ -387,8 +386,15 @@ public class SqlOperatorTest {
routines.removeIf(operator ->
!sqlOperator.getClass().isInstance(operator));
- assertThat(routines, hasSize(1));
- assertThat(sqlOperator, equalTo(routines.get(0)));
+ if (routines.size() == 2) {
+ // Some arithmetic operators looks like they are overloaded,
+ // e.g. PLUS and CHECKED_PLUS
+ assertTrue(SqlKind.CHECKED_ARITHMETIC.contains(routines.get(0).kind)
+ || SqlKind.CHECKED_ARITHMETIC.contains(routines.get(1).kind));
+ } else {
+ assertThat(routines, hasSize(1));
+ assertThat(sqlOperator, equalTo(routines.get(0)));
+ }
}
}
@@ -3258,8 +3264,10 @@ public class SqlOperatorTest {
}
@Test void testMinusOperator() {
- final SqlOperatorFixture f = fixture();
- f.setFor(SqlStdOperatorTable.MINUS, VmName.EXPAND);
+ SqlOperatorFixture f = fixture()
+ .setFor(SqlStdOperatorTable.MINUS, VmName.EXPAND)
+ // BigQuery requires arithmetic overflows
+ .withConformance(SqlConformanceEnum.BIG_QUERY);
f.checkScalarExact("-2-1", -3);
f.checkScalarExact("-2-1-5", -8);
f.checkScalarExact("2-1", 1);
@@ -3275,22 +3283,18 @@ public class SqlOperatorTest {
f.checkNull("1e1-cast(null as double)");
f.checkNull("cast(null as tinyint) - cast(null as smallint)");
- // TODO: Fix bug
- if (Bug.FNL25_FIXED) {
- // Should throw out of range error
- f.checkFails("cast(100 as tinyint) - cast(-100 as tinyint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(-20000 as smallint) - cast(20000 as smallint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(1.5e9 as integer) - cast(-1.5e9 as integer)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(-5e18 as bigint) - cast(5e18 as bigint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(5e18 as decimal(19,0)) - cast(-5e18 as
decimal(19,0))",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(-5e8 as decimal(19,10)) - cast(5e8 as
decimal(19,10))",
- OUT_OF_RANGE_MESSAGE, true);
- }
+ f.checkFails("cast(100 as tinyint) - cast(-100 as tinyint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(-20000 as smallint) - cast(20000 as smallint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(1.5e9 as integer) - cast(-1.5e9 as integer)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(-5e18 as bigint) - cast(5e18 as bigint)",
+ LONG_OVERFLOW, true);
+ f.checkFails("cast(5e18 as decimal(19,0)) - cast(-5e18 as decimal(19,0))",
+ DECIMAL_OVERFLOW, true);
+ f.checkFails("cast(-6e8 as decimal(19,10)) - cast(6e8 as decimal(19,10))",
+ DECIMAL_OVERFLOW, true);
}
@Test void testMinusIntervalOperator() {
@@ -3419,8 +3423,10 @@ public class SqlOperatorTest {
}
@Test void testMultiplyOperator() {
- final SqlOperatorFixture f = fixture();
- f.setFor(SqlStdOperatorTable.MULTIPLY, VmName.EXPAND);
+ final SqlOperatorFixture f = fixture()
+ .setFor(SqlStdOperatorTable.MULTIPLY, VmName.EXPAND)
+ // BigQuery requires arithmetic overflows
+ .withConformance(SqlConformanceEnum.BIG_QUERY);
f.checkScalarExact("2*3", 6);
f.checkScalarExact("2*-3", -6);
f.checkScalarExact("+2*3", 6);
@@ -3439,21 +3445,19 @@ public class SqlOperatorTest {
f.checkNull("2e-3*cast(null as integer)");
f.checkNull("cast(null as tinyint) * cast(4 as smallint)");
- if (Bug.FNL25_FIXED) {
- // Should throw out of range error
- f.checkFails("cast(100 as tinyint) * cast(-2 as tinyint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(200 as smallint) * cast(200 as smallint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(1.5e9 as integer) * cast(-2 as integer)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(5e9 as bigint) * cast(2e9 as bigint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(2e9 as decimal(19,0)) * cast(-5e9 as decimal(19,0))",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(5e4 as decimal(19,10)) * cast(2e4 as decimal(19,10))",
- OUT_OF_RANGE_MESSAGE, true);
- }
+ // Should throw out of range error
+ f.checkFails("cast(100 as tinyint) * cast(-2 as tinyint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(200 as smallint) * cast(200 as smallint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(1.5e9 as integer) * cast(-2 as integer)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(5e9 as bigint) * cast(2e9 as bigint)",
+ LONG_OVERFLOW, true);
+ f.checkFails("cast(2e9 as decimal(19,0)) * cast(-5e9 as decimal(19,0))",
+ DECIMAL_OVERFLOW, true);
+ f.checkFails("cast(5e4 as decimal(19,10)) * cast(2e4 as decimal(19,10))",
+ DECIMAL_OVERFLOW, true);
}
@Test void testMultiplyIntervals() {
@@ -3584,8 +3588,10 @@ public class SqlOperatorTest {
}
@Test void testPlusOperator() {
- final SqlOperatorFixture f = fixture();
- f.setFor(SqlStdOperatorTable.PLUS, VmName.EXPAND);
+ final SqlOperatorFixture f = fixture()
+ .setFor(SqlStdOperatorTable.PLUS, VmName.EXPAND)
+ // BigQuery requires arithmetic overflows
+ .withConformance(SqlConformanceEnum.BIG_QUERY);
f.checkScalarExact("1+2", 3);
f.checkScalarExact("-1+2", 1);
f.checkScalarExact("1+2+3", 6);
@@ -3601,22 +3607,20 @@ public class SqlOperatorTest {
f.checkNull("cast(null as tinyint)+1");
f.checkNull("1e-2+cast(null as double)");
- if (Bug.FNL25_FIXED) {
- // Should throw out of range error
- f.checkFails("cast(100 as tinyint) + cast(100 as tinyint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(-20000 as smallint) + cast(-20000 as smallint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(1.5e9 as integer) + cast(1.5e9 as integer)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(5e18 as bigint) + cast(5e18 as bigint)",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(-5e18 as decimal(19,0))"
- + " + cast(-5e18 as decimal(19,0))",
- OUT_OF_RANGE_MESSAGE, true);
- f.checkFails("cast(5e8 as decimal(19,10)) + cast(5e8 as decimal(19,10))",
- OUT_OF_RANGE_MESSAGE, true);
- }
+ // Should throw out of range error
+ f.checkFails("cast(100 as tinyint) + cast(100 as tinyint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(-20000 as smallint) + cast(-20000 as smallint)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(1.5e9 as integer) + cast(1.5e9 as integer)",
+ INTEGER_OVERFLOW, true);
+ f.checkFails("cast(5e18 as bigint) + cast(5e18 as bigint)",
+ LONG_OVERFLOW, true);
+ f.checkFails("cast(-5e18 as decimal(19,0))"
+ + " + cast(-5e18 as decimal(19,0))",
+ DECIMAL_OVERFLOW, true);
+ f.checkFails("cast(5e8 as decimal(19,10)) + cast(5e8 as decimal(19,10))",
+ DECIMAL_OVERFLOW, true);
}
@Test void testPlusOperatorAny() {
@@ -13033,7 +13037,7 @@ public class SqlOperatorTest {
f.checkScalarExact("ceil(cast(3 as bigint))", "DOUBLE NOT NULL", "3.0");
f.checkScalarExact("ceil(cast(3.5 as double))", "DOUBLE NOT NULL", "4.0");
f.checkScalarExact("ceil(cast(3.45 as decimal(19, 1)))",
- "DECIMAL(19, 1) NOT NULL", "4");
+ "DECIMAL(19, 1) NOT NULL", "4.0");
f.checkScalarExact("ceil(cast(3.45 as float))", "FLOAT NOT NULL", "4.0");
f.checkNull("ceil(cast(null as tinyint))");
}