mrhhsg commented on code in PR #68488:
URL: https://github.com/apache/doris/pull/68488#discussion_r4104676703


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java:
##########
@@ -675,12 +679,34 @@ public Expression visitIf(If ifExpr, 
ExpressionRewriteContext context) {
             return typeCoercionTrueValue;
         } else if (condition.equals(BooleanLiteral.FALSE) || 
condition.isNullLiteral()) {
             return typeCoercionFalseValue;
-        } else if (typeCoercionTrueValue.equals(typeCoercionFalseValue)) {
+        } else if (isSameBranch(typeCoercionTrueValue, 
typeCoercionFalseValue)) {
             return typeCoercionTrueValue;
         }
         return TypeCoercionUtils.ensureSameResultType(originIf, ifExpr, 
context);
     }
 
+    // Literal.equals takes NaNs of both signs as equal, but signbit() tells 
them apart, so branches
+    // holding a NaN literal, also as an element of a complex literal, are not 
merged into one
+    private static boolean isSameBranch(Expression branch, Expression other) {
+        return branch.equals(other) && !branch.anyMatch(expression -> 
holdsNaN((Expression) expression));

Review Comment:
   Not changed in this PR. The `NULLIF` / OR-dedup / CASE `uniqueOperands` 
behavior comes from `Literal.equals`/`hashCode` treating NaNs of both signs as 
equal. That is unchanged and gives the same result as at the merge base. There, 
`CAST('-nan' AS DOUBLE)` folded to the positive NaN, so both `IF`s in your 
example were structurally identical and `NULLIF` already folded to NULL. None 
of these rewrites gives a different answer with this PR than without it.
   
   These rewrites also no longer affect `percentile_reservoir`. The level BE 
executes is now the literal FE validated, and a NaN level is rejected.
   
   Making literal equality sign-aware for NaN is a global change. Comparing raw 
bits breaks, because Java arithmetic produces NaNs whose sign depends on the 
operation. It also needs the wire fix from the `DoubleLiteral` thread before a 
sign-distinguishing result can survive to BE. So signed-NaN semantics across 
the optimizer should be a separate change, not part of this function fix.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/FloatLiteral.java:
##########
@@ -66,6 +66,10 @@ protected Expression uncheckedCastTo(DataType targetType) 
throws AnalysisExcepti
             return this;
         }
         if (targetType.isDoubleType()) {
+            if (Float.isNaN(value)) {
+                // widening on BE keeps the sign of a NaN, which signbit() can 
observe
+                return new DoubleLiteral(Math.copySign(Double.NaN, 
Float.floatToRawIntBits(value) < 0 ? -1.0 : 1.0));

Review Comment:
   Not changed in this PR. Even if `FloatLiteral.toLegacyLiteral` kept the 
sign, the slot-dependent tree in your example would still lose it at the 
fragment wire: Java Thrift `writeDouble` uses `Double.doubleToLongBits`, as 
described in the `DoubleLiteral` thread. So fixing only the legacy conversion 
would change nothing observable.
   
   This case is also not a regression. At the merge base, `CAST('-nan(foo)' AS 
FLOAT)` did not fold at all (NULL under non-strict cast, an error under 
strict), while BE returned a negative NaN, so fold on and fold off already 
disagreed. The `percentile_reservoir` level is not affected, because BE 
executes the FE-validated literal and a NaN level is rejected. Carrying the NaN 
sign through FE-to-BE literal transport is a protocol-level change, and it 
belongs in a separate PR.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/PercentileReservoir.java:
##########
@@ -67,22 +72,56 @@ private PercentileReservoir(NullableAggregateFunctionParams 
functionParams) {
 
     @Override
     public void checkLegalityBeforeTypeCoercion() {
+        checkLevel();
+    }
+
+    @Override
+    public void checkLegalityAfterRewrite() {
+        checkLevel();
+    }
+
+    /**
+     * Execute the level literal that checkLevel() validated. BE would 
otherwise evaluate the constant
+     * expression itself wherever it is not folded (load planning, DISTINCT, 
debug_skip_fold_constant),
+     * and a cast such as FLOAT to DOUBLE can compute a different value there 
than the FE folding.
+     */
+    @Override
+    public Expression rewriteWhenAnalyze() {
+        return withChildren(ImmutableList.of(getArgument(0), checkLevel()));

Review Comment:
   Not changed. The representations were already split at the merge base, so no 
choice here keeps every existing state compatible:
   
   - At the merge base, `SELECT`, `INSERT ... SELECT` and `INSERT ... VALUES` 
states already stored the FE value. `FoldConstantRuleOnFE` folded the coerced 
`CAST(<decimal> AS DOUBLE)` with `FractionalLiteral.uncheckedCastTo`, which is 
`BigDecimal.doubleValue()` and is not changed by this PR.
   - Only load planning (`NereidsLoadUtils` sets `debug_skip_fold_constant`) 
let BE compute `(double)unscaled / (double)10^scale`.
   
   So for a level of that precision, states written by queries and states 
written by loads could already not be merged with each other before this PR. 
The PR makes every writer use one value, the FE value that queries already used.
   
   You are right that there is one upgrade effect. A state written by a load 
before the upgrade, with a DECIMAL level of more than about 15 significant 
digits, will not merge with one written after it. Keeping the old BE value for 
loads would instead keep the query/load split for all new data. The root cause 
is that BE's decimal-to-double cast is not correctly rounded, and DECIMAL256 
goes through `long double`, so the result depends on the platform. That should 
be fixed in BE in a separate PR rather than copied into FE. I'll note the 
load-path effect for such high-precision levels in the PR description.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/PercentileReservoir.java:
##########
@@ -67,22 +72,56 @@ private PercentileReservoir(NullableAggregateFunctionParams 
functionParams) {
 
     @Override
     public void checkLegalityBeforeTypeCoercion() {
+        checkLevel();
+    }
+
+    @Override
+    public void checkLegalityAfterRewrite() {
+        checkLevel();

Review Comment:
   Fixed in 6dd4e45efde. `checkLegalityAfterRewrite` now validates a copy of 
the level with every `Nullable` / `NonNullable` replaced by its child 
(`rewriteUp`), while the executed level keeps the wrappers, so the requested 
state layout is preserved. The pre-push review also found that chained casts 
nest the wrappers (`NonNullable(Nullable(0.25))`, or `Cast(Nullable(Cast(0.25 
AS FLOAT)) AS DOUBLE)` through a nullable FLOAT level), so every depth is 
stripped, not only the top one. The analysis-time check and the level rewrite 
still see the unstripped argument, so a user-written `nullable(0.25)` level is 
still rejected as at the merge base.
   
   Tests: 
`PercentileReservoirParameterTest.testLevelWrappedByAggStateCastIsAccepted` 
builds the three shapes with the real `ConvertAggStateCast.convert`, checks 
that they are accepted, and checks that `1.5` beneath the wrappers is still 
rejected. The regression cases `qt_nullable_level_state_cast`, 
`qt_chained_level_state_cast` and `qt_chained_float_level_state_cast` in 
`test_percentile_reservoir_constant_level` each return 2.25. Before the fix all 
three failed with "must be a constant". `datatype_p0/agg_state` (14 suites) 
also passes.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to