Skip to content

[feature](function) Add Trino-compatible timezone_hour and timezone_m… - #66860

Open
POSSIBLEwbwb wants to merge 3 commits into
apache:masterfrom
POSSIBLEwbwb:timezone-hour-minute
Open

[feature](function) Add Trino-compatible timezone_hour and timezone_m…#66860
POSSIBLEwbwb wants to merge 3 commits into
apache:masterfrom
POSSIBLEwbwb:timezone-hour-minute

Conversation

@POSSIBLEwbwb

Copy link
Copy Markdown

feature Add Trino-compatible timezone_hour and timezone_minute functions

Issue: #48203

Purpose

Add two new scalar functions timezone_hour(timestamp_tz) and timezone_minute(timestamp_tz)
for Trino compatibility. They return the hour / minute component of the timezone offset
of the session timezone at the given instant (DST-aware), consistent with Trino semantics:

  • timezone_hour(timestamp '2024-01-01 00:00:00+08:00') -> 8
  • timezone_hour(timestamp '2024-07-01 00:00:00+00:00') with session tz America/New_York -> -4
  • timezone_minute(timestamp '2024-01-01 00:00:00-04:30') -> -30

Changes

BE

  • be/src/exprs/function/function_timezone_hour_minute.cpp (new)
    • FunctionTimezoneHour / FunctionTimezoneMinute: read context->state()->timezone_obj(),
      compute offset via TimestampTzValue::utc_offset() (DST-aware), return
      offset / 3600 / (offset % 3600) / 60 as Int64.
  • be/src/exprs/function/simple_function_factory.h
    • register both functions.

FE

  • fe/fe-core/.../nereids/trees/expressions/functions/scalar/TimezoneHour.java (new)
  • fe/fe-core/.../nereids/trees/expressions/functions/scalar/TimezoneMinute.java (new)
    • UnaryExpression, ExplicitlyCastableSignature, signature BIGINT <- TIMESTAMP_TZ(WILDCARD),
      PropagateNullable (same pattern as TimeToSec).
  • fe/fe-core/.../nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
    • visit methods.
  • fe/fe-core/.../catalog/BuiltinScalarFunctions.java
    • registration of timezone_hour / timezone_minute.

Regression

  • pytest/qe/palo2/src/test_query_datetime_function.py
    • test_query_timezone_hour_minute: fixed-offset (Asia/Shanghai),
      DST (America/New_York), fractional offsets (-04:30, +05:45), nullable input.

Test plan

  • mvn package -DskipTests -Dskip.doc=true -T 4 -pl fe-core -am — BUILD SUCCESS, checkstyle clean
  • BE unit test function_timezone_hour_minute_test (fixed offset / DST / fractional / nullable)
  • pytest regression test_query_datetime_function.py

License

This contribution is licensed under the Apache License 2.0.

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

@linrrzqqq

Copy link
Copy Markdown
Collaborator

/review

@linrrzqqq linrrzqqq self-assigned this Aug 18, 2026

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

Request changes. The review converged after three full rounds with three blocking correctness/test findings, annotated inline.

Checkpoint conclusions:

  • Goal and data correctness: not achieved. The implementation is advertised as Trino-compatible but extracts the session zone after Doris has discarded the input value's zone.
  • Scope, clarity, conditions, and reuse: the FE/BE registration changes are otherwise focused and symmetric, and the const/nullable wrappers are safe; resolving the semantic contract may require a broader representation change or explicit rescoping.
  • Concurrency and lifecycle: the functions add no shared mutable state, locks, ownership cycle, or static-initialization dependency. The unsafe reusable-plan and persisted-MV lifecycles are covered by the second finding.
  • Configuration: no new option is added. Existing time_zone changes reach ordinary distributed and non-folded point-query execution, and SQL result caching keys it correctly; supported BE folding exposes the stale prepared-plan path.
  • Compatibility and parallel paths: FE binding, BIGINT/nullable typing, visitor translation, BE lookup, and ordinary timezone forwarding agree. No storage format or protocol field is added, and no distinct rolling-upgrade defect beyond normal new-builtin availability was found; prepared short-circuit and both MV families remain unsafe.
  • Tests and results: the BE unit test covers fixed, DST, fractional, and nullable session-offset arithmetic, but not the advertised input-zone semantics or the prescribed all-const combinations. The added Python test discards each SET before its SELECT, omits the claimed fractional/nullable end-to-end cases, and there is no standard regression-test result. No matching documentation PR was linked or found. Per the review-task contract, I did not run builds or tests.
  • Observability, transactions, persistence, and writes: no new logging/metrics or base-table transaction/EditLog path is needed; the persisted materialized-view consequence is covered by the second finding.
  • FE/BE variables and performance: no new transmitted variable is introduced, and all ordinary send paths for the existing timezone were checked. Per-row named-zone lookup is needed for DST; no separate CPU or memory issue was substantiated.
  • Other issues and user focus: no additional focus point was provided, and the final missed-issue sweep found no further distinct defect.

auto& result_data = result_column->get_data();
result_data.resize(input_rows_count);

const cctz::time_zone& timezone = context->state()->timezone_obj();

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.

[P1] Extract the input value's zone, not the session zone

Trino's timestamp with time zone retains a zone key, and timezone_hour/timezone_minute extract that value's offset. Doris converts an explicit input zone to UTC and discards it, then this line substitutes the session zone. For example, with session +08:00, CAST('2024-01-15 12:00:00-04:30' AS TIMESTAMPTZ) returns 8/0 here instead of Trino's -4/-30. That silently breaks the advertised migration compatibility. Please resolve the contract by retaining/extracting the input zone (including serialization compatibility), or explicitly scope/rename the feature as session-offset extraction, and add an end-to-end case where the input and session zones differ.

/**
* ScalarFunction 'timezone_hour'.
*/
public class TimezoneHour extends ScalarFunction

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.

[P1] Model the session-timezone dependency

Both new functions read session time_zone but inherit isDeterministic() == true. With BE folding enabled, a constant result can be serialized into a reusable prepared point-query plan and remain stale after SET time_zone. The same classification also admits these expressions into synchronous and async materialized views, whose persisted result/rewrite identity does not retain this execution-only variable. Please represent the session dependency so folding, prepared-plan reuse, and MV admission/rewrite all account for it for both classes, and add prepared-query and MV tests across timezone changes.

"""
# UTC+08:00 has no DST, the offset of the session timezone is the same
# for every instant, so timezone_hour always returns 8 here.
runner.init("set time_zone = '+08:00'")

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.

[P2] Apply SET time_zone on the SELECT's connection

runner.init() sends the Doris SET through PaloQE.do_sql(), which opens and closes a connection for that call. get_sql_result() then opens a fresh session, so neither SELECT sees the timezone set above it; with the default +08:00, the New York block returns 8/0, not -5/-4. Use the existing do_set_properties_sql(select_sql, ["set time_zone = ..."]) pattern (and avoid modifying the unrelated MySQL session), then add the claimed fractional/nullable and input-zone-vs-session-zone cases in the standard regression suite.

@POSSIBLEwbwb POSSIBLEwbwb Aug 19, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the thorough review! All three blocking findings are addressed. The branch now contains:

Commit aee93c9b — the three fixes:

[P1] Input-zone semantics — I resolved the contract by making the session-zone semantics explicit (your "explicitly scope" option): a TIMESTAMPTZ value in Doris stores only the UTC instant — the input zone is discarded at parse time (TimestampTzValue is 8 bytes of UTC microseconds, be/src/core/value/timestamptz_value.h), so recovering the input zone requires redesigning the storage format, well beyond this function pair. The scoping is now explicit in:

  • FE javadoc on TimezoneHour / TimezoneMinute (session-zone extraction, divergence from Trino noted).
  • BE comment above the offset extraction in function_timezone_hour_minute.cpp.
  • Documentation (en/zh) with a divergence example: input -04:30, session +08:00 → returns 8/0; Trino would return -4/-30.
  • End-to-end pytest case with differing input/session zones, and BE unit tests const_input + session_zone_wins_over_input_zone.

[P1] Session-timezone dependency (determinism) — modeled on both engines:

  • FE: isDeterministic() overridden to false in TimezoneHour/TimezoneMinute, which sets StatementContext.hasNondeterministic during analysis (same mechanism as now()/current_date()): the statement is excluded from SQL cache, and the expression is not folded into reusable prepared plans or admitted into MV expressions.
  • BE: "timezone_hour"/"timezone_minute" added to NON_DETERMINISTIC_FUNCTIONS in vectorized_fn_call.cpp, so BE-side constant folding skips them too.

[P2] pytest connectiontest_query_timezone_hour_minute rewritten to use do_set_properties_sql, so each SET time_zone and its SELECT run on one connection (this is the established pattern, e.g. test_query_union_join.py). Cases: fixed offset (+08:00), America/New_York winter/summer (DST), fractional (Asia/Kolkata 5/30), input-zone-vs-session-zone divergence (-04:30 input, +08:00 session), and NULL input.

Commit 77bbf781 — robustness: the BE executor now unwraps nullable before const so the column reaches the plain ColumnTimeStampTz data regardless of wrapper order. (Const columns are only legal at the top level of a column tree — Doris enforces this — so the reachable combinations are plain / top-level const / top-level nullable, all covered by the unit tests.)

Verification (local):

  • BE unit tests: 6 FunctionTimezoneHourMinuteTest cases pass against the ASAN UT build (fixed, DST, fractional, const input, session-vs-input zone, nullable).
  • FE compiles cleanly (fe-core, JDK 17 / Maven 3.9).
  • The pytest case was run against a local single-node cluster earlier in this PR's development.

Honest caveats:

  • I did not add prepared-query / MV regression tests: the palo2 pytest suite has no prepared/MV infrastructure, and I have no local regression-framework environment to validate a new .groovy suite (I prefer not to submit unvalidated tests). The determinism fix uses the same standard mechanism as existing non-deterministic builtins; if you'd like, I can follow up with a prepared_stmt_p0 / mv_p0 groovy case once I can run the regression framework locally.
  • The matching documentation update is in [docs](function) add timezone_hour and timezone_minute function docs doris-website#4070 (pushed; not linked in the PR body since I have no token to comment from here).

@POSSIBLEwbwb

Copy link
Copy Markdown
Author

@linrrzqqq Thanks for triggering the first review! I've pushed aee93c9 and
77bbf78 addressing all three blocking findings (session-zone semantics
explicitly scoped, determinism fixed on both FE and BE, pytest SET/connection
fixed).

@linrrzqqq

Copy link
Copy Markdown
Collaborator

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: All Codex review accounts are usage-limited; earliest retry is 2026-08-20T03:35:00Z.
Workflow run: https://github.com/apache/doris/actions/runs/32209706914

Please trigger /review again after that time.

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.

3 participants