From 301461a48dbc8b0d366261b40e8246b6168c464c Mon Sep 17 00:00:00 2001 From: yaoxiao Date: Sun, 24 May 2026 21:45:35 +0800 Subject: [PATCH] [fix](nereids) abort SimplifyArithmeticComparison rewrite when the new constant overflows (#61761) date_sub(i, interval 1 day) <= '9999-12-31' was being rewritten to i <= days_add('9999-12-31', 1). The new constant overflows the DATE domain, but the rule did not detect this: FoldConstantRule.evaluate swallows exceptions in non-debug mode and returns the unfolded expression, so the overflow leaked to runtime as "Operation day_add of 9999-12-31 00:00:00, 1 out of range". Eagerly fold the rearranged constant inside tryRearrangeChildren when both sides are literals. A non-Literal result means folding failed, in which case we throw so the outer try/catch falls back to the original comparison. This covers the symmetric overflow case for every operator pair in REARRANGEMENT_MAP (days/weeks/hours/minutes/ seconds + numeric add/sub/divide). Also add unit tests for the date overflow cases and a regression test based on the original repro. --- .../SimplifyArithmeticComparisonRule.java | 15 ++++ .../SimplifyArithmeticComparisonRuleTest.java | 30 ++++++++ ...test_arithmetic_comparison_overflow.groovy | 68 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 regression-test/suites/nereids_rules_p0/expression/simplify_arithmetic/test_arithmetic_comparison_overflow.groovy diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRule.java index a25f1bb81d4e91..5d488d6b37af2a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRule.java @@ -132,6 +132,21 @@ private static List tryRearrangeChildren(Expression left, Expression Expression newChild = oppositeOperator.getConstructor(Expression.class, Expression.class) .newInstance(right, leftLiteral); + // Eagerly fold the new constant so overflow / domain errors abort the rewrite + // here instead of leaking to runtime. For example, rewriting + // date_sub(i, 1) <= '9999-12-31' ==> i <= days_add('9999-12-31', 1) + // overflows DATE max and must fall back to the original comparison (#61761). + // FoldConstantRule.evaluate swallows exceptions in non-debug mode and returns + // the expression unchanged, so a non-Literal result signals a failed fold. + if (right instanceof Literal) { + Expression folded = FoldConstantRule.evaluate(newChild, context); + if (!(folded instanceof Literal)) { + throw new RuntimeException(String.format( + "Rearranged constant %s cannot be safely folded; keeping original comparison", newChild)); + } + newChild = folded; + } + if (left instanceof Divide && leftLiteral.compareTo(new IntegerLiteral(0)) < 0) { // Multiplying by a negative number will change the operator. return Arrays.asList(newChild, leftExpr); diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRuleTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRuleTest.java index f2f8dfcf555f86..146c318e05e91e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRuleTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifyArithmeticComparisonRuleTest.java @@ -184,6 +184,36 @@ public void testDateLike() { assertRewriteAfterTypeCoercion("seconds_sub(CA, 1) > '2021-01-01 00:00:00'", "(cast(CA as DATETIMEV2(0)) > '2021-01-01 00:00:01')"); } + @Test + public void testDateOverflowKeepsOriginal() { + // Rewriting `date_sub(d, K) <= MAX_DATE` to `d <= date_add(MAX_DATE, K)` would overflow + // the DATE / DATETIME domain. Verify the rule keeps the original comparison instead of + // producing an unsafe rewrite that explodes downstream (#61761). + executor = new ExpressionRuleExecutor(ImmutableList.of( + bottomUp( + SimplifyArithmeticRule.INSTANCE, + SimplifyArithmeticComparisonRule.INSTANCE, + FoldConstantRuleOnFE.VISITOR_INSTANCE + ) + )); + + // DATE: 9999-12-31 + 1 day overflows -> no rewrite + assertRewriteAfterTypeCoercion("days_sub(CA, 1) <= '9999-12-31'", + "(days_sub(CA, 1) <= date '9999-12-31')"); + + // DATETIME: '9999-12-31 23:59:59' + 1 second overflows -> no rewrite + assertRewriteAfterTypeCoercion("seconds_sub(AA, 1) <= '9999-12-31 23:59:59'", + "(seconds_sub(AA, 1) <= '9999-12-31 23:59:59')"); + + // DATE: 9999-12-25 + 1 week overflows -> no rewrite + assertRewriteAfterTypeCoercion("weeks_sub(CA, 1) <= '9999-12-25'", + "(weeks_sub(CA, 1) <= date '9999-12-25')"); + + // Sanity check: when the rearranged constant is safely in range the rule still rewrites. + assertRewriteAfterTypeCoercion("days_sub(CA, 1) <= '2021-01-01'", + "(CA <= date '2021-01-02')"); + } + private void assertRewriteAfterSimplify(String expr, String expected) { Expression needRewriteExpression = PARSER.parseExpression(expr); needRewriteExpression = replaceUnboundSlot(needRewriteExpression, Maps.newHashMap()); diff --git a/regression-test/suites/nereids_rules_p0/expression/simplify_arithmetic/test_arithmetic_comparison_overflow.groovy b/regression-test/suites/nereids_rules_p0/expression/simplify_arithmetic/test_arithmetic_comparison_overflow.groovy new file mode 100644 index 00000000000000..7d4e8b09d52417 --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/expression/simplify_arithmetic/test_arithmetic_comparison_overflow.groovy @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Regression test for https://github.com/apache/doris/issues/61761 +// SimplifyArithmeticComparisonRule used to rearrange +// date_sub(i, K) <= MAX_DATE ==> i <= date_add(MAX_DATE, K) +// without checking that the new constant overflows the date domain, +// which caused a runtime "out of range" error. +suite("test_arithmetic_comparison_overflow") { + sql "SET enable_nereids_planner=true" + sql "SET enable_fallback_to_original_planner=false" + + sql "drop table if exists test_rewrite_arithmetic_61761" + + sql """ + create table test_rewrite_arithmetic_61761 ( + i datetime NOT NULL, + d date NOT NULL + ) + DISTRIBUTED BY HASH(i) BUCKETS 1 + PROPERTIES ("replication_allocation" = "tag.location.default: 1"); + """ + + sql """ + insert into test_rewrite_arithmetic_61761 values + ('2026-01-20 10:00:00', '2026-01-20'); + """ + + // The original repro: datetime - 1 day <= MAX_DATETIME used to throw + // "Operation day_add of 9999-12-31 23:59:59, 1 out of range" + // Now it should evaluate normally and return TRUE. + qt_datetime_minus_day_vs_max """ + select date_sub(i, interval 1 day) <= '9999-12-31' + from test_rewrite_arithmetic_61761; + """ + + qt_datetime_minus_second_vs_max """ + select date_sub(i, interval 1 second) <= '9999-12-31 23:59:59' + from test_rewrite_arithmetic_61761; + """ + + qt_date_minus_week_vs_max """ + select date_sub(d, interval 1 week) <= '9999-12-25' + from test_rewrite_arithmetic_61761; + """ + + // Sanity: rewrites that don't overflow should still produce the right answer. + qt_datetime_minus_day_safe """ + select date_sub(i, interval 1 day) <= '2026-01-21 00:00:00' + from test_rewrite_arithmetic_61761; + """ + + sql "drop table if exists test_rewrite_arithmetic_61761" +}