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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/PercentileReservoir.java:
##########
@@ -67,21 +70,42 @@ private PercentileReservoir(NullableAggregateFunctionParams 
functionParams) {
 
     @Override
     public void checkLegalityBeforeTypeCoercion() {
+        checkLevel();
+    }
+
+    @Override
+    public void checkLegalityAfterRewrite() {
+        checkLevel();
+    }
+
+    /**
+     * The level must be a constant that folds to a literal in [0, 1]. It is 
folded here instead of
+     * waiting for the rewrite phase because a constant expression such as 
0.25 + 0.25 is only a
+     * literal after folding, some plans (INSERT ... VALUES, load column 
mappings) never run the
+     * rewrite phase, and constant folding can be turned off by 
debug_skip_fold_constant.
+     * The level is brought to DOUBLE with the same implicit cast that 
signature coercion applies,
+     * so a level that is not a valid DOUBLE behaves like the coerced 
expression: NULL under the
+     * default non-strict cast and an error under strict cast, for '' as well 
as cast('' as double).
+     */
+    private void checkLevel() {
         Expression levelArgument = getArgument(1);
-        if (!levelArgument.isConstant()) {
+        Expression level = levelArgument.isConstant()

Review Comment:
   Thanks, confirmed and fixed in a0e4f5b2331 and 5495a8602c3.
   
   Reproduced on a local cluster before the fix: with 
`CAST((CAST('1.00000005960464483090177623170427978038787841796875' AS FLOAT) > 
CAST(1 AS FLOAT)) AS DOUBLE)` as the level, `percentile_reservoir` over 
`numbers(10)` returned 9 with folding and 0 with `debug_skip_fold_constant = 
true`. BE `StringParser::string_to_float_internal` parses into a `double` and 
then narrows, while FE used `Float.parseFloat`.
   
   - a0e4f5b2331: FE casts a string to FLOAT by parsing a double and narrowing 
it (`StringLikeLiteral.castToFloat`). It also narrows DOUBLE to FLOAT with a 
single rounding instead of going through the shortest decimal string: the 
double 1 + 2^-24 prints as `1.0000000596046448`, which is above the midpoint, 
so `cast(cast('1.0000000596046448' as double) as float)` had the same problem.
   - 5495a8602c3 does what you suggested and makes the validated value the 
executed one. `PercentileReservoir` implements `RewriteWhenAnalyze`, so once 
the function is analyzed, the level is replaced with the literal `checkLevel()` 
validated. `StateCombinator` and `CombineCombinator` forward this to their 
nested function. BE therefore executes the checked literal on every path: 
plain, DISTINCT, window, `_state` / `_combine`, `INSERT ... VALUES`, and load 
column mappings, which always plan with constant folding off. That also covers 
casts whose FE folding still differs from BE, e.g. `cast(0.1 as float)`: FE 
widens it to 0.1, BE to 0.10000000149011612. Before this change, a stream load 
mapping `s = percentile_reservoir_state(v, cast(0.1 as float))` wrote a state 
that failed with "incompatible quantiles" when merged with a state built by a 
query.
   
   Tests:
   - UT:
     - `StringLikeLiteralTest` and `DoubleLiteralTest` pin the raw bits.
     - 
`PercentileReservoirParameterTest.testAnalyzedLevelIsTheValidatedLiteral` 
checks that the level is replaced for the function, `_state` and `_combine`.
   - Regression (`test_percentile_reservoir_constant_level`):
     - the three midpoint and signed-NaN level shapes, plus `cast(0.1 as 
float)`, give identical results with and without folding, for plain, DISTINCT 
and window aggregation;
     - a stored FE-folded state merges with a BE-computed one;
     - a stream load state merges with a query-built state.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteral.java:
##########
@@ -198,39 +200,46 @@ protected Expression castToIntegral(DataType targetType, 
boolean strictCast) {
     }
 
     protected Expression castToFloat() {
-        String trimmedValue = value.trim();
-        if (doublePattern.matcher(trimmedValue).matches()) {
+        Matcher matcher = doublePattern.matcher(value);
+        if (matcher.matches()) {
+            String trimmedValue = matcher.group("number");
             if 
(DoubleLiteral.POS_INF_NAME.contains(trimmedValue.toLowerCase())) {
                 return Literal.of(Float.POSITIVE_INFINITY);
             }
             if 
(DoubleLiteral.NEG_INF_NAME.contains(trimmedValue.toLowerCase())) {
                 return Literal.of(Float.NEGATIVE_INFINITY);
             }
-            if (DoubleLiteral.NAN_NAME.contains(trimmedValue.toLowerCase())) {
+            if 
(DoubleLiteral.NAN_NAME.contains(trimNanPayload(trimmedValue).toLowerCase())) {
                 return Literal.of(Float.NaN);
             }
-            return Literal.of(Float.parseFloat(value.trim()));
+            return Literal.of(Float.parseFloat(trimmedValue));
         }
         throw new CastException(String.format("%s can't cast to float in 
strict mode.", value));
     }
 
     protected Expression castToDouble() {
-        String trimmedValue = value.trim();
-        if (doublePattern.matcher(trimmedValue).matches()) {
+        Matcher matcher = doublePattern.matcher(value);
+        if (matcher.matches()) {
+            String trimmedValue = matcher.group("number");
             if 
(DoubleLiteral.POS_INF_NAME.contains(trimmedValue.toLowerCase())) {
                 return Literal.of(Double.POSITIVE_INFINITY);
             }
             if 
(DoubleLiteral.NEG_INF_NAME.contains(trimmedValue.toLowerCase())) {
                 return Literal.of(Double.NEGATIVE_INFINITY);
             }
-            if (DoubleLiteral.NAN_NAME.contains(trimmedValue.toLowerCase())) {
+            if 
(DoubleLiteral.NAN_NAME.contains(trimNanPayload(trimmedValue).toLowerCase())) {

Review Comment:
   Thanks, confirmed and fixed in a0e4f5b2331 and 5495a8602c3.
   
   Before the fix, `signbit(cast('-nan(foo)' as double))` folded to false on FE 
and returned true on BE, so `CAST(signbit(CAST('-nan(foo)' AS DOUBLE)) AS 
DOUBLE)` as the level returned the minimum with folding and the maximum with 
`debug_skip_fold_constant = true`.
   
   - a0e4f5b2331: `StringLikeLiteral` folds `-nan` / `-nan(payload)` to a 
negative NaN, matching fast_float's `parse_infnan`. The FLOAT form keeps the 
sign too, and FLOAT<->DOUBLE literal casts preserve the NaN sign, as 
`static_cast` does on BE.
   - 5495a8602c3: the level is now executed as the literal FE validated 
(`RewriteWhenAnalyze`, also forwarded by the `_state` / `_combine` 
combinators), so the two paths can no longer run different levels.
     - Signed NaNs exposed one more fold that relied on NaN equality: 
`FoldConstantRuleOnFE` merged If / CASE branches that are `equals`, and 
`Literal.equals` treats NaNs of both signs as equal. So `if(<unfoldable false>, 
cast('-nan' as double), cast('nan' as double))` collapsed to the true branch.
     - Branches that hold a FLOAT/DOUBLE NaN literal, including elements of an 
array, map or struct literal, are now never merged. Such a level stays unfolded 
and is rejected as not constant, as it was before this PR.
   
   Tests:
   - UT:
     - `StringLikeLiteralTest` asserts the raw bits: `0xfff8000000000000` / 
`0xffc00000` for `-nan`, positive for `nan(foo)` / `+nan`, and the sign kept 
through the FLOAT->DOUBLE cast.
     - `FoldConstantTest` checks that If / CASE over -NaN/+NaN DOUBLE, FLOAT 
and ARRAY literals is not collapsed.
   - Regression: `signbit` of `-nan(foo)`, `' -nan '`, `nan(foo)` and FLOAT 
`-nan(foo)` gives the same `true true false true` with and without folding; the 
signed-NaN level gives the same result for plain and DISTINCT in both modes; 
the NaN-If level shapes are rejected.



-- 
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