From 94d4c66d7f87f772c01c7dae4050018ee5b95b99 Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Thu, 18 Jun 2026 22:13:08 +0800 Subject: [PATCH 1/2] [fix](nereids) Fix divide-by-zero guard in divideDecimal constant folding divideDecimal checked the numerator (first) instead of the denominator (second) when guarding against division by zero. As a result DECIMALV2 constant folding evaluated 0 / x as NULL instead of 0, and x / 0 threw ArithmeticException (caught by ExpressionEvaluator and left unfolded). Guard on second, matching divideDouble and divideDecimalV3. Adds regression tests for both cases. --- .../executable/NumericArithmetic.java | 2 +- .../executable/NumericArithmeticTest.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmetic.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmetic.java index 6f664e2fadf00a..33fa7a3c525d19 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmetic.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmetic.java @@ -278,7 +278,7 @@ public static Expression divideDouble(DoubleLiteral first, DoubleLiteral second) */ @ExecFunction(name = "divide") public static Expression divideDecimal(DecimalLiteral first, DecimalLiteral second) { - if (first.getValue().compareTo(BigDecimal.ZERO) == 0) { + if (second.getValue().compareTo(BigDecimal.ZERO) == 0) { return new NullLiteral(first.getDataType()); } BigDecimal result = first.getValue().divide(second.getValue()); diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmeticTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmeticTest.java index 749a57e8b5db21..2368c67be489f8 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmeticTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/NumericArithmeticTest.java @@ -17,10 +17,12 @@ package org.apache.doris.nereids.trees.expressions.functions.executable; +import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; import org.apache.doris.nereids.trees.expressions.literal.DecimalLiteral; import org.apache.doris.nereids.trees.expressions.literal.DecimalV3Literal; import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral; +import org.apache.doris.nereids.trees.expressions.literal.NullLiteral; import org.apache.doris.nereids.types.DecimalV2Type; import org.apache.doris.nereids.types.DecimalV3Type; @@ -47,6 +49,29 @@ public void testDecimalV2Abs() { Assertions.assertEquals(DecimalV2Type.createDecimalV2Type(10, 0), result.getDataType()); } + @Test + public void testDivideDecimalZeroNumerator() { + // 0 / 5 must fold to decimal 0, not NULL. The zero-guard must check the + // denominator (second), not the numerator (first). + DecimalLiteral zero = new DecimalLiteral(DecimalV2Type.createDecimalV2Type(10, 0), new BigDecimal(0)); + DecimalLiteral five = new DecimalLiteral(DecimalV2Type.createDecimalV2Type(10, 0), new BigDecimal(5)); + Expression result = NumericArithmetic.divideDecimal(zero, five); + Assertions.assertTrue(result instanceof DecimalLiteral, + "0 / 5 should fold to a decimal, but got " + result); + Assertions.assertEquals(0, ((DecimalLiteral) result).getValue().compareTo(BigDecimal.ZERO), + "0 / 5 should fold to 0"); + } + + @Test + public void testDivideDecimalZeroDenominator() { + // 5 / 0 must fold to NULL (SQL divide-by-zero semantics), not throw. + DecimalLiteral five = new DecimalLiteral(DecimalV2Type.createDecimalV2Type(10, 0), new BigDecimal(5)); + DecimalLiteral zero = new DecimalLiteral(DecimalV2Type.createDecimalV2Type(10, 0), new BigDecimal(0)); + Expression result = NumericArithmetic.divideDecimal(five, zero); + Assertions.assertTrue(result instanceof NullLiteral, + "5 / 0 should fold to NULL, but got " + result); + } + @Test public void testSignBit() { Assertions.assertEquals(BooleanLiteral.FALSE, NumericArithmetic.signbit(new DoubleLiteral(0.0))); From 8165c4a5d401c9c0b3f961a6494d96ee97bf2522 Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Mon, 22 Jun 2026 16:29:23 +0800 Subject: [PATCH 2/2] [test](nereids) Cover divideDecimal zero handling through fold-constant dispatch Adds a FoldConstantRuleOnFE-level test that builds Divide over two DecimalV2 (DecimalLiteral) operands and verifies the full dispatch: 0 / 5 folds to decimal 0 (not NULL) and 5 / 0 folds to NULL. Complements the existing direct unit tests on the executable helper. --- .../rules/expression/FoldConstantTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java index 9f46a7970a2c72..874863e962e60d 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java @@ -702,6 +702,27 @@ void testFoldString() { }, "soundex only supports ASCII"); } + @Test + void testFoldDivideDecimalV2() { + executor = new ExpressionRuleExecutor(ImmutableList.of( + bottomUp(FoldConstantRuleOnFE.VISITOR_INSTANCE) + )); + // 0 / 5 over DecimalV2 literals must fold to decimal 0 through the full FoldConstantRuleOnFE + // dispatch (which selects NumericArithmetic.divideDecimal), not NULL. + Divide divide = new Divide(new DecimalLiteral(new BigDecimal(0)), new DecimalLiteral(new BigDecimal(5))); + Expression rewritten = executor.rewrite(divide, context); + Assertions.assertFalse(rewritten instanceof NullLiteral, "0 / 5 must not fold to NULL, got " + rewritten); + Assertions.assertTrue(rewritten instanceof DecimalLiteral, + "0 / 5 should fold to a decimal literal, got " + rewritten); + Assertions.assertEquals(0, ((DecimalLiteral) rewritten).getValue().compareTo(BigDecimal.ZERO), + "0 / 5 should fold to 0"); + + // 5 / 0 over DecimalV2 literals must fold to NULL (Doris/MySQL divide-by-zero semantics). + divide = new Divide(new DecimalLiteral(new BigDecimal(5)), new DecimalLiteral(new BigDecimal(0))); + rewritten = executor.rewrite(divide, context); + Assertions.assertTrue(rewritten instanceof NullLiteral, "5 / 0 should fold to NULL, got " + rewritten); + } + @Test void testleFoldNumeric() { executor = new ExpressionRuleExecutor(ImmutableList.of(