This is an automated email from the ASF dual-hosted git repository.
mbudiu 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 e664ae0702 [CALCITE-3522] SqlValidator.validateLiteral rejects
literals with a DECIMAL type that require more than 64 bits
e664ae0702 is described below
commit e664ae0702e86d276ada2174c3a19a60feeea75e
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Aug 1 10:50:29 2024 -0700
[CALCITE-3522] SqlValidator.validateLiteral rejects literals with a DECIMAL
type that require more than 64 bits
Signed-off-by: Mihai Budiu <[email protected]>
---
.../org/apache/calcite/sql/SqlNumericLiteral.java | 3 +-
.../calcite/sql/validate/SqlValidatorImpl.java | 30 +++---
.../src/main/java/org/apache/calcite/util/Bug.java | 5 -
.../org/apache/calcite/test/SqlOperatorTest.java | 106 +++++++++++++--------
4 files changed, 81 insertions(+), 63 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNumericLiteral.java
b/core/src/main/java/org/apache/calcite/sql/SqlNumericLiteral.java
index 7b9f6ac31a..6867d199f0 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlNumericLiteral.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlNumericLiteral.java
@@ -103,7 +103,7 @@ public class SqlNumericLiteral extends SqlLiteral {
BigDecimal bd = getValueNonNull();
SqlTypeName result;
// Will throw if the number cannot be represented as a long.
- long l = bd.longValue();
+ long l = bd.longValueExact();
if ((l >= Integer.MIN_VALUE) && (l <= Integer.MAX_VALUE)) {
result = SqlTypeName.INTEGER;
} else {
@@ -115,7 +115,6 @@ public class SqlNumericLiteral extends SqlLiteral {
// Fallback to DECIMAL.
}
}
-
// else we have a decimal
return typeFactory.createSqlType(
SqlTypeName.DECIMAL,
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 9daadadec0..5adfea1375 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -133,7 +133,6 @@ import org.checkerframework.dataflow.qual.Pure;
import org.slf4j.Logger;
import java.math.BigDecimal;
-import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -3417,20 +3416,21 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
@Override public void validateLiteral(SqlLiteral literal) {
switch (literal.getTypeName()) {
case DECIMAL:
- // Decimal and long have the same precision (as 64-bit integers), so
- // the unscaled value of a decimal must fit into a long.
-
- // REVIEW jvs 4-Aug-2004: This should probably be calling over to
- // the available calculator implementations to see what they
- // support. For now use ESP instead.
- //
- // jhyde 2006/12/21: I think the limits should be baked into the
- // type system, not dependent on the calculator implementation.
- BigDecimal bd = literal.getValueAs(BigDecimal.class);
- BigInteger unscaled = bd.unscaledValue();
- long longValue = unscaled.longValue();
- if (!BigInteger.valueOf(longValue).equals(unscaled)) {
- // overflow
+ // Accept any decimal value that does not exceed the max
+ // precision and scale of the type system.
+ final RelDataTypeSystem typeSystem = getTypeFactory().getTypeSystem();
+ final BigDecimal bd = literal.getValueAs(BigDecimal.class);
+ final BigDecimal noTrailingZeros = bd.stripTrailingZeros();
+ // If we don't strip trailing zeros we may reject values such as
1.000....0.
+
+ final int maxPrecision = typeSystem.getMaxNumericPrecision();
+ if (noTrailingZeros.precision() > maxPrecision) {
+ throw newValidationError(literal,
+ RESOURCE.numberLiteralOutOfRange(bd.toString()));
+ }
+
+ final int maxScale = typeSystem.getMaxNumericScale();
+ if (noTrailingZeros.scale() > maxScale) {
throw newValidationError(literal,
RESOURCE.numberLiteralOutOfRange(bd.toString()));
}
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 3c39697816..521da4d29d 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -152,11 +152,6 @@ public abstract class Bug {
* Improve RelMdPredicates performance</a> is fixed. */
public static final boolean CALCITE_2401_FIXED = false;
- /** Whether
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-2539">[CALCITE-2539]
- * Several test case not passed in CalciteSqlOperatorTest.java</a> is fixed.
*/
- public static final boolean CALCITE_2539_FIXED = false;
-
/** Whether
* <a
href="https://issues.apache.org/jira/browse/CALCITE-2869">[CALCITE-2869]
* JSON data type support</a> is fixed. */
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 7e880e94c6..72b3559336 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -430,6 +430,43 @@ public class SqlOperatorTest {
false);
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-3522">
+ * Sql validator limits decimal literals to 64 bits</a>. */
+ @Test void testLargeLiterals() {
+ // Some of these literals were too large to be accepted previously, but
+ // now are legal as decimal literals.
+ SqlOperatorFixture f = fixture();
+ f.checkCastFails("9223372036854775808", "INTEGER",
+ OUT_OF_RANGE_MESSAGE, true, SqlOperatorFixture.CastType.CAST);
+ f.checkCastFails("9223372036854775808.1", "INTEGER",
+ "Numeric literal.*out of range", false,
SqlOperatorFixture.CastType.CAST);
+ f.checkCastFails("223372036854775808", "INTEGER",
+ OUT_OF_RANGE_MESSAGE, true, SqlOperatorFixture.CastType.CAST);
+ f.checkCastFails("9223372036854775808", "BIGINT",
+ "Overflow", true, SqlOperatorFixture.CastType.CAST);
+ f.checkCastFails("'" + Numeric.TINYINT.maxOverflowNumericString + "'",
+ "TINYINT", OUT_OF_RANGE_MESSAGE, true,
SqlOperatorFixture.CastType.CAST);
+ String largePrecision = "1234567891011.0";
+ String largeScale = "1.01234567891011";
+ f.checkScalarExact(largePrecision, "DECIMAL(14, 1) NOT NULL",
largePrecision);
+ f.checkScalarExact(largeScale, "DECIMAL(15, 14) NOT NULL", largeScale);
+
+ // Check that the type system can reject large decimal literals
+ SqlOperatorFixture f0 = f.withFactory(tf ->
+ tf.withTypeSystem(typeSystem ->
+ new DelegatingTypeSystem(typeSystem) {
+ @Override public int getMaxNumericPrecision() {
+ return 10;
+ }
+
+ @Override public int getMaxNumericScale() {
+ return 10;
+ }
+ }));
+ f0.checkFails("^" + largePrecision + "^", OUT_OF_RANGE_MESSAGE, false);
+ f0.checkFails("^" + largeScale + "^", OUT_OF_RANGE_MESSAGE, false);
+ }
+
@Test void testNotBetween() {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.NOT_BETWEEN, VM_EXPAND);
@@ -626,13 +663,11 @@ public class SqlOperatorTest {
// Overflow test
if (numeric == Numeric.BIGINT) {
- // Calcite cannot even represent a literal so large, so
- // for this query even the safe casts fail at compile-time
- // (runtime == false).
- f.checkFails("cast(^" + numeric.maxOverflowNumericString + "^ as
BIGINT)",
- LITERAL_OUT_OF_RANGE_MESSAGE, false);
- f.checkFails("cast(^" + numeric.minOverflowNumericString + "^ as
BIGINT)",
- LITERAL_OUT_OF_RANGE_MESSAGE, false);
+ // Overflow for casting decimals produces a different error
+ f.checkCastFails(numeric.maxOverflowNumericString,
+ type, "Overflow", true, castType);
+ f.checkCastFails(numeric.minOverflowNumericString,
+ type, "Overflow", true, castType);
} else {
if (numeric != Numeric.DECIMAL5_2) {
// This condition is for bug [CALCITE-6078], not yet fixed
@@ -1576,11 +1611,6 @@ public class SqlOperatorTest {
final SqlOperatorFixture f = fixture();
f.setFor(SqlLibraryOperators.MSSQL_CONVERT, VmName.EXPAND);
// ensure 'style' argument is ignored
- // 3rd argument 'style' is a literal. However,
- // AbstractSqlTester converts values to a single value in a column.
- // see AbstractSqlTester.buildQuery2
- // But CONVERT 'style' is supposed to be a literal.
- // So for now, they are put in a @Disabled test
f.checkScalar("convert(INTEGER, 45.4, 999)", "45", "INTEGER NOT NULL");
f.checkScalar("convert(DATE, '2000-01-01', 999)", "2000-01-01", "DATE NOT
NULL");
// including 'NULL' style argument
@@ -4265,15 +4295,12 @@ public class SqlOperatorTest {
+ " \\^";
f.checkFails("'yd3223' similar to '[:LOWER:]{2}[:DIGIT:]{,5}'",
expectedError, true);
- if (Bug.CALCITE_2539_FIXED) {
- f.checkFails("'cd' similar to '[(a-e)]d' ",
- "Invalid regular expression: \\[\\(a-e\\)\\]d at 1",
- true);
-
- f.checkFails("'yd' similar to '[(a-e)]d' ",
- "Invalid regular expression: \\[\\(a-e\\)\\]d at 1",
- true);
- }
+ f.checkFails("'cd' similar to '[(a-e)]d' ",
+ ".*Invalid regular expression '\\[\\(a-e\\)\\]d', index 1",
+ true);
+ f.checkFails("'yd' similar to '[(a-e)]d' ",
+ ".*Invalid regular expression '\\[\\(a-e\\)\\]d', index 1",
+ true);
// all the following tests wrong results due to missing functionality
// or defect (FRG-375, 377).
@@ -15547,36 +15574,33 @@ public class SqlOperatorTest {
final List<RelDataType> types =
SqlTests.getTypes(f.getFactory().getTypeFactory());
for (RelDataType type : types) {
+ SqlTypeName sqlTypeName = type.getSqlTypeName();
for (Object o : getValues((BasicSqlType) type, false)) {
SqlLiteral literal =
- type.getSqlTypeName().createLiteral(o, SqlParserPos.ZERO);
+ sqlTypeName.createLiteral(o, SqlParserPos.ZERO);
SqlString literalString =
literal.toSqlString(AnsiSqlDialect.DEFAULT);
- if ((type.getSqlTypeName() == SqlTypeName.BIGINT)
- || ((type.getSqlTypeName() == SqlTypeName.DECIMAL)
- && (type.getPrecision() == 19))) {
- // Values which are too large to be literals fail at
- // validate time.
- f.checkFails("CAST(^" + literalString + "^ AS " + type + ")",
- "Numeric literal '.*' out of range", false);
- } else if ((type.getSqlTypeName() == SqlTypeName.CHAR)
- || (type.getSqlTypeName() == SqlTypeName.VARCHAR)
- || (type.getSqlTypeName() == SqlTypeName.BINARY)
- || (type.getSqlTypeName() == SqlTypeName.VARBINARY)) {
+ if (sqlTypeName == SqlTypeName.DECIMAL) {
+ // Casting to decimal does not fail
+ } else if (sqlTypeName == SqlTypeName.BIGINT) {
+ // This is in fact a cast of a decimal literal to BIGINT,
+ // so the error is different.
+ f.checkFails("CAST(" + literalString + " AS " + type + ")",
+ "Overflow",
+ true);
+ } else if ((sqlTypeName == SqlTypeName.CHAR)
+ || (sqlTypeName == SqlTypeName.VARCHAR)
+ || (sqlTypeName == SqlTypeName.BINARY)
+ || (sqlTypeName == SqlTypeName.VARBINARY)) {
// Casting overlarge string/binary values do not fail -
// they are truncated. See testCastTruncates().
} else {
// Value outside legal bound should fail at runtime (not
// validate time).
- //
- // NOTE: Because Java and Fennel calcs give
- // different errors, the pattern hedges its bets.
- if (Bug.CALCITE_2539_FIXED) {
- f.checkFails("CAST(" + literalString + " AS " + type + ")",
- "(?s).*(Overflow during calculation or cast\\.|Code=22003).*",
- true);
- }
+ f.checkFails("CAST(" + literalString + " AS " + type + ")",
+ OUT_OF_RANGE_MESSAGE,
+ true);
}
}
}