This is an automated email from the ASF dual-hosted git repository.

zhenchen 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 c274aacdfd [CALCITE-7346] Prevent overflow in metadata row-count when 
LIMIT/OFFSET literal exceeds Long range
c274aacdfd is described below

commit c274aacdfd2d0edc9a7184765c327fd8d35e666d
Author: Zhen Chen <[email protected]>
AuthorDate: Tue Dec 30 20:50:56 2025 +0800

    [CALCITE-7346] Prevent overflow in metadata row-count when LIMIT/OFFSET 
literal exceeds Long range
---
 .../calcite/rel/metadata/RelMdMaxRowCount.java     | 14 ++++++------
 .../calcite/rel/metadata/RelMdMinRowCount.java     | 14 ++++++------
 .../apache/calcite/rel/metadata/RelMdRowCount.java | 13 +++++------
 .../org/apache/calcite/rel/metadata/RelMdUtil.java | 26 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelMetadataTest.java   | 11 +++++++++
 5 files changed, 57 insertions(+), 21 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
index 8f666051ab..e728c22e1e 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
@@ -41,6 +41,8 @@
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import static 
org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
+
 /**
  * RelMdMaxRowCount supplies a default implementation of
  * {@link RelMetadataQuery#getMaxRowCount} for the standard logical algebra.
@@ -115,11 +117,10 @@ public Double getMaxRowCount(Sort rel, RelMetadataQuery 
mq) {
       rowCount = Double.POSITIVE_INFINITY;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
@@ -129,11 +130,10 @@ public Double getMaxRowCount(EnumerableLimit rel, 
RelMetadataQuery mq) {
       rowCount = Double.POSITIVE_INFINITY;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
@@ -214,7 +214,7 @@ public Double getMaxRowCount(RelSubset rel, 
RelMetadataQuery mq) {
       if (node instanceof Sort) {
         Sort sort = (Sort) node;
         if (sort.fetch instanceof RexLiteral) {
-          return (double) RexLiteral.longValue(sort.fetch);
+          return literalValueApproximatedByDouble(sort.fetch, 
Double.POSITIVE_INFINITY);
         }
       }
     }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
index f4280ee719..869d343335 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
@@ -39,6 +39,8 @@
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import static 
org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
+
 /**
  * RelMdMinRowCount supplies a default implementation of
  * {@link RelMetadataQuery#getMinRowCount} for the standard logical algebra.
@@ -114,11 +116,10 @@ public Double getMinRowCount(Sort rel, RelMetadataQuery 
mq) {
       rowCount = 0D;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
@@ -128,11 +129,10 @@ public Double getMinRowCount(EnumerableLimit rel, 
RelMetadataQuery mq) {
       rowCount = 0D;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
@@ -174,7 +174,7 @@ public Double getMinRowCount(RelSubset rel, 
RelMetadataQuery mq) {
       if (node instanceof Sort) {
         Sort sort = (Sort) node;
         if (sort.fetch instanceof RexLiteral) {
-          return (double) RexLiteral.longValue(sort.fetch);
+          return literalValueApproximatedByDouble(sort.fetch, 
Double.POSITIVE_INFINITY);
         }
       }
     }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
index 637e8777cc..f853fd647a 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
@@ -36,7 +36,6 @@
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
-import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.util.Bug;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.NumberUtil;
@@ -44,6 +43,8 @@
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import static 
org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
+
 /**
  * RelMdRowCount supplies a default implementation of
  * {@link RelMetadataQuery#getRowCount} for the standard logical algebra.
@@ -164,11 +165,10 @@ public Double getRowCount(Calc rel, RelMetadataQuery mq) {
       return null;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
@@ -178,11 +178,10 @@ public Double getRowCount(Calc rel, RelMetadataQuery mq) {
       return null;
     }
 
-    final long offset = rel.offset instanceof RexLiteral ? 
RexLiteral.longValue(rel.offset) : 0;
+    final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
     rowCount = Math.max(rowCount - offset, 0D);
 
-    final double limit =
-        rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : 
rowCount;
+    final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
     return limit < rowCount ? limit : rowCount;
   }
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
index 227e19a852..3412e70eee 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
@@ -464,6 +464,32 @@ public static double capInfinity(Double d) {
     return d.isInfinite() ? Double.MAX_VALUE : d;
   }
 
+  /**
+   * Returns the numeric value stored in a literal as a double.
+   *
+   * <p>Throws when the literal exceeds {@link Double#MAX_VALUE} instead of
+   * silently rounding to infinity. Doubles still approximate large integers 
(53
+   * bits of mantissa), so the returned value becomes only an approximation
+   * when numbers are very large.
+   */
+  public static double literalValueApproximatedByDouble(@Nullable RexNode node,
+      double defaultValue) {
+    if (!(node instanceof RexLiteral)) {
+      return defaultValue;
+    }
+    final Number number = RexLiteral.numberValue(node);
+    final BigDecimal decimal = NumberUtil.toBigDecimal(number);
+    if (decimal == null) {
+      throw new IllegalArgumentException(
+          "literal value " + number + " cannot be converted to BigDecimal");
+    }
+    if (decimal.abs().compareTo(BigDecimal.valueOf(Double.MAX_VALUE)) > 0) {
+      throw new IllegalArgumentException(
+          "literal value " + decimal + " exceeds double range");
+    }
+    return decimal.doubleValue();
+  }
+
   /**
    * Returns default estimates for selectivities, in the absence of stats.
    *
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java 
b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index e8cc0eb81a..acb04f1762 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -1324,6 +1324,17 @@ void testColumnOriginsUnion() {
     fixture.assertThatRowCount(is(EMP_SIZE), is(0D), is(123456D));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7346";>[CALCITE-7346]
+   * Prevent overflow in metadata row-count when LIMIT/OFFSET literal exceeds 
Long range</a>. */
+  @Test void testRowCountSortLimitBeyondLong() {
+    final BigDecimal fetch = 
BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE);
+    final double fetchDouble = fetch.doubleValue();
+    final String sql = "select * from emp order by ename limit " + fetchDouble;
+    final RelMetadataFixture fixture = sql(sql);
+    fixture.assertThatRowCount(is(EMP_SIZE), is(0D), is(fetchDouble));
+  }
+
   @Test void testRowCountSortHighOffset() {
     final String sql = "select * from emp order by ename offset 123456";
     final RelMetadataFixture fixture = sql(sql);

Reply via email to