Skip to content

[Bug] Arrow Flight SQL: UTC TIMESTAMPTZ is tagged with the non-portable Arrow timezone "Z" #67370

Open

Description

@morningman

Search before asking

  • I had searched in the issues and found no similar issues.

Version

Apache Doris 4.1.3-rc02, commit 31263df4dc1d4d3a27517d264802cd4d6b92c874

Client: Python + ADBC Flight SQL driver (adbc_driver_flightsql), FE arrow_flight_sql_port = 41070.
The MySQL/JDBC protocol is used as the control path for comparison.

What's Wrong?

When reading a mapped Iceberg TIMESTAMPTZ column with the session time_zone set to UTC, Doris Flight SQL returns the Arrow type timestamp[s, tz=Z].

PyArrow treats the timezone string as a zoneinfo key, so to_pylist() raises ZoneInfoNotFoundError because Z is not a valid IANA time zone name. The underlying epoch values themselves are correct: [1735704000, null, 939528000, null]. Rewriting only the schema metadata from Z to UTC leaves the epochs unchanged and makes the conversion succeed.

What You Expected?

UTC timezone metadata should use a name that PyArrow / zoneinfo can resolve (e.g. UTC, or a fixed +00:00 offset), so that to_pylist() returns UTC datetimes directly.

How to Reproduce?

  1. Bring up the Iceberg regression fixture and create a catalog with "enable.mapping.timestamp_tz" = "true".
  2. SWITCH to that catalog, USE test_timestamp_tz, and set the session time_zone to UTC.
  3. Read test_ice_timestamp_tz_orc over Python ADBC Flight SQL.
  4. Inspect the Arrow schema and call to_pylist().
SWITCH test_iceberg_timestamp_tz_with_mapping;
USE test_timestamp_tz;
SET time_zone = 'UTC';
SELECT * FROM test_ice_timestamp_tz_orc ORDER BY id;

Client side:

import adbc_driver_flightsql.dbapi as flight_sql

conn = flight_sql.connect(uri="grpc://127.0.0.1:41070",
                          db_kwargs={"username": "root", "password": ""})
cur = conn.cursor()
cur.execute("SET time_zone = 'UTC'")
cur.execute("SELECT * FROM test_iceberg_timestamp_tz_with_mapping."
            "test_timestamp_tz.test_ice_timestamp_tz_orc ORDER BY id")
table = cur.fetch_arrow_table()
print(table.schema)       # timestamp[s, tz=Z]
print(table.to_pylist())  # ZoneInfoNotFoundError

Anything Else?

Z is valid ISO-8601 offset syntax but is not a portable Arrow timezone string; the Arrow spec expects either an IANA name or a fixed +HH:MM offset. The Doris session time_zone value is apparently passed through to the Arrow field metadata verbatim.

Workaround: on the client, cast only the Arrow timezone metadata from Z to UTC before converting to Python; this does not change the epoch values. Casting to STRING in SQL also works, but loses the native TIMESTAMPTZ Arrow type.

Found with the external_table_p0/iceberg/test_iceberg_timestamp_tz fixture and confirmed with a raw Arrow probe plus a metadata-only cast.

Tracking issue: #65615

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Activity

  1. Doris-Breakwater commented on Sep 1, 2026

    @Doris-Breakwater
    Collaborator

    Breakwater-GitHub-Analysis-Slot: slot_71f66e5c4174

    Initial maintainer assessment

    Disposition: Accept as an Arrow Flight SQL interoperability bug. The report contains a complete version and reproduction, and the epoch values plus metadata-only workaround strongly indicate a schema-annotation problem rather than TIMESTAMPTZ value corruption. The impact is still user-visible: a standard Python Arrow conversion fails even though the query data is otherwise correct.

    The issue is currently unlabeled and unassigned. Suggested labels are kind/bug and area/rpc; the Iceberg fixture exposes the problem, but the failing code path is the generic Flight SQL Arrow schema builder rather than Iceberg decoding.

    Code-path findings at 31263df4dc1d4d3a27517d264802cd4d6b92c874

    Verified facts:

    1. The FE fetches the schema generated by the result BE and forwards that schema in FlightInfo; it does not reconstruct the TIMESTAMPTZ field itself (FlightSqlConnectProcessor.java, DorisFlightSqlProducer.java).
    2. The BE result sink passes RuntimeState::timezone() into get_arrow_schema_from_expr_ctxs() (result_sink_operator.cpp). For TYPE_TIMESTAMPTZ, convert_to_arrow_type() constructs arrow::TimestampType(time_unit, timezone) with that string unchanged (arrow_row_batch.cpp). There is no Arrow-specific validation or normalization at this boundary.
    3. The FE query coordinator canonicalizes the session timezone using ZoneId.of(...).getId() before putting it in TQueryGlobals (Coordinator.java, TimeUtils.java). Java canonicalization returns Z for the valid Doris fixed-offset session value +00:00. Doris BE also recognizes Z as Zulu, so that identifier can reach schema construction successfully.
    4. Arrow timestamp timezone metadata permits a tz-database name or a numeric +XX:XX/-XX:XX offset. Z is neither form. Consequently, emitting timestamp[..., tz=Z] is non-portable and PyArrow's ZoneInfoNotFoundError is consistent with the schema (Arrow timestamp format contract).

    This verifies the root-cause boundary: a timezone identifier valid within Doris/Java can be copied into an Arrow schema even when it is not a valid Arrow timezone identifier. It also proves a deterministic failure path for a session timezone of +00:00.

    One detail remains unverified: at this exact commit, TimeUtils.getCanonicalTimeZone("UTC") evaluates to UTC, not Z. Therefore the report's literal SET time_zone = 'UTC' should not be assumed to be the precise origin of Z without observing the effective value on the same Flight session. This discrepancy does not invalidate the reported server output or the generic bug, but it matters for identifying every upstream trigger and choosing regression coverage.

    Missing evidence for the exact UTC -> Z trigger

    Please add the following from the same ADBC connection and cursor, immediately before the failing query:

    1. The fetched result of SHOW VARIABLES LIKE 'time_zone' (not only confirmation that SET returned successfully).
    2. repr(table.schema.field(<timestamptz-column>).type.tz) for a small matrix of session values: UTC, +00:00, and Asia/Shanghai.
    3. Exact pyarrow, adbc_driver_flightsql, and Python versions. These are not needed to accept the server-side metadata bug, but they make the client failure reproducible.
    4. FE and participating BE version/commit output, to rule out a mixed-version cluster if the effective session value is confirmed as UTC while the wire schema still contains Z.

    A Doris execution profile is not needed for this issue; this is schema construction, not a performance or operator-timing problem.

    Recommended next steps

    1. Normalize Arrow timezone metadata at the Arrow schema-construction boundary, rather than changing Doris's general execution-time timezone representation. At minimum, map the zero-offset identifier Z to the portable UTC (or +00:00) while leaving epoch buffers unchanged and preserving valid IANA names/non-zero fixed offsets.
    2. Add a focused BE unit test around TYPE_TIMESTAMPTZ Arrow type construction covering UTC, Z, +00:00, a non-zero offset, and an IANA zone. Because conversion is recursive, include at least one nested TIMESTAMPTZ case.
    3. Add an end-to-end Flight SQL/ADBC regression for both SET time_zone = 'UTC' and SET time_zone = '+00:00' that asserts the wire-schema timezone and calls to_pylist() on nullable TIMESTAMPTZ values. Existing Flight SQL coverage checks JDBC type identity but does not assert timezone metadata portability.
    4. Link the eventual fix and regression to tracking issue [Tracking] Arrow Flight SQL and ADBC Catalog: issues and improvements #65615.
  2. Doris-Breakwater commented on Sep 1, 2026

    @Doris-Breakwater
    Collaborator

    Breakwater-GitHub-Analysis-Slot: slot_71f66e5c4174

    Initial maintainer assessment

    Disposition: Accept as an Arrow Flight SQL interoperability bug. The report contains a complete version and reproduction, and the epoch values plus metadata-only workaround strongly indicate a schema-annotation problem rather than TIMESTAMPTZ value corruption. The impact is still user-visible: a standard Python Arrow conversion fails even though the query data is otherwise correct.

    The issue is currently unlabeled and unassigned. Suggested labels are kind/bug and area/rpc; the Iceberg fixture exposes the problem, but the failing code path is the generic Flight SQL Arrow schema builder rather than Iceberg decoding.

    Code-path findings at 31263df4dc1d4d3a27517d264802cd4d6b92c874

    Verified facts:

    1. The FE fetches the schema generated by the result BE and forwards that schema in FlightInfo; it does not reconstruct the TIMESTAMPTZ field itself (FlightSqlConnectProcessor.java, DorisFlightSqlProducer.java).
    2. The BE result sink passes RuntimeState::timezone() into get_arrow_schema_from_expr_ctxs() (result_sink_operator.cpp). For TYPE_TIMESTAMPTZ, convert_to_arrow_type() constructs arrow::TimestampType(time_unit, timezone) with that string unchanged (arrow_row_batch.cpp). There is no Arrow-specific validation or normalization at this boundary.
    3. TIMESTAMPTZ payload encoding is independent of that metadata string: DataTypeTimeStampTzSerDe::write_column_to_arrow() converts every non-null value to a Unix epoch using cctz::utc_time_zone() (data_type_timestamptz_serde.cpp). This matches the report's correct raw epochs and supports treating the defect as metadata-only.
    4. The FE query coordinator canonicalizes the session timezone using ZoneId.of(...).getId() before putting it in TQueryGlobals (Coordinator.java, TimeUtils.java). Java canonicalization returns Z for the valid Doris fixed-offset session value +00:00. Doris BE also recognizes Z as Zulu, so that identifier can reach schema construction successfully.
    5. Arrow timestamp timezone metadata permits a tz-database name or a numeric +XX:XX/-XX:XX offset. Z is neither form. Consequently, emitting timestamp[..., tz=Z] is non-portable and PyArrow's ZoneInfoNotFoundError is consistent with the schema (Arrow timestamp format contract).

    This verifies the root-cause boundary: a timezone identifier valid within Doris/Java can be copied into an Arrow schema even when it is not a valid Arrow timezone identifier. It also proves a deterministic failure path for a session timezone of +00:00.

    One detail remains unverified: at this exact commit, TimeUtils.getCanonicalTimeZone("UTC") evaluates to UTC, not Z. Therefore the report's literal SET time_zone = 'UTC' should not be assumed to be the precise origin of Z without observing the effective value on the same Flight session. This discrepancy does not invalidate the reported server output or the generic bug, but it matters for identifying every upstream trigger and choosing regression coverage.

    Missing evidence for the exact UTC -> Z trigger

    Please add the following from the same ADBC connection and cursor, immediately before the failing query:

    1. The fetched result of SHOW VARIABLES LIKE 'time_zone' (not only confirmation that SET returned successfully).
    2. repr(table.schema.field(<timestamptz-column>).type.tz) for a small matrix of session values: UTC, +00:00, and Asia/Shanghai.
    3. Exact pyarrow, adbc_driver_flightsql, and Python versions. These are not needed to accept the server-side metadata bug, but they make the client failure reproducible.
    4. FE and participating BE version/commit output, to rule out a mixed-version cluster if the effective session value is confirmed as UTC while the wire schema still contains Z.

    A Doris execution profile is not needed for this issue; this is schema construction, not a performance or operator-timing problem.

    Recommended next steps

    1. Normalize Arrow timezone metadata at the Arrow schema-construction boundary, rather than changing Doris's general execution-time timezone representation. At minimum, map the zero-offset identifier Z to the portable UTC (or +00:00) while leaving epoch buffers unchanged and preserving valid IANA names/non-zero fixed offsets.
    2. Add a focused BE unit test around TYPE_TIMESTAMPTZ Arrow type construction covering UTC, Z, +00:00, a non-zero offset, and an IANA zone. Because conversion is recursive, include at least one nested TIMESTAMPTZ case.
    3. Add an end-to-end Flight SQL/ADBC regression for both SET time_zone = 'UTC' and SET time_zone = '+00:00' that asserts the wire-schema timezone and calls to_pylist() on nullable TIMESTAMPTZ values. Existing Flight SQL coverage checks JDBC type identity but does not assert timezone metadata portability.
    4. Link the eventual fix and regression to tracking issue [Tracking] Arrow Flight SQL and ADBC Catalog: issues and improvements #65615.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions