Uh oh!
There was an error while loading. Please reload this page.
[fix](function) Prefer DATETIMEV2 over TIMESTAMPTZ when binding *_diff functions on non-literal string args - #67238
Conversation
… functions on non-literal string args All 12 *_diff scalar functions listed their TIMESTAMPTZ signature first in SIGNATURES. SearchSignature's timezone-coercion penalty only fires for literals it can inspect (ExpressionUtils.getLiteralAfterUnwrapNullable, apache#64127), so for a string-typed non-literal argument (varchar column, subquery projection slot, UNION-ALL output slot) every candidate signature ties and the tie-break keeps the first-listed candidate: TIMESTAMPTZ. The value is then cast through timestamptz(6), which treats it as session-local time and shifts it to UTC, so results are off-by-one under any non-UTC session time_zone. Move the TimeStampTz signature to last in each file. Literal-with-timezone arguments still route to TIMESTAMPTZ via the timeZoneCoersionScore branch, and typed TIMESTAMPTZ / DATETIMEV2 / DATEV2 arguments still bind their identical-match signatures; only the previously order-decided tie for non-literal string arguments changes, to DATETIMEV2 (wall-clock semantics). New DiffFunctionSignatureTest covers all 12 functions in both directions: varchar slots bind DATETIMEV2 (fails before this change), timestamptz slots still bind TIMESTAMPTZ. Fixesapache#66120
hello-stephen
commented
Aug 27, 2026
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
morrySnow
commented
Aug 28, 2026
run buildall |
morrySnow
commented
Aug 28, 2026
/review |
There was a problem hiding this comment.
Request changes: the signature reorder fixes the intended homogeneous VARCHAR fallback, but it also changes valid literal calls from TIMESTAMPTZ instant semantics to DATETIMEV2 civil semantics. One inline comment covers the two related, concrete failure classes.
Critical checkpoints:
- Goal and proof: all 12 functions now select DATETIMEV2 for uninspectable VARCHAR slots, and the new unit directly covers that plus typed TIMESTAMPTZ slots. The stated goal is still incomplete because mixed accepted literal forms can rebind; the unit omits those cases.
- Scope and clarity: the production edit is mechanically small, but signature order is a broader semantic tie-break than the test demonstrates.
- Concurrency: not involved.
- Lifecycle/static initialization: only immutable signature-list ordering changes; no non-intuitive lifecycle or cross-unit initialization dependency is introduced.
- Configuration: none added or changed.
- Compatibility: no wire, storage, serialized-format, or function-symbol change; existing FE folding and BE DATETIMEV2/DATEV2/TIMESTAMPTZ overloads remain aligned for rolling execution.
- Parallel paths: all 12 scalar classes were updated consistently. Analyzer cast insertion, cached/recomputed signatures, nullable/subquery/UNION shapes, TimeDiff precision, FE folding, and BE dispatch were traced; no separate missed path survived.
- Conditional logic: the existing summed timezone score and the narrower DateTimeChecker grammar are the blocking correctness seam described inline.
- Tests and results: the JUnit test is structurally consistent and covers every constructor, but it lacks mixed zoned/zone-less and checker-versus-cast grammar regressions. No generated result file changed. No local build or test was run because this review environment explicitly forbids builds.
- Observability: no new runtime path needs logs or metrics.
- Transactions/persistence and data writes: not involved.
- New FE-BE variables: none.
- Performance: constant-size signature lists and matching work are unchanged; no material CPU or memory concern.
- Other correctness: the inline issue is user-visible and blocking because elapsed-hour results change across DST.
User focus: no additional focus was supplied.
Completion status: three review rounds completed; every Round 3 agent returned NO_NEW_VALUABLE_FINDINGS, all candidates were accepted, deduplicated, or dismissed with evidence, and this review targets exact head f5c40e8. FE UT, compile, and performance checks were still pending at submission time.
| FunctionSignature.ret(BigIntType.INSTANCE).args(DateV2Type.INSTANCE, DateV2Type.INSTANCE)); | ||
| FunctionSignature.ret(BigIntType.INSTANCE).args(DateV2Type.INSTANCE, DateV2Type.INSTANCE), | ||
| FunctionSignature.ret(BigIntType.INSTANCE) | ||
| .args(TimeStampTzType.WILDCARD, TimeStampTzType.WILDCARD)); |
There was a problem hiding this comment.
[P1] Preserve instant semantics for all accepted zoned literal forms
The timezone score is summed across both arguments, so a recognized zoned literal paired with a zone-less literal scores +1 - 1 = 0 and ties DATETIMEV2. With time_zone='America/Los_Angeles', hours_diff('2021-03-14 03:30:00-07:00', '2021-03-14 01:30:00') therefore changes from 1 elapsed hour on the old TIMESTAMPTZ winner to 2 civil hours after moving this overload last. There is a second instance of the same policy gap where both inputs have explicit zones: the cast grammar accepts and normalizes compact -0700, but DateTimeChecker requires a colon before offset minutes, so pairing 2021-03-15 00:00:00-0700 with 2021-03-14 00:00:00-08:00 again ties and changes 23 elapsed hours to 24 civil hours. Please preserve TIMESTAMPTZ whenever an inspectable literal supplies any accepted explicit zone, using the same accepted grammar as the cast path, without reintroducing the all-uninspectable-VARCHAR fallback; add regressions for both mixed cases.
…ith a zone-less one
Signature matching summed the timezone preference across arguments, so a call
pairing a zoned literal with a zone-less one scored +1-1=0 and tied the
DATETIMEV2 overload. With the TIMESTAMPTZ signature now listed last, that tie
resolved to civil semantics and changed results -- e.g. under
time_zone='America/Los_Angeles', hours_diff('2021-03-14 03:30:00-07:00',
'2021-03-14 01:30:00') returned 2 civil hours instead of 1 elapsed hour across
the DST boundary.
Track "some argument carried an explicit zone" and "some argument did not"
independently instead of summing, so an explicit zone anywhere in the call
decides instant semantics and cannot be cancelled out. Zone-less-only literals
still prefer DATETIMEV2 (apache#64127), and calls with no inspectable literal still
fall through to list order (this PR's original fix).lets-order-some-fries
commented
Aug 31, 2026
Good catch — the summed score was a real regression from the reorder. Fixed in 66e0ff1.
Both of your concrete cases are covered. New regressions in On the compact-offset grammar, one thing worth separating out. A residual case remains: when every zone-carrying literal in the call uses the compact form, Closing it means widening |
lets-order-some-fries
commented
Sep 2, 2026
/review |
lets-order-some-fries
commented
Sep 5, 2026
@morrySnow when you have a moment — this one needs a committer trigger to move, and I want to make sure it isn't just sitting on a stale signal. The Two things are stale rather than failing:
So a re-run of both would either clear it or give you something real to look at. No rush on my open question about widening |
What problem does this PR solve?
Issue Number: close#66120
Related PR: #64127
Problem Summary:
DATEDIFFand the other eleven*_diffscalar functions silently return off-by-one (or off-by-N) results when (1) an argument is a string-typed non-literal — a varchar column, subquery projection slot, or UNION-ALL output slot — and (2) the sessiontime_zoneis not UTC.All 12 functions list their TIMESTAMPTZ signature first in
SIGNATURES. The timezone-coercion penalty inSearchSignature.doMatchTypesonly fires for literals it can inspect (ExpressionUtils.getLiteralAfterUnwrapNullable, added in #64127), so for a varcharSlotReferenceevery candidate signature ties and the tie-break keeps the first-listed candidate — TIMESTAMPTZ. The varchar value is then cast throughtimestamptz(6), which treats it as session-local time and shifts it to UTC, so day-number arithmetic runs on the shifted value.Fix, as proposed by @Baymine in #66120: move the TimeStampTz signature to last in each of the 12
*Difffiles. Literal-with-timezone arguments still route to TIMESTAMPTZ via thetimeZoneCoersionScorebranch; typed TIMESTAMPTZ / DATETIMEV2 / DATEV2 arguments still bind their identical-match signatures. Only the previously order-decided tie for non-literal string arguments changes: TIMESTAMPTZ → DATETIMEV2 (wall-clock semantics, independent of session time zone).New unit test
DiffFunctionSignatureTestcovers all 12 functions in both directions: varchar slots must bind DATETIMEV2 (fails on master before this change) and TIMESTAMPTZ slots must still bind TIMESTAMPTZ. Happy to also add a SQL regression suite mirroring the issue's reproducer (varchar column / subquery slot / UNION-ALL slot under+08:00) if wanted — omitted here because the.outexpected-files need a cluster run to generate.Release note
Fixed DATEDIFF and the other *_diff functions returning wrong results on varchar columns and subquery slots when the session time_zone is not UTC.
Check List (For Author)
Test
Behavior changed:
*_diffcalls whose string-typed argument is NOT a literal, the argument now binds to DATETIMEV2 (wall-clock) instead of TIMESTAMPTZ (UTC-shifting). That re-bind is the fix; literal arguments and typed date/datetime/timestamptz arguments are unaffected.Does this need documentation?