This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new f841f065aa1 branch-4.2: [fix](nereids) fix limit + offset overflow
when pushing down TopN/Limit #64633 (#68186)
f841f065aa1 is described below
commit f841f065aa1fbccc948c051033e83a612ac6e8e7
Author: 924060929 <[email protected]>
AuthorDate: Sun Sep 20 10:33:26 2026 +0800
branch-4.2: [fix](nereids) fix limit + offset overflow when pushing down
TopN/Limit #64633 (#68186)
Cherry-picked from #64633
---------
Signed-off-by: Aurélien Pupier <[email protected]>
Co-authored-by: Aurélien Pupier <[email protected]>
---
.../glue/translator/PhysicalPlanTranslator.java | 8 ++++++-
.../nereids/processor/post/PushTopnToAgg.java | 6 +++++
.../implementation/LogicalTopNToPhysicalTopN.java | 15 +++++++++---
.../rules/rewrite/CollectLimitAboveConsumer.java | 13 +++++++++--
.../nereids/rules/rewrite/LimitAggToTopNAgg.java | 5 ++++
.../doris/nereids/rules/rewrite/MergeLimits.java | 11 +++++++++
.../doris/nereids/rules/rewrite/MergeTopNs.java | 6 +++++
.../rewrite/PushDownLimitDistinctThroughJoin.java | 8 +++++++
.../rewrite/PushDownLimitDistinctThroughUnion.java | 7 ++++++
.../rewrite/PushDownScoreTopNIntoOlapScan.java | 8 +++----
.../rewrite/PushDownTopNDistinctThroughJoin.java | 18 +++++++++++----
.../rewrite/PushDownTopNDistinctThroughUnion.java | 11 ++++++++-
.../rules/rewrite/PushDownTopNThroughJoin.java | 10 ++++++++
.../rules/rewrite/PushDownTopNThroughUnion.java | 12 ++++++++--
.../rules/rewrite/PushDownTopNThroughWindow.java | 11 +++++++++
.../rewrite/PushDownVectorTopNIntoOlapScan.java | 7 ++++++
.../doris/nereids/rules/rewrite/SplitLimit.java | 9 +++++++-
.../nereids/trees/plans/logical/LogicalLimit.java | 6 +++++
.../nereids/trees/plans/logical/LogicalTopN.java | 5 ++++
.../trees/plans/physical/PhysicalLimit.java | 10 ++++++++
.../nereids/trees/plans/physical/PhysicalTopN.java | 5 ++++
.../java/org/apache/doris/nereids/util/Utils.java | 16 +++++++++++++
.../org/apache/doris/nereids/util/UtilsTest.java | 17 ++++++++++++++
.../push_down_top_n_through_union.groovy | 27 ++++++++++++++++++++++
24 files changed, 233 insertions(+), 18 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
index be66a89526c..614d49be774 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
@@ -2710,7 +2710,13 @@ public class PhysicalPlanTranslator extends
DefaultPlanVisitor<PlanFragment, Pla
// push sort to scan opt
if (sortNode.getChild(0) instanceof OlapScanNode) {
OlapScanNode scanNode = ((OlapScanNode) sortNode.getChild(0));
- if (checkPushSort(sortNode, scanNode.getOlapTable())) {
+ // When the offset is set, the pushed scan limit is limit +
offset. If that overflows the
+ // long range it would wrap to a negative limit, so skip the
whole push-sort-to-scan
+ // optimization (do not push the sort info either); the sort
node still applies the real
+ // limit/offset. Sort info and sort limit are always pushed
together.
+ boolean limitOverflows = sortNode.getOffset() > 0
+ && Utils.addOverflows(sortNode.getLimit(),
sortNode.getOffset());
+ if (checkPushSort(sortNode, scanNode.getOlapTable()) &&
!limitOverflows) {
SortInfo sortInfo = sortNode.getSortInfo();
scanNode.setSortInfo(sortInfo);
scanNode.getSortInfo().setSortTupleSlotExprs(sortNode.getResolvedTupleExprs());
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushTopnToAgg.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushTopnToAgg.java
index d3161a0dba3..c6824335d4a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushTopnToAgg.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushTopnToAgg.java
@@ -28,6 +28,7 @@ import
org.apache.doris.nereids.trees.plans.physical.PhysicalHashAggregate;
import
org.apache.doris.nereids.trees.plans.physical.PhysicalHashAggregate.TopnPushInfo;
import org.apache.doris.nereids.trees.plans.physical.PhysicalProject;
import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
/**
@@ -49,6 +50,11 @@ public class PushTopnToAgg extends PlanPostProcessor {
@Override
public Plan visitPhysicalTopN(PhysicalTopN<? extends Plan> topN,
CascadesContext ctx) {
topN.child().accept(this, ctx);
+ if (Utils.addOverflows(topN.getLimit(), topN.getOffset())) {
+ // limit + offset overflows the long range: no aggregate can hold
that many rows, so
+ // pushing a topn limit into the aggregate cannot reduce anything;
leave the topn as is.
+ return topN;
+ }
if (ConnectContext.get().getSessionVariable().topnOptLimitThreshold <=
topN.getLimit() + topN.getOffset()
&& !ConnectContext.get().getSessionVariable().pushTopnToAgg) {
return topN;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalTopNToPhysicalTopN.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalTopNToPhysicalTopN.java
index 2371407555b..359e4b0bf9c 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalTopNToPhysicalTopN.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalTopNToPhysicalTopN.java
@@ -17,12 +17,14 @@
package org.apache.doris.nereids.rules.implementation;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.SortPhase;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.collect.Lists;
@@ -46,13 +48,20 @@ public class LogicalTopNToPhysicalTopN extends
OneImplementationRuleFactory {
* mergeTopN(limit, off, require gather) -> localTopN(off+limit, 0,
require any)
*/
private List<PhysicalTopN<? extends Plan>> twoPhaseSort(LogicalTopN<?
extends Plan> logicalTopN) {
- PhysicalTopN<Plan> localSort = new
PhysicalTopN<>(logicalTopN.getOrderKeys(),
- logicalTopN.getLimit() + logicalTopN.getOffset(), 0,
SortPhase.LOCAL_SORT,
- logicalTopN.getLogicalProperties(), logicalTopN.child(0));
int sortPhaseNum = 0;
if (ConnectContext.get() != null) {
sortPhaseNum =
ConnectContext.get().getSessionVariable().sortPhaseNum;
}
+ // The local sort keeps limit + offset rows. When that overflows the
long range (e.g. LIMIT
+ // and OFFSET both BIGINT_MAX), we cannot produce a valid physical
TopN: the two-phase
+ // MERGE_SORT needs a local limit that overflows, and even a
single-phase GATHER_SORT would
+ // pass the overflowing limit/offset to BE where HeapSorter also
computes limit + offset.
+ if (Utils.addOverflows(logicalTopN.getLimit(),
logicalTopN.getOffset())) {
+ throw new AnalysisException("limit + offset overflows the long
range");
+ }
+ PhysicalTopN<Plan> localSort = new
PhysicalTopN<>(logicalTopN.getOrderKeys(),
+ logicalTopN.getLimit() + logicalTopN.getOffset(), 0,
SortPhase.LOCAL_SORT,
+ logicalTopN.getLogicalProperties(), logicalTopN.child(0));
if (sortPhaseNum == 1) {
PhysicalTopN<Plan> onePhaseSort = new
PhysicalTopN<>(logicalTopN.getOrderKeys(), logicalTopN.getLimit(),
logicalTopN.getOffset(), SortPhase.GATHER_SORT,
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CollectLimitAboveConsumer.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CollectLimitAboveConsumer.java
index bb573304e3a..0b0009fcdaf 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CollectLimitAboveConsumer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CollectLimitAboveConsumer.java
@@ -23,6 +23,7 @@ import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
@@ -52,7 +53,15 @@ public class CollectLimitAboveConsumer implements
RewriteRuleFactory {
private void collectLimitRows(CascadesContext cascadesContext,
LogicalLimit<?> limit,
LogicalCTEConsumer cteConsumer) {
- cascadesContext.putConsumerIdToLimitRows(
- cteConsumer.getRelationId(), limit.getLimit() +
limit.getOffset());
+ // The recorded value is the number of rows this consumer needs (limit
+ offset). When that
+ // overflows the long range the consumer effectively needs all rows,
so do not record anything:
+ // tryToConstructLimit treats a missing entry as "unbounded" and
leaves the producer unlimited
+ // (this consumer's own limit still applies above it). Recording a
wrapped-around negative count
+ // would corrupt the producer row bound.
+ if (Utils.addOverflows(limit.getLimit(), limit.getOffset())) {
+ return;
+ }
+ cascadesContext.putConsumerIdToLimitRows(cteConsumer.getRelationId(),
+ limit.getLimit() + limit.getOffset());
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LimitAggToTopNAgg.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LimitAggToTopNAgg.java
index f2ca5e68817..a209a1dd2da 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LimitAggToTopNAgg.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/LimitAggToTopNAgg.java
@@ -30,6 +30,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.collect.ImmutableList;
@@ -57,6 +58,7 @@ public class LimitAggToTopNAgg implements RewriteRuleFactory {
logicalLimit(logicalAggregate())
.when(limit -> ConnectContext.get() != null
&&
ConnectContext.get().getSessionVariable().pushTopnToAgg
+ && !Utils.addOverflows(limit.getLimit(),
limit.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= limit.getLimit() + limit.getOffset())
.when(limit -> {
@@ -73,6 +75,7 @@ public class LimitAggToTopNAgg implements RewriteRuleFactory {
logicalLimit(logicalProject(logicalAggregate()))
.when(limit -> ConnectContext.get() != null
&&
ConnectContext.get().getSessionVariable().pushTopnToAgg
+ && !Utils.addOverflows(limit.getLimit(),
limit.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= limit.getLimit() + limit.getOffset())
.when(limit -> {
@@ -93,6 +96,7 @@ public class LimitAggToTopNAgg implements RewriteRuleFactory {
logicalTopN(logicalAggregate())
.when(topn -> ConnectContext.get() != null
&&
ConnectContext.get().getSessionVariable().pushTopnToAgg
+ && !Utils.addOverflows(topn.getLimit(),
topn.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= topn.getLimit() + topn.getOffset())
.when(topn -> {
@@ -115,6 +119,7 @@ public class LimitAggToTopNAgg implements
RewriteRuleFactory {
logicalTopN(logicalProject(logicalAggregate()))
.when(topn -> ConnectContext.get() != null
&&
ConnectContext.get().getSessionVariable().pushTopnToAgg
+ && !Utils.addOverflows(topn.getLimit(),
topn.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= topn.getLimit() + topn.getOffset())
.when(topn -> {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeLimits.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeLimits.java
index e544eed07ce..e9e466123b0 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeLimits.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeLimits.java
@@ -17,10 +17,12 @@
package org.apache.doris.nereids.rules.rewrite;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
+import org.apache.doris.nereids.util.Utils;
/**
* This rule aims to merge consecutive limits.
@@ -54,7 +56,16 @@ public class MergeLimits extends OneRewriteRuleFactory {
}).toRule(RuleType.MERGE_LIMITS);
}
+ /**
+ * Merge two consecutive limits' offsets into a single offset. Consecutive
limits must be merged,
+ * and an overflowing combined offset cannot be represented as a single
offset, so fail fast
+ * instead of wrapping to a negative offset.
+ */
public static long mergeOffset(long upperOffset, long bottomOffset) {
+ if (Utils.addOverflows(upperOffset, bottomOffset)) {
+ throw new AnalysisException(
+ "offset overflows long range when merging limits: " +
upperOffset + " + " + bottomOffset);
+ }
return upperOffset + bottomOffset;
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeTopNs.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeTopNs.java
index 167115b9e6d..622f0d72f99 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeTopNs.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeTopNs.java
@@ -17,11 +17,13 @@
package org.apache.doris.nereids.rules.rewrite;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.properties.OrderKey;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
+import org.apache.doris.nereids.util.Utils;
import java.util.List;
@@ -58,6 +60,10 @@ public class MergeTopNs extends OneRewriteRuleFactory {
long limit = topN.getLimit();
long childOffset = childTopN.getOffset();
long childLimit = childTopN.getLimit();
+ if (Utils.addOverflows(offset, childOffset)) {
+ throw new AnalysisException(
+ "offset overflows long range when merging
TopNs: " + offset + " + " + childOffset);
+ }
long newOffset = offset + childOffset;
// The parent's offset is applied on top of the child's
output, so only
// (childLimit - offset) of the child's rows survive.
Clamp the merged limit
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughJoin.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughJoin.java
index 906007decf4..960e0e0b128 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughJoin.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughJoin.java
@@ -26,6 +26,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.collect.ImmutableList;
@@ -46,6 +47,7 @@ public class PushDownLimitDistinctThroughJoin implements
RewriteRuleFactory {
.when(LogicalAggregate::isDistinct))
.when(limit ->
ConnectContext.get() != null
+ &&
!Utils.addOverflows(limit.getLimit(), limit.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= limit.getLimit() +
limit.getOffset())
.then(limit -> {
@@ -64,6 +66,12 @@ public class PushDownLimitDistinctThroughJoin implements
RewriteRuleFactory {
logicalLimit(logicalAggregate(logicalProject(logicalJoin()).when(LogicalProject::isAllSlots))
.when(LogicalAggregate::isDistinct))
.then(limit -> {
+ // limit + offset overflowing means no child can
hold that many rows, so the
+ // push-down cannot reduce anything; skip it. (The
direct branch is gated the
+ // same way via topn_opt_limit_threshold.)
+ if (Utils.addOverflows(limit.getLimit(),
limit.getOffset())) {
+ return null;
+ }
LogicalAggregate<LogicalProject<LogicalJoin<Plan,
Plan>>> agg = limit.child();
LogicalProject<LogicalJoin<Plan, Plan>> project =
agg.child();
LogicalJoin<Plan, Plan> join = project.child();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
index 1a136673a1d..fd75ca129cc 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
@@ -27,6 +27,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
@@ -64,6 +65,12 @@ public class PushDownLimitDistinctThroughUnion implements
RewriteRuleFactory {
logicalLimit(logicalAggregate(logicalUnion().when(union ->
union.getQualifier() == Qualifier.ALL))
.when(agg -> agg.isDistinct()))
.then(limit -> {
+ // limit + offset overflowing the long range means
no child can hold that
+ // many rows, so pushing the limit below the union
cannot reduce anything;
+ // skip the rewrite. The parent limit still
applies the original limit/offset.
+ if (Utils.addOverflows(limit.getLimit(),
limit.getOffset())) {
+ return null;
+ }
LogicalAggregate<LogicalUnion> agg = limit.child();
LogicalUnion union = agg.child();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownScoreTopNIntoOlapScan.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownScoreTopNIntoOlapScan.java
index 824716f2a45..cd18e7acc18 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownScoreTopNIntoOlapScan.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownScoreTopNIntoOlapScan.java
@@ -44,6 +44,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.thrift.TExprOpcode;
import com.google.common.collect.ImmutableList;
@@ -189,10 +190,9 @@ public class PushDownScoreTopNIntoOlapScan implements
RewriteRuleFactory {
}
// When limit + offset overflows the long range, the pushed scan limit
would wrap to a
- // negative value. Fail with the same error as ordinary TopN instead
of leaving score()
- // unmaterialized and reporting an unrelated score() usage error.
- if (topN.getLimit() > Long.MAX_VALUE - topN.getOffset()) {
- throw new AnalysisException("limit + offset overflows the long
range");
+ // negative value; skip the push-down and let the TopN above the scan
apply limit/offset.
+ if (Utils.addOverflows(topN.getLimit(), topN.getOffset())) {
+ return null;
}
long scoreLimit = topN.getLimit() + topN.getOffset();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughJoin.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughJoin.java
index 4b0b8a89dc4..a67f06decd7 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughJoin.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughJoin.java
@@ -28,6 +28,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.util.PlanUtils;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.collect.ImmutableList;
@@ -49,6 +50,7 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
// TODO: complex order by
.when(topn ->
ConnectContext.get() != null
+ &&
!Utils.addOverflows(topn.getLimit(), topn.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= topn.getLimit() + topn.getOffset())
.when(topN ->
topN.getOrderKeys().stream().map(OrderKey::getExpr)
@@ -70,6 +72,13 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
.when(topN ->
topN.getOrderKeys().stream().map(OrderKey::getExpr)
.allMatch(Slot.class::isInstance))
.then(topN -> {
+ // limit + offset overflowing the long range means
no child can hold that
+ // many rows, so pushing the TopN below the join
cannot reduce anything;
+ // skip the rewrite. (The direct branch is gated
the same way via
+ // topn_opt_limit_threshold.)
+ if (Utils.addOverflows(topN.getLimit(),
topN.getOffset())) {
+ return null;
+ }
LogicalAggregate<LogicalProject<LogicalJoin<Plan,
Plan>>> distinct = topN.child();
LogicalProject<LogicalJoin<Plan, Plan>> project =
distinct.child();
LogicalJoin<Plan, Plan> join = project.child();
@@ -95,6 +104,7 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
}
private Plan pushTopNThroughJoin(LogicalTopN<? extends Plan> topN,
LogicalJoin<Plan, Plan> join) {
+ long childLimit = topN.getLimit() + topN.getOffset();
Set<Slot> groupBySlots = ((LogicalAggregate<?>)
topN.child()).getGroupByExpressions().stream()
.flatMap(e ->
e.getInputSlots().stream()).collect(Collectors.toSet());
switch (join.getJoinType()) {
@@ -107,7 +117,7 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
join.left().getOutputSet(), topN.getOrderKeys());
if (!pushedOrderKeys.isEmpty()) {
LogicalTopN<Plan> left = topN.withLimitOrderKeyAndChild(
- topN.getLimit() + topN.getOffset(), 0,
pushedOrderKeys,
+ childLimit, 0, pushedOrderKeys,
PlanUtils.distinct(join.left()));
return join.withChildren(left, join.right());
}
@@ -122,7 +132,7 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
join.right().getOutputSet(), topN.getOrderKeys());
if (!pushedOrderKeys.isEmpty()) {
LogicalTopN<Plan> right = topN.withLimitOrderKeyAndChild(
- topN.getLimit() + topN.getOffset(), 0,
pushedOrderKeys,
+ childLimit, 0, pushedOrderKeys,
PlanUtils.distinct(join.right()));
return join.withChildren(join.left(), right);
}
@@ -135,14 +145,14 @@ public class PushDownTopNDistinctThroughJoin implements
RewriteRuleFactory {
join.left().getOutputSet(), topN.getOrderKeys());
if (!(join.left() instanceof TopN) &&
!leftPushedOrderKeys.isEmpty()) {
leftChild = topN.withLimitOrderKeyAndChild(
- topN.getLimit() + topN.getOffset(), 0,
leftPushedOrderKeys,
+ childLimit, 0, leftPushedOrderKeys,
PlanUtils.distinct(join.left()));
}
List<OrderKey> rightPushedOrderKeys =
getPushedOrderKeys(groupBySlots,
join.right().getOutputSet(), topN.getOrderKeys());
if (!(join.right() instanceof TopN) &&
!rightPushedOrderKeys.isEmpty()) {
rightChild = topN.withLimitOrderKeyAndChild(
- topN.getLimit() + topN.getOffset(), 0,
rightPushedOrderKeys,
+ childLimit, 0, rightPushedOrderKeys,
PlanUtils.distinct(join.right()));
}
if (leftChild == join.left() && rightChild == join.right()) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
index cde42c7ad24..3e0dee97aca 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
@@ -29,6 +29,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.PlanUtils;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
@@ -65,6 +66,14 @@ public class PushDownTopNDistinctThroughUnion implements
RewriteRuleFactory {
logicalTopN(logicalAggregate(logicalUnion().when(union ->
union.getQualifier() == Qualifier.ALL))
.when(agg -> agg.isDistinct()))
.then(topN -> {
+ // The child TopN must keep limit + offset rows.
When that sum overflows the
+ // long range (e.g. LIMIT and OFFSET both
BIGINT_MAX) no child can hold that many
+ // rows, so pushing down cannot reduce anything
and would create an illegal
+ // negative limit. Skip the rewrite; the parent
TopN still applies limit/offset.
+ if (Utils.addOverflows(topN.getLimit(),
topN.getOffset())) {
+ return null;
+ }
+ long childLimit = topN.getLimit() +
topN.getOffset();
LogicalAggregate<LogicalUnion> agg = topN.child();
LogicalUnion union = agg.child();
List<Plan> newChildren = new ArrayList<>();
@@ -78,7 +87,7 @@ public class PushDownTopNDistinctThroughUnion implements
RewriteRuleFactory {
.map(orderKey ->
orderKey.withExpression(
ExpressionUtils.replace(orderKey.getExpr(), replaceMap)))
.collect(ImmutableList.toImmutableList());
- newChildren.add(new LogicalTopN<>(orderKeys,
topN.getLimit() + topN.getOffset(), 0,
+ newChildren.add(new LogicalTopN<>(orderKeys,
childLimit, 0,
PlanUtils.distinct(union.child(childIdx))));
}
if (union.children().equals(newChildren)) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughJoin.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughJoin.java
index 74b9cb1f8e6..904c038ac2e 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughJoin.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughJoin.java
@@ -26,6 +26,7 @@ import org.apache.doris.nereids.trees.plans.algebra.TopN;
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
+import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import com.google.common.collect.ImmutableList;
@@ -47,6 +48,7 @@ public class PushDownTopNThroughJoin implements
RewriteRuleFactory {
// TODO: complex orderby
.when(topn ->
ConnectContext.get() != null
+ &&
!Utils.addOverflows(topn.getLimit(), topn.getOffset())
&&
ConnectContext.get().getSessionVariable().topnOptLimitThreshold
>= topn.getLimit() + topn.getOffset())
.when(topN ->
topN.getOrderKeys().stream().map(OrderKey::getExpr)
@@ -66,6 +68,13 @@ public class PushDownTopNThroughJoin implements
RewriteRuleFactory {
.when(topN ->
topN.getOrderKeys().stream().map(OrderKey::getExpr)
.allMatch(Slot.class::isInstance))
.then(topN -> {
+ // limit + offset overflowing the long range means
no child can hold that
+ // many rows, so pushing the TopN below the join
cannot reduce anything;
+ // skip the rewrite. (The direct TopN -> Join
branch is gated the same way
+ // via topn_opt_limit_threshold.)
+ if (Utils.addOverflows(topN.getLimit(),
topN.getOffset())) {
+ return null;
+ }
LogicalProject<LogicalJoin<Plan, Plan>> project =
topN.child();
LogicalJoin<Plan, Plan> join = project.child();
@@ -138,4 +147,5 @@ public class PushDownTopNThroughJoin implements
RewriteRuleFactory {
return null;
}
}
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughUnion.java
index b02d7a1e45d..0416a4c5a79 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughUnion.java
@@ -28,6 +28,7 @@ import
org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
@@ -62,6 +63,14 @@ public class PushDownTopNThroughUnion implements
RewriteRuleFactory {
return ImmutableList.of(
logicalTopN(logicalUnion().when(union -> union.getQualifier()
== Qualifier.ALL))
.then(topN -> {
+ // The child TopN must keep limit + offset rows.
When that sum overflows the
+ // long range (e.g. LIMIT and OFFSET both
BIGINT_MAX) no child can hold that many
+ // rows, so pushing down cannot reduce anything
and would create an illegal
+ // negative limit. Skip the rewrite; the parent
TopN still applies limit/offset.
+ if (Utils.addOverflows(topN.getLimit(),
topN.getOffset())) {
+ return null;
+ }
+ long childLimit = topN.getLimit() +
topN.getOffset();
LogicalUnion union = topN.child();
List<Plan> newChildren = new ArrayList<>();
for (int j = 0; j < union.children().size(); j++) {
@@ -77,8 +86,7 @@ public class PushDownTopNThroughUnion implements
RewriteRuleFactory {
.map(orderKey ->
orderKey.withExpression(
ExpressionUtils.replace(orderKey.getExpr(), replaceMap)))
.collect(ImmutableList.toImmutableList());
- newChildren.add(
- new LogicalTopN<>(orderKeys,
topN.getLimit() + topN.getOffset(), 0, child));
+ newChildren.add(new LogicalTopN<>(orderKeys,
childLimit, 0, child));
}
if (union.children().equals(newChildren)) {
return null;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java
index 8dc4cd6f73a..6c5117448bf 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java
@@ -30,6 +30,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.trees.plans.logical.LogicalWindow;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
@@ -53,6 +54,11 @@ public class PushDownTopNThroughWindow implements
RewriteRuleFactory {
if (!checkTopNForPartitionLimitPushDown(topn, windowExprId)) {
return topn;
}
+ // When limit + offset overflows the long range (e.g. LIMIT
and OFFSET both
+ // BIGINT_MAX) the partition limit cannot reduce anything;
skip the push-down.
+ if (Utils.addOverflows(topn.getLimit(), topn.getOffset())) {
+ return topn;
+ }
long partitionLimit = topn.getLimit() + topn.getOffset();
Pair<WindowExpression, Long> windowFuncLongPair = window
.getPushDownWindowFuncAndLimit(null, partitionLimit);
@@ -76,6 +82,11 @@ public class PushDownTopNThroughWindow implements
RewriteRuleFactory {
if (!checkTopNForPartitionLimitPushDown(topn, windowExprId)) {
return topn;
}
+ // When limit + offset overflows the long range (e.g. LIMIT
and OFFSET both
+ // BIGINT_MAX) the partition limit cannot reduce anything;
skip the push-down.
+ if (Utils.addOverflows(topn.getLimit(), topn.getOffset())) {
+ return topn;
+ }
long partitionLimit = topn.getLimit() + topn.getOffset();
Pair<WindowExpression, Long> windowFuncLongPair = window
.getPushDownWindowFuncAndLimit(null, partitionLimit);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java
index 5abcf35a94f..7271e06b52d 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java
@@ -36,6 +36,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
@@ -146,6 +147,12 @@ public class PushDownVectorTopNIntoOlapScan implements
RewriteRuleFactory {
return null;
}
+ // When limit + offset overflows the long range, the pushed scan limit
would wrap to a
+ // negative value; skip the push-down and let the TopN above the scan
apply limit/offset.
+ if (Utils.addOverflows(topN.getLimit(), topN.getOffset())) {
+ return null;
+ }
+
Plan plan = scan.appendVirtualColumnsAndTopN(
ImmutableList.of(orderKeyAlias),
topN.getOrderKeys(), Optional.of(topN.getLimit() +
topN.getOffset()),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SplitLimit.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SplitLimit.java
index 06f7760c433..0db8a0380d0 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SplitLimit.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SplitLimit.java
@@ -17,10 +17,12 @@
package org.apache.doris.nereids.rules.rewrite;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.LimitPhase;
import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
+import org.apache.doris.nereids.util.Utils;
/**
* Split limit into two phase
@@ -38,8 +40,13 @@ public class SplitLimit extends OneRewriteRuleFactory {
.then(limit -> {
long l = limit.getLimit();
long o = limit.getOffset();
+ if (Utils.addOverflows(l, o)) {
+ throw new AnalysisException(
+ "limit + offset overflows the long range in
two-phase limit");
+ }
return new LogicalLimit<>(l, o,
- LimitPhase.GLOBAL, new LogicalLimit<>(l + o, 0,
LimitPhase.LOCAL, limit.child())
+ LimitPhase.GLOBAL,
+ new LogicalLimit<>(l + o, 0, LimitPhase.LOCAL,
limit.child())
);
}).toRule(RuleType.SPLIT_LIMIT);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
index e651fb75272..70f81a49e64 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLimit.java
@@ -54,9 +54,15 @@ public class LogicalLimit<CHILD_TYPE extends Plan> extends
LogicalUnary<CHILD_TY
this(limit, offset, phase, Optional.empty(), Optional.empty(), child);
}
+ /** Build a LogicalLimit with the given limit, offset and phase. */
public LogicalLimit(long limit, long offset, LimitPhase phase,
Optional<GroupExpression> groupExpression,
Optional<LogicalProperties> logicalProperties, CHILD_TYPE child) {
super(PlanType.LOGICAL_LIMIT, groupExpression, logicalProperties,
child);
+ // limit/offset are always non-negative ("no limit" is represented by
Long.MAX_VALUE). A
+ // negative value here means limit + offset overflowed somewhere
upstream and would produce an
+ // illegal plan that hangs in BE; fail fast instead.
+ Preconditions.checkArgument(limit >= 0 && offset >= 0,
+ "LogicalLimit limit and offset must be non-negative, but got
limit=%s, offset=%s", limit, offset);
this.limit = limit;
this.offset = offset;
this.phase = phase;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
index 6d6aa2b8131..552eee2f8a3 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalTopN.java
@@ -60,6 +60,11 @@ public class LogicalTopN<CHILD_TYPE extends Plan> extends
LogicalUnary<CHILD_TYP
Optional<LogicalProperties> logicalProperties, CHILD_TYPE child) {
super(PlanType.LOGICAL_TOP_N, groupExpression, logicalProperties,
child);
this.orderKeys =
ImmutableList.copyOf(Objects.requireNonNull(orderKeys, "orderKeys can not be
null"));
+ // limit/offset are always non-negative ("no limit" is represented by
Long.MAX_VALUE). A
+ // negative value here means limit + offset overflowed somewhere
upstream and would produce an
+ // illegal plan that hangs in BE; fail fast instead.
+ Preconditions.checkArgument(limit >= 0 && offset >= 0,
+ "LogicalTopN limit and offset must be non-negative, but got
limit=%s, offset=%s", limit, offset);
this.limit = limit;
this.offset = offset;
this.expressions = Suppliers.memoize(() -> {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalLimit.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalLimit.java
index 27155ae57f7..7661bae2887 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalLimit.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalLimit.java
@@ -63,6 +63,11 @@ public class PhysicalLimit<CHILD_TYPE extends Plan> extends
PhysicalUnary<CHILD_
LimitPhase phase, Optional<GroupExpression> groupExpression,
LogicalProperties logicalProperties,
CHILD_TYPE child) {
super(PlanType.PHYSICAL_LIMIT, groupExpression, logicalProperties,
child);
+ // limit/offset are always non-negative ("no limit" is represented by
Long.MAX_VALUE). A
+ // negative value here means limit + offset overflowed somewhere
upstream and would produce an
+ // illegal plan that hangs in BE; fail fast instead.
+ Preconditions.checkArgument(limit >= 0 && offset >= 0,
+ "PhysicalLimit limit and offset must be non-negative, but got
limit=%s, offset=%s", limit, offset);
this.limit = limit;
this.offset = offset;
this.phase = phase;
@@ -81,6 +86,11 @@ public class PhysicalLimit<CHILD_TYPE extends Plan> extends
PhysicalUnary<CHILD_
Statistics statistics, CHILD_TYPE child) {
super(PlanType.PHYSICAL_LIMIT, groupExpression, logicalProperties,
physicalProperties, statistics,
child);
+ // limit/offset are always non-negative ("no limit" is represented by
Long.MAX_VALUE). A
+ // negative value here means limit + offset overflowed somewhere
upstream and would produce an
+ // illegal plan that hangs in BE; fail fast instead.
+ Preconditions.checkArgument(limit >= 0 && offset >= 0,
+ "PhysicalLimit limit and offset must be non-negative, but got
limit=%s, offset=%s", limit, offset);
this.limit = limit;
this.offset = offset;
this.phase = phase;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalTopN.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalTopN.java
index eeafbebd458..cdcc909fb6e 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalTopN.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalTopN.java
@@ -70,6 +70,11 @@ public class PhysicalTopN<CHILD_TYPE extends Plan> extends
AbstractPhysicalSort<
super(PlanType.PHYSICAL_TOP_N, orderKeys, phase, groupExpression,
logicalProperties, physicalProperties,
statistics, child);
Objects.requireNonNull(orderKeys, "orderKeys should not be null in
PhysicalTopN.");
+ // limit/offset are always non-negative ("no limit" is represented by
Long.MAX_VALUE). A
+ // negative value here means limit + offset overflowed somewhere
upstream and would produce an
+ // illegal plan that hangs in BE; fail fast instead.
+ Preconditions.checkArgument(limit >= 0 && offset >= 0,
+ "PhysicalTopN limit and offset must be non-negative, but got
limit=%s, offset=%s", limit, offset);
this.limit = limit;
this.offset = offset;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/Utils.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/Utils.java
index bf0f4a1dd0f..d32c50d55ed 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/Utils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/Utils.java
@@ -138,6 +138,22 @@ public class Utils {
return false;
}
+ /**
+ * Whether {@code a + b} overflows the signed long range. {@code a} and
{@code b} are expected to
+ * be non-negative (e.g. a limit and an offset).
+ *
+ * <p>When combining a {@code limit} and an {@code offset} into the number
of rows a child must
+ * keep (e.g. pushing a TopN/Limit down, or building a two-phase sort),
the raw {@code limit +
+ * offset} can exceed the long range when both are close to {@code
BIGINT_MAX}; it then wraps to a
+ * negative number, which is an illegal limit and produces a broken plan.
Overflow here means the
+ * child would need more rows than any relation can hold (no relation
exceeds {@code
+ * Long.MAX_VALUE} rows), so the optimization cannot reduce anything:
callers should skip the
+ * rewrite and fall back to the correct unoptimized execution.
+ */
+ public static boolean addOverflows(long a, long b) {
+ return a > Long.MAX_VALUE - b;
+ }
+
/**
* Wrapper to a function without return value.
*/
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/UtilsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/UtilsTest.java
index 0c7d903311f..f1fcccab3e7 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/UtilsTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/UtilsTest.java
@@ -32,4 +32,21 @@ public class UtilsTest {
String en = "database123";
Assertions.assertFalse(Utils.containChinese(en));
}
+
+ @Test
+ public void testAddOverflows() {
+ // sums that fit in the long range do not overflow
+ Assertions.assertFalse(Utils.addOverflows(0, 0));
+ Assertions.assertFalse(Utils.addOverflows(1, 2));
+ Assertions.assertFalse(Utils.addOverflows(Long.MAX_VALUE, 0));
+ Assertions.assertFalse(Utils.addOverflows(0, Long.MAX_VALUE));
+ Assertions.assertFalse(Utils.addOverflows(Long.MAX_VALUE - 1, 1));
+
+ // limit + offset that exceeds the long range overflows. This is the
case (e.g. LIMIT and
+ // OFFSET both BIGINT_MAX) that previously produced a negative,
illegal child limit when
+ // pushing TopN/Limit down; callers now skip the rewrite instead.
+ Assertions.assertTrue(Utils.addOverflows(Long.MAX_VALUE, 1));
+ Assertions.assertTrue(Utils.addOverflows(Long.MAX_VALUE,
Long.MAX_VALUE));
+ Assertions.assertTrue(Utils.addOverflows(Long.MAX_VALUE - 1, 2));
+ }
}
diff --git
a/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_through_union.groovy
b/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_through_union.groovy
index 28ef3d1b61a..cb2c819cd1f 100644
---
a/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_through_union.groovy
+++
b/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_through_union.groovy
@@ -312,4 +312,31 @@ suite("push_down_top_n_through_union") {
ORDER BY logTimestamp desc
LIMIT 8;
"""
+
+ // When limit + offset overflows the long range (e.g. LIMIT and OFFSET
both at BIGINT_MAX),
+ // the planner reports an error instead of silently producing a broken
plan. Both the TopN path
+ // (ORDER BY + LIMIT/OFFSET) and the Limit path (LIMIT/OFFSET without
ORDER BY) are covered.
+ test {
+ sql """
+ select count(*) as c from (
+ select id from (
+ select 1 as id union all select 2 as id union all select 3
as id
+ ) t
+ order by id limit 9223372036854775807 offset
9223372036854775807
+ ) s;
+ """
+ exception "limit + offset overflows"
+ }
+
+ test {
+ sql """
+ select count(*) as c from (
+ select id from (
+ select 1 as id union all select 2 as id union all select 3
as id
+ ) t
+ limit 9223372036854775807 offset 9223372036854775807
+ ) s;
+ """
+ exception "limit + offset overflows"
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]