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"));
+  }
 }

Reply via email to