This is an automated email from the ASF dual-hosted git repository.
rubenada 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 5e3326a0a8 [CALCITE-7731] Bound plain-notation expansion of DECIMAL
literals to prevent parse-time OutOfMemoryError (follow-up, added more tests)
5e3326a0a8 is described below
commit 5e3326a0a8e7cec4c804f5e00c80b5510b61945d
Author: Suraj Rajan <[email protected]>
AuthorDate: Mon Aug 24 15:21:39 2026 +0530
[CALCITE-7731] Bound plain-notation expansion of DECIMAL literals to
prevent parse-time OutOfMemoryError (follow-up, added more tests)
---
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 24 ++++++++++++++++++++++
.../org/apache/calcite/rex/RexBuilderTest.java | 20 ++++++++++++++++++
.../java/org/apache/calcite/sql/SqlNodeTest.java | 22 ++++++++++++++++++++
3 files changed, 66 insertions(+)
diff --git
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 8e2b6efb53..eb30ef3089 100644
---
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -117,6 +117,7 @@
import org.opentest4j.TestAbortedException;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -132,8 +133,10 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -428,6 +431,27 @@ private static RelNode projectOfLiterals() {
isLinux(expectedPreserved));
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7731">[CALCITE-7731]
+ * Bound plain-notation expansion of DECIMAL literals to prevent parse-time
+ * OutOfMemoryError</a>. A DECIMAL literal whose plain-notation expansion
would
+ * exceed the configured bound must not be converted to SQL text. */
+ @Test void testDecimalLiteralPlainNotationBoundInRelToSql() {
+ final RelBuilder b = relBuilder();
+ final RexBuilder rexBuilder = b.getRexBuilder();
+ // A literal with 20,000 digits comfortably exceeds the default 10,000
+ // digit bound, without requiring a multi-gigabyte allocation to build.
+ final BigDecimal outOfBound = new BigDecimal(BigInteger.TEN.pow(20_000));
+ final RelNode root = b
+ .scan("EMP")
+ .project(rexBuilder.makeExactLiteral(outOfBound))
+ .build();
+ final SqlDialect dialect = DatabaseProduct.CALCITE.getDialect();
+ final IllegalStateException e =
+ assertThrows(IllegalStateException.class, () -> toSql(root, dialect));
+ assertThat(e.getMessage(),
+ containsString("exceeds the configured plain-notation bound"));
+ }
+
/** Parses a SQL query and converts it to a relational expression. */
private static RelNode sqlToRel(String sql, SchemaPlus defaultSchema,
SqlParser.Config parserConfig, Set<SqlLibrary> librarySet,
diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
index e23679eaf5..6637a2c776 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
@@ -772,6 +772,26 @@ private void checkDate(RexLiteral literal) {
assertThat(rexLiteralHalfUp.getValue(), hasToString("12300"));
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7731">[CALCITE-7731]
+ * Bound plain-notation expansion of DECIMAL literals to prevent parse-time
+ * OutOfMemoryError</a>. Casting to a DECIMAL type whose (dialect-permitted)
+ * negative scale would make the plain-notation expansion exceed the
+ * configured bound must fail rather than attempt the expansion. */
+ @Test void testDecimalWithNegativeScaleExceedingPlainNotationBound() {
+ final RelDataTypeFactory typeFactory =
+ new SqlTypeFactoryImpl(
+ CustomTypeSystems.withMinScale(RelDataTypeSystem.DEFAULT,
+ typeName -> -20_000));
+ final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL, 3,
-20_000);
+ final RexBuilder builder = new RexBuilder(typeFactory);
+ final BigDecimal value = new BigDecimal("123");
+ final IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () -> {
+ builder.makeLiteral(value, type);
+ });
+ assertThat(e.getMessage(),
+ containsString("plain-notation expansion exceeds the configured
bound"));
+ }
+
/** Tests {@link DateString} year range. */
@Test void testDateStringYearError() {
try {
diff --git a/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java
b/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java
index 220c5cb15b..75311d2612 100644
--- a/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java
@@ -23,6 +23,8 @@
import org.apache.calcite.util.TestUtil;
import org.apache.calcite.util.Util;
+import com.google.common.base.Strings;
+
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
@@ -35,7 +37,9 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Test of {@link SqlNode} and other SQL AST classes.
@@ -74,6 +78,24 @@ class SqlNodeTest {
isEqualsDeep("CAST(a AS ROW(field INTEGER))"));
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7731">[CALCITE-7731]
+ * Bound plain-notation expansion of DECIMAL literals to prevent parse-time
+ * OutOfMemoryError</a>. A {@link SqlNumericLiteral} whose value's
plain-notation
+ * expansion would exceed the configured bound must fail when unparsed, even
if
+ * the literal was not created via the SQL parser. */
+ @Test void testNumericLiteralToValueExceedingPlainNotationBound() {
+ // A literal with a 20,001-digit fractional expansion comfortably exceeds
+ // the default 10,000 digit bound, without requiring a multi-gigabyte
+ // allocation to build.
+ final String digits = "0." + Strings.repeat("0", 20_000) + "1";
+ final SqlNumericLiteral literal =
+ SqlLiteral.createExactNumeric(digits, SqlParserPos.ZERO);
+ final IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, literal::toValue);
+ assertThat(e.getMessage(),
+ containsString("exceeds the configured plain-notation bound"));
+ }
+
private static Matcher<String> isEqualsDeep(String sqlExpected) {
return new CustomTypeSafeMatcher<String>("isDeepEqual") {
@Override protected boolean matchesSafely(String sqlActual) {