Skip to content

fix(bigquery-jdbc): preserve the instant on the TIMESTAMP write path and bind LocalDateTime as STRING - #14647

Open
Neenu1995 wants to merge 7 commits into
mainfrom
fix-temporal-offset-drop
Open

Neenu1995 wants to merge 7 commits into
mainfrom
fix-temporal-offset-drop

Conversation

@Neenu1995

@Neenu1995 Neenu1995 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes b/565785268 (setTimestamp drops the offset), b/565786188 (setObject(Instant / OffsetDateTime / ZonedDateTime) discards the zone), b/566151602 (setObject(LocalDateTime)
can't be inserted into DATETIME).

Root cause

TIMESTAMP parameters were rendered with Timestamp.toString(): JVM-local digits, no zone label.
BigQuery reads an unlabelled string as UTC, so on any non-UTC JVM the stored moment moved by the
JVM offset. Zone-aware types were collapsed to Timestamp first and lost their zone the same way.

setObject(LocalDateTime) went through Timestamp.valueOf and was bound as a TIMESTAMP
parameter, which BigQuery refuses to insert into a DATETIME column. It looked fine on main
only because interpret-in-JVM-zone and print-in-JVM-zone cancelled; fixing the formatter alone
would have broken that cancellation and shifted LocalDateTime digits, so both land together.

Fix

  • BigQueryParameterHandler: every instant-carrying type in a TIMESTAMP slot is reduced to
    its Instant and rendered in UTC with a trailing Z. UTC (not the caller's offset) because the
    client-library validator rejects -05:00 but accepts Z. DATETIME slots unchanged.
  • BigQueryPreparedStatement.setTemporalObject: LocalDateTime is bound as a STRING of
    fixed-width digits. Chosen over a typed DATETIME binding to avoid a breaking change — STRING
    is accepted by both DATETIME and TIMESTAMP columns.

Behaviour change (JVM = America/New_York)

Call Before After
setTimestamp / setObject(Timestamp|Instant|OffsetDateTime|ZonedDateTime) moment shifted −5 h exact instant
setTimestamp(ts, cal) shifted by Calendar + JVM exact instant
getTimestamp → setTimestamp round trip moves on every hop identical
setObject(LocalDateTime) → INSERT into DATETIME rejected (type TIMESTAMP) inserted, digits unchanged
setObject(LocalDateTime) → INSERT into TIMESTAMP digits read as UTC unchanged

No change to DATE, TIME, DATETIME formatting or any read path.

Tests

  • Unit : instant preserved across UTC/NY/Kolkata; zone-aware types render
    identically; output passes QueryParameterValue's validator; DATETIME slot unchanged;
    LocalDateTime binds as STRING with zone-free, fixed-width digits.
  • IT (ITTimestampTimeZoneTest, new, 4 JVM zones): all write-path cases above, including real
    INSERTs into TIMESTAMP and DATETIME. Read-path cases for b/565791346, b/563395776 and
    b/565818805 are included but @Disabled for the follow-up PR.

Follow-ups

  • Read path: b/565791346 (Calendar getters), b/563395776 (getString), b/565818888 (TIMESTAMP(12)).
  • b/565818805 setTime(t, cal) direction — unmeasured here.
  • CallableStatement.setObject(String, LocalDateTime) and the Storage Write API batch path do
    not go through this formatter — not changed.
  • Typed DATETIME binding for LocalDateTime is a deliberate behaviour change to decide separately.

@Neenu1995
Neenu1995 requested review from a team as code owners October 13, 2026 17:31

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors temporal parameter handling in the BigQuery JDBC driver to ensure timezone-independent formatting. It formats TIMESTAMP parameters in UTC with a trailing 'Z' and binds LocalDateTime as a STRING to prevent breaking changes, supported by comprehensive unit and integration tests. The review feedback highlights a critical improvement to specify Locale.ROOT when creating DateTimeFormatter instances to prevent formatting issues in environments with non-Gregorian default locales. Additionally, it suggests formatting java.util.Date using the UTC instant formatter to avoid malformed string fallbacks.

@Neenu1995
Neenu1995 marked this pull request as draft October 13, 2026 17:35
@Neenu1995
Neenu1995 marked this pull request as ready for review October 13, 2026 18:10

This branch has not been deployed

No deployments
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.

1 participant