This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push: new 4c6df9062e [fix](DECIMALV3)fix cumulative precision when literal and DECIMALV3 operations in Legacy (#20354) 4c6df9062e is described below commit 4c6df9062ecf39acb80469fc586e1e75e53ac010 Author: Mryange <59914473+mrya...@users.noreply.github.com> AuthorDate: Fri Jun 9 08:58:55 2023 +0800 [fix](DECIMALV3)fix cumulative precision when literal and DECIMALV3 operations in Legacy (#20354) The precision handling for division with DECIMALV3 is as follows (excluding cases where division increases precision): (p1, s1) / (p2, s2) ----> (p1 + s2, s1) However, due to precision loss in division, it is considered to increase the precision of the left operand: (p1, s1) / (p2, s2) =====> (p1 + s2, s1 + s2) / (p2, s2) ----> (p1 + s2, s1) However, the legacy optimizer repeats the analyze and substitute steps for an expression, which can result in the accumulation of precision: (p1, s1) / (p2, s2) =====> (p1 + s2, s1 + s2) / (p2, s2) =====> (p1 + s2 + s2, s1 + s2 + s2) / (p2, s2) To address this, the previous approach was to forcibly convert the left operand of DECIMALV3 calculations. This results in rewriting the expression as: (p1, s1) / (p2, s2) =====> cast((p1, s1) as (p1 + s2, s1 + s2)) / (p2, s2) Then, during the substitution step, a check is performed. If it is a cast expression, the expression modified by the cast is extracted: cast((p1, s1) as (p1 + s2, s1 + s2)) =====> (p1, s1) protected Expr substituteImpl(ExprSubstitutionMap smap, ExprSubstitutionMap disjunctsMap, Analyzer analyzer) { if (isImplicitCast()) { return getChild(0).substituteImpl(smap, disjunctsMap, analyzer); } This way, there won't be repeated analysis, preventing the continuous increase in precision. However, if the left expression is a constant (literal), theoretically, the precision would continue to increase. Unfortunately, the code that was removed in this PR (#19926) obscured this issue. for (Expr child : children) { if (child instanceof DecimalLiteral && child.getType().isDecimalV3()) { ((DecimalLiteral)child).tryToReduceType(); } } An attempt will be made to reduce the precision of literals in the expressions. However, this code snippet can cause such a bug. mysql [test]>select cast(1 as DECIMALV3(16, 2)) / cast(3 as DECIMALV3(16, 2)); +-----------------------------------------------------------+ | CAST(1 AS DECIMALV3(16, 2)) / CAST(3 AS DECIMALV3(16, 2)) | +-----------------------------------------------------------+ | 0.00 | +-----------------------------------------------------------+ 1.00 / 3.00, due to reduced precision, becomes 1 / 3. <--Describe your changes.--> --- .../main/java/org/apache/doris/analysis/ArithmeticExpr.java | 12 ++++++++++-- .../src/main/java/org/apache/doris/analysis/CastExpr.java | 10 ++++++++++ .../main/java/org/apache/doris/analysis/DecimalLiteral.java | 7 +++++++ .../java/org/apache/doris/rewrite/FoldConstantsRule.java | 3 +++ regression-test/data/correctness/test_cast_as_decimalv3.out | 11 ++++++++--- .../suites/correctness/test_cast_as_decimalv3.groovy | 3 +++ 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ArithmeticExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ArithmeticExpr.java index 3df32f0ae9..1a962680e2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ArithmeticExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ArithmeticExpr.java @@ -505,7 +505,7 @@ public class ArithmeticExpr extends Expr { if (((ScalarType) type).getScalarScale() != ((ScalarType) children.get(1).type).getScalarScale()) { castChild(type, 1); } - } else if (op == Operator.DIVIDE && t1TargetType.isDecimalV3()) { + } else if (op == Operator.DIVIDE && (t1TargetType.isDecimalV3())) { int leftPrecision = t1Precision + t2Scale + Config.div_precision_increment; int leftScale = t1Scale + t2Scale + Config.div_precision_increment; if (leftPrecision > ScalarType.MAX_DECIMAL128_PRECISION) { @@ -515,7 +515,15 @@ public class ArithmeticExpr extends Expr { type = castBinaryOp(Type.DOUBLE); break; } - castChild(ScalarType.createDecimalV3Type(leftPrecision, leftScale), 0); + Expr child = getChild(0); + if (child instanceof DecimalLiteral) { + DecimalLiteral literalChild = (DecimalLiteral) child; + Expr newChild = literalChild + .castToDecimalV3ByDivde(ScalarType.createDecimalV3Type(leftPrecision, leftScale)); + setChild(0, newChild); + } else { + castChild(ScalarType.createDecimalV3Type(leftPrecision, leftScale), 0); + } } else if (op == Operator.MOD) { // TODO use max int part + max scale of two operands as result type // because BE require the result and operands types are the exact the same decimalv3 type diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java index 8cc25b9b27..be93975efb 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java @@ -63,6 +63,8 @@ public class CastExpr extends Expr { // True if this cast does not change the type. private boolean noOp = false; + private boolean notFold = false; + private static final Map<Pair<Type, Type>, Function.NullableMode> TYPE_NULLABLE_MODE; static { @@ -582,5 +584,13 @@ public class CastExpr extends Expr { public String getStringValueForArray() { return children.get(0).getStringValueForArray(); } + + public void setNotFold(boolean notFold) { + this.notFold = notFold; + } + + public boolean isNotFold() { + return this.notFold; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DecimalLiteral.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/DecimalLiteral.java index 81fe2bf755..47e98e1142 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DecimalLiteral.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/DecimalLiteral.java @@ -384,6 +384,13 @@ public class DecimalLiteral extends LiteralExpr { return super.uncheckedCastTo(targetType); } + public Expr castToDecimalV3ByDivde(Type targetType) { + // onlye use in DecimalLiteral divide DecimalV3 + CastExpr expr = new CastExpr(targetType, this); + expr.setNotFold(true); + return expr; + } + @Override public int hashCode() { return 31 * super.hashCode() + Objects.hashCode(value); diff --git a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java index dcc0bede70..8b65597812 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java @@ -109,6 +109,9 @@ public class FoldConstantsRule implements ExprRewriteRule { // cast-to-types and that can lead to query failures, e.g., CTAS if (expr instanceof CastExpr) { CastExpr castExpr = (CastExpr) expr; + if (castExpr.isNotFold()) { + return castExpr; + } if (castExpr.getChild(0) instanceof NullLiteral) { return castExpr.getChild(0); } diff --git a/regression-test/data/correctness/test_cast_as_decimalv3.out b/regression-test/data/correctness/test_cast_as_decimalv3.out index 2e7ab315b3..9032acc1ee 100644 --- a/regression-test/data/correctness/test_cast_as_decimalv3.out +++ b/regression-test/data/correctness/test_cast_as_decimalv3.out @@ -1,9 +1,14 @@ -- This file is automatically generated. You should know what you did if you want to edit this -- !select1 -- -0.333333333333333333333333 -0.250000000000000000000000 -0.200000000000000000000000 +0.333333 +0.250000 +0.200000 -- !select2 -- 0.333333 +-- !select3 -- +0.33333 +0.25000 +0.20000 + diff --git a/regression-test/suites/correctness/test_cast_as_decimalv3.groovy b/regression-test/suites/correctness/test_cast_as_decimalv3.groovy index 6bf346ad87..b7e4998a23 100644 --- a/regression-test/suites/correctness/test_cast_as_decimalv3.groovy +++ b/regression-test/suites/correctness/test_cast_as_decimalv3.groovy @@ -47,4 +47,7 @@ suite("test_cast_as_decimalv3") { qt_select2 """ select cast(1 as DECIMALV3(5, 2)) / cast(3 as DECIMALV3(5, 2)) """ + qt_select3 """ + select 1.0 / val from divtest order by id + """ } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org