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 ec34f39dad [CALCITE-7750] Bound plain-notation expansion of DECIMAL
literals in Primitive.checkOverflow
ec34f39dad is described below
commit ec34f39dad1533526fdb6cfb6edef6fd5a318fac
Author: Jeremy Schoemaker <[email protected]>
AuthorDate: Tue Aug 25 19:06:11 2026 -0500
[CALCITE-7750] Bound plain-notation expansion of DECIMAL literals in
Primitive.checkOverflow
Primitive.checkOverflow calls BigDecimal.toPlainString on a value with
scale < 0 without bounding the plain-notation expansion, the same
pattern bounded elsewhere in CALCITE-7731. A large negative scale can
force toPlainString to materialize gigabytes and OOM.
Gate the toPlainString call with isBoundedDecimal, mirroring
SqlUtil.isBoundedDecimal in core but inlined in linq4j to avoid a
circular linq4j -> core dependency. The bound reads
calcite.parser.maxDecimalLiteralPlainDigits (default 10000) and checks
precision + abs(scale) <= limit, throwing IllegalArgumentException
before allocation if exceeded.
Add PrimitiveTest coverage for the in-bounds paths and for a
pathological scale that must be rejected.
---
.../org/apache/calcite/linq4j/tree/Primitive.java | 12 +++++++++++
.../apache/calcite/linq4j/test/PrimitiveTest.java | 25 ++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
index a35fba35a0..371685cd40 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
@@ -417,11 +417,23 @@ static BigDecimal checkOverflow(BigDecimal value, int
precision, int scale,
if (scale < 0) {
// The result maybe scientific notation string,e.g. 1.234E+6,
// we need to convert it to 1234000
+ if (!isBoundedDecimal(result)) {
+ throw new IllegalArgumentException(
+ "DECIMAL literal exceeds the configured plain-notation bound: " +
result);
+ }
return new BigDecimal(result.toPlainString());
}
return result;
}
+ private static boolean isBoundedDecimal(BigDecimal value) {
+ // Mirrors org.apache.calcite.sql.SqlUtil.isBoundedDecimal without
+ // introducing a core -> linq4j dependency (linq4j cannot depend on core).
+ // Bound is calcite.parser.maxDecimalLiteralPlainDigits (default 10000).
+ int limit =
Integer.getInteger("calcite.parser.maxDecimalLiteralPlainDigits", 10_000);
+ return (long) value.precision() + Math.abs((long) value.scale()) <= limit;
+ }
+
/** Called from BuiltInMethod.CHAR_DECIMAL_CAST */
public static @Nullable Object charToDecimalCast(
@Nullable String value, int precision, int scale) {
diff --git
a/linq4j/src/test/java/org/apache/calcite/linq4j/test/PrimitiveTest.java
b/linq4j/src/test/java/org/apache/calcite/linq4j/test/PrimitiveTest.java
index eedb153e0e..1e4898f02d 100644
--- a/linq4j/src/test/java/org/apache/calcite/linq4j/test/PrimitiveTest.java
+++ b/linq4j/src/test/java/org/apache/calcite/linq4j/test/PrimitiveTest.java
@@ -20,15 +20,18 @@
import org.junit.jupiter.api.Test;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -305,4 +308,26 @@ public void set(Object v) {
assertThat(Primitive.BOOLEAN.arrayToString(booleans4),
is("[true, false, false, false, true, true, false]"));
}
+
+ @Test void testCharToDecimalCastWithinBounds() {
+ assertThat(Primitive.charToDecimalCast("1.5", 5, 2),
+ is(new BigDecimal("1.50")));
+ assertThat(Primitive.charToDecimalCast("0", 38, 0),
+ is(new BigDecimal("0")));
+ // scale < 0 branch, well below the bound.
+ assertThat(Primitive.charToDecimalCast("1000", 4, -3),
+ is(new BigDecimal("1000")));
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7750">[CALCITE-7750]
+ * Bound plain-notation expansion of DECIMAL literals in
+ * Primitive.checkOverflow</a>. */
+ @Test void testCharToDecimalCastRejectsPathologicalScale() {
+ IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () ->
+ Primitive.charToDecimalCast("1E10000", 1, -10_000));
+ assertThat(e.getMessage(),
+ containsString("plain-notation bound"));
+ }
}