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);