Skip to content

Commit c274aac

Browse files
committed
[CALCITE-7346] Prevent overflow in metadata row-count when LIMIT/OFFSET literal exceeds Long range
1 parent b9e9e9d commit c274aac

File tree

5 files changed

+57
-21
lines changed

5 files changed

+57
-21
lines changed

core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141

4242
import org.checkerframework.checker.nullness.qual.Nullable;
4343

44+
import static org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
45+
4446
/**
4547
* RelMdMaxRowCount supplies a default implementation of
4648
* {@link RelMetadataQuery#getMaxRowCount} for the standard logical algebra.
@@ -115,11 +117,10 @@ public Double getMaxRowCount(Sort rel, RelMetadataQuery mq) {
115117
rowCount = Double.POSITIVE_INFINITY;
116118
}
117119

118-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
120+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
119121
rowCount = Math.max(rowCount - offset, 0D);
120122

121-
final double limit =
122-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
123+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
123124
return limit < rowCount ? limit : rowCount;
124125
}
125126

@@ -129,11 +130,10 @@ public Double getMaxRowCount(EnumerableLimit rel, RelMetadataQuery mq) {
129130
rowCount = Double.POSITIVE_INFINITY;
130131
}
131132

132-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
133+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
133134
rowCount = Math.max(rowCount - offset, 0D);
134135

135-
final double limit =
136-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
136+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
137137
return limit < rowCount ? limit : rowCount;
138138
}
139139

@@ -214,7 +214,7 @@ public Double getMaxRowCount(RelSubset rel, RelMetadataQuery mq) {
214214
if (node instanceof Sort) {
215215
Sort sort = (Sort) node;
216216
if (sort.fetch instanceof RexLiteral) {
217-
return (double) RexLiteral.longValue(sort.fetch);
217+
return literalValueApproximatedByDouble(sort.fetch, Double.POSITIVE_INFINITY);
218218
}
219219
}
220220
}

core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939

4040
import org.checkerframework.checker.nullness.qual.Nullable;
4141

42+
import static org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
43+
4244
/**
4345
* RelMdMinRowCount supplies a default implementation of
4446
* {@link RelMetadataQuery#getMinRowCount} for the standard logical algebra.
@@ -114,11 +116,10 @@ public Double getMinRowCount(Sort rel, RelMetadataQuery mq) {
114116
rowCount = 0D;
115117
}
116118

117-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
119+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
118120
rowCount = Math.max(rowCount - offset, 0D);
119121

120-
final double limit =
121-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
122+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
122123
return limit < rowCount ? limit : rowCount;
123124
}
124125

@@ -128,11 +129,10 @@ public Double getMinRowCount(EnumerableLimit rel, RelMetadataQuery mq) {
128129
rowCount = 0D;
129130
}
130131

131-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
132+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
132133
rowCount = Math.max(rowCount - offset, 0D);
133134

134-
final double limit =
135-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
135+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
136136
return limit < rowCount ? limit : rowCount;
137137
}
138138

@@ -174,7 +174,7 @@ public Double getMinRowCount(RelSubset rel, RelMetadataQuery mq) {
174174
if (node instanceof Sort) {
175175
Sort sort = (Sort) node;
176176
if (sort.fetch instanceof RexLiteral) {
177-
return (double) RexLiteral.longValue(sort.fetch);
177+
return literalValueApproximatedByDouble(sort.fetch, Double.POSITIVE_INFINITY);
178178
}
179179
}
180180
}

core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@
3636
import org.apache.calcite.rel.core.TableScan;
3737
import org.apache.calcite.rel.core.Union;
3838
import org.apache.calcite.rel.core.Values;
39-
import org.apache.calcite.rex.RexLiteral;
4039
import org.apache.calcite.util.Bug;
4140
import org.apache.calcite.util.ImmutableBitSet;
4241
import org.apache.calcite.util.NumberUtil;
4342
import org.apache.calcite.util.Util;
4443

4544
import org.checkerframework.checker.nullness.qual.Nullable;
4645

46+
import static org.apache.calcite.rel.metadata.RelMdUtil.literalValueApproximatedByDouble;
47+
4748
/**
4849
* RelMdRowCount supplies a default implementation of
4950
* {@link RelMetadataQuery#getRowCount} for the standard logical algebra.
@@ -164,11 +165,10 @@ public Double getRowCount(Calc rel, RelMetadataQuery mq) {
164165
return null;
165166
}
166167

167-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
168+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
168169
rowCount = Math.max(rowCount - offset, 0D);
169170

170-
final double limit =
171-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
171+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
172172
return limit < rowCount ? limit : rowCount;
173173
}
174174

@@ -178,11 +178,10 @@ public Double getRowCount(Calc rel, RelMetadataQuery mq) {
178178
return null;
179179
}
180180

181-
final long offset = rel.offset instanceof RexLiteral ? RexLiteral.longValue(rel.offset) : 0;
181+
final double offset = literalValueApproximatedByDouble(rel.offset, 0D);
182182
rowCount = Math.max(rowCount - offset, 0D);
183183

184-
final double limit =
185-
rel.fetch instanceof RexLiteral ? RexLiteral.longValue(rel.fetch) : rowCount;
184+
final double limit = literalValueApproximatedByDouble(rel.fetch, rowCount);
186185
return limit < rowCount ? limit : rowCount;
187186
}
188187

core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,32 @@ public static double capInfinity(Double d) {
464464
return d.isInfinite() ? Double.MAX_VALUE : d;
465465
}
466466

467+
/**
468+
* Returns the numeric value stored in a literal as a double.
469+
*
470+
* <p>Throws when the literal exceeds {@link Double#MAX_VALUE} instead of
471+
* silently rounding to infinity. Doubles still approximate large integers (53
472+
* bits of mantissa), so the returned value becomes only an approximation
473+
* when numbers are very large.
474+
*/
475+
public static double literalValueApproximatedByDouble(@Nullable RexNode node,
476+
double defaultValue) {
477+
if (!(node instanceof RexLiteral)) {
478+
return defaultValue;
479+
}
480+
final Number number = RexLiteral.numberValue(node);
481+
final BigDecimal decimal = NumberUtil.toBigDecimal(number);
482+
if (decimal == null) {
483+
throw new IllegalArgumentException(
484+
"literal value " + number + " cannot be converted to BigDecimal");
485+
}
486+
if (decimal.abs().compareTo(BigDecimal.valueOf(Double.MAX_VALUE)) > 0) {
487+
throw new IllegalArgumentException(
488+
"literal value " + decimal + " exceeds double range");
489+
}
490+
return decimal.doubleValue();
491+
}
492+
467493
/**
468494
* Returns default estimates for selectivities, in the absence of stats.
469495
*

core/src/test/java/org/apache/calcite/test/RelMetadataTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,17 @@ void testColumnOriginsUnion() {
13241324
fixture.assertThatRowCount(is(EMP_SIZE), is(0D), is(123456D));
13251325
}
13261326

1327+
/** Test case for
1328+
* <a href="https://issues.apache.org/jira/browse/CALCITE-7346">[CALCITE-7346]
1329+
* Prevent overflow in metadata row-count when LIMIT/OFFSET literal exceeds Long range</a>. */
1330+
@Test void testRowCountSortLimitBeyondLong() {
1331+
final BigDecimal fetch = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE);
1332+
final double fetchDouble = fetch.doubleValue();
1333+
final String sql = "select * from emp order by ename limit " + fetchDouble;
1334+
final RelMetadataFixture fixture = sql(sql);
1335+
fixture.assertThatRowCount(is(EMP_SIZE), is(0D), is(fetchDouble));
1336+
}
1337+
13271338
@Test void testRowCountSortHighOffset() {
13281339
final String sql = "select * from emp order by ename offset 123456";
13291340
final RelMetadataFixture fixture = sql(sql);

0 commit comments

Comments
 (0)