Skip to content

[fix](fe) Align FE constant folding results with execution#65319

Open
morrySnow wants to merge 1 commit into
apache:masterfrom
morrySnow:fix-const-folding
Open

[fix](fe) Align FE constant folding results with execution#65319
morrySnow wants to merge 1 commit into
apache:masterfrom
morrySnow:fix-const-folding

Conversation

@morrySnow

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary: FE constant folding returned results that differed from execution for several Nereids expressions. DATE and DATEV2 casts parsed the whole datetime value and could round a valid max-date input past the DATETIMEV2 boundary before truncating to date. TIME values lost their sign when coerced to DATETIMEV2 for TIMEDIFF folding, and TIMEDIFF had no FE executable implementation. DecimalV3 zero values cast to string used BigDecimal scientific notation instead of the target scale. STR_TO_DATE only inferred DATE for literal format arguments, so constant dynamic formats such as CONCAT('%Y-', '%j') kept DATETIMEV2 schema. FIELD used Java NaN comparison, unlike execution where NaN matches NaN. This change aligns those FE folding paths with execution and adds focused unit coverage.

Release note

Fix FE constant folding results for several date/time, decimal, str_to_date, and NaN FIELD expressions.

Check List (For Author)

  • Test: Unit Test
    • ./run-fe-ut.sh --run org.apache.doris.nereids.rules.expression.FoldConstantTest
  • Behavior changed: Yes. FE constant folding now matches execution for the covered expressions.
  • Does this need documentation: No

Problem Summary: FE constant folding returned results that differed from execution for several Nereids expressions. DATE and DATEV2 casts parsed the whole datetime value and could round a valid max-date input past the DATETIMEV2 boundary before truncating to date. TIME values lost their sign when coerced to DATETIMEV2 for TIMEDIFF folding, and TIMEDIFF had no FE executable implementation. DecimalV3 zero values cast to string used BigDecimal scientific notation instead of the target scale. STR_TO_DATE only inferred DATE for literal format arguments, so constant dynamic formats such as CONCAT('%Y-', '%j') kept DATETIMEV2 schema. FIELD used Java NaN comparison, unlike execution where NaN matches NaN. This change aligns those FE folding paths with execution and adds focused unit coverage.

Fix FE constant folding results for several date/time, decimal, str_to_date, and NaN FIELD expressions.

- Test: Unit Test
    - ./run-fe-ut.sh --run org.apache.doris.nereids.rules.expression.FoldConstantTest
- Behavior changed: Yes. FE constant folding now matches execution for the covered expressions.
- Does this need documentation: No
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morrySnow

Copy link
Copy Markdown
Contributor Author

run buildall

@morrySnow

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review found several FE constant-folding mismatches that can make folded results diverge from BE execution. I am requesting changes on the inline issues below.

Validation: static review only; FE test execution was not run because this checkout is missing thirdparty/installed/protoc.

tz = "+08:00";
}
if (!isDatetime) {
return getDateLiteralDateTime(year, month, date, hour, minute, second, targetType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date-only path returns here before the timezone suffix is validated. getDateLiteralDateTime(...) only checks hour/minute/second and discards tz, so inputs like 2024-05-01T00:00XYZ or 2024-05-01T00:00+14:30 now fold to a DATE/DATEV2 literal. BE cast execution still rejects those cases; the existing cast tests expect null for both invalid timezone names and +14:30. Please keep the timezone validation on this path before truncating to the date, for example by factoring the validation from getDateTimeLiteral(...) or passing tz into the date-only helper.


@Override
protected Expression uncheckedCastTo(DataType targetType) throws AnalysisException {
long microsecondValue = ((Double) getValue()).longValue();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies the signed full microsecond value before every target cast, but BE only uses the microsecond field for TIMEV2->DATETIMEV2. For DATE, DATEV2, and DATETIME(v1), cast_to_date.h adds signed hour/minute/second and ignores the fractional part. That means a fold such as cast(cast('-00:00:00.999999' as time(6)) as date) can move to the previous day in FE, while BE keeps the current date. Please keep the full microsecond path for DATETIMEV2 and mirror BE's hour/minute/second-only behavior for the other targets.

}
this.negative = value < 0;
long v = (long) Math.abs(value);
long v = (long) Math.abs(Math.round(value));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sec_to_time(DoubleLiteral) passes sec * 1000000 to this constructor, but BE does not round that value: TimeValue::from_double_with_limit truncates the double-derived microseconds. With Math.round, sec_to_time(cast(0.0000015 as double)) folds to 00:00:00.000002 in FE, while BE keeps 00:00:00.000001. Please preserve truncation for the sec_to_time(double) path, or otherwise split the rounding behavior so this constructor still matches BE execution.

return (Literal) format;
}
if (format.isConstant()) {
Expression evaluated = ExpressionEvaluator.INSTANCE.eval(format);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still does not handle nested constant format expressions. ExpressionEvaluator.eval(format) is shallow here, so a constant format like concat('%Y-', cast('%j' as string)) still contains a Cast child during signature inference and evaluates back to the original expression instead of a StringLikeLiteral. For str_to_date(col, concat('%Y-', cast('%j' as string))), the first argument is not constant, so the root call cannot later fold to a literal and callers can still see DATETIMEV2 even though the format has no time fields. Please recursively fold/normalize the format before deriving the signature, and add a nonconstant-input test for this case.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants