Skip to content

PostgreSQL: fix ActiveRecord.default_timezone runtime switching being ignored - #1237

Open
ryudoawaru wants to merge 2 commits into
jruby:masterfrom
ryudoawaru:pg-default-timezone-runtime-switch
Open

ryudoawaru wants to merge 2 commits into
jruby:masterfrom
ryudoawaru:pg-default-timezone-runtime-switch

Conversation

@ryudoawaru

@ryudoawaru ryudoawaru commented Oct 28, 2026 •

Copy link
Copy Markdown

Problem

On master, changing ActiveRecord.default_timezone (or the process zone while :local) at runtime never changes the PostgreSQL session time zone, so the JDBC driver keeps returning timestamps in the zone set at connect time.

Reproduction (:utc -> :local + Asia/Tokyo -> :local + America/New_York -> :utc), SHOW TIME ZONE:

step master this PR
:utc UTC UTC
:local + Asia/Tokyo UTC Asia/Tokyo
:local + America/New_York UTC America/New_York
:utc UTC UTC

Causes and changes

  • update_typemap_for_default_timezone was only called from exec_no_cache/exec_cache, which the ORM path never reaches (it uses internal_exec_query), and was guarded by @timestamp_decoder, which is always nil under JDBC. Now hooked into internal_exec_query; the dead PG::TextDecoder branch is removed.
  • local_tz memoized the first session zone. It now reads Java's default TimeZone live.
  • The guard compared only :utc/:local, so switching between two local zones was missed. It now also compares the resolved zone name.
  • State is recorded at the top of configure_connection (it issues SQL that re-enters internal_exec_query, so this avoids recursion and a redundant first reconfigure). It uses @configured_default_timezone, not @default_timezone: Rails treats the latter as a per-connection override, and pinning it breaks quoting with PREPARED_STATEMENTS=false.
  • setTimestampParameter passes getDefaultTimeZone(context) instead of null, like the sibling date/time parsers. I do not have a test showing this line matters (a bind-parameter test I tried passed with or without it, and cannot run with PREPARED_STATEMENTS=false, so I dropped it). Happy to drop the line if you prefer.

New test: test/db/postgresql/default_timezone_test.rb (fails on master, passes here).

Testing

Local PostgreSQL, JRuby 10.0.0.1, Java 21. Compared against unmodified master:

  • rake test_postgresql, default and PREPARED_STATEMENTS=false: no new failures. The 21 existing errors (PostgresSchemaDumpTest, uninitialized constant PG) are identical on master, which is also why the ARJDBC Postgres CI jobs are red on master (run for Remove special cases for 8.0 support #1226).
  • rake rails:test_postgresql against Rails 8-1-stable, time-related files only (12 files, 489 tests): no new failures in either mode; two Rails tests that failed on master now pass (FinderTest#test_condition_local_time_interpolation_with_default_timezone_utc, FinderTest#test_condition_utc_time_interpolation_with_default_timezone_local).

Not run: the full Rails activerecord suite, MySQL/SQLite3.

🤖 Generated with Claude Code

ryudoawaru and others added 2 commits October 28, 2026 15:01
… ignored

Changing ActiveRecord.default_timezone (or the process zone while :local)
at runtime never updated the PostgreSQL session time zone, so the JDBC
driver kept returning timestamps in the zone set at connect time.

* update_typemap_for_default_timezone was only called from
  exec_no_cache/exec_cache, which the ORM query path never reaches
  (it goes through internal_exec_query), and it was guarded by
  @timestamp_decoder, which is always nil under JDBC. Hook
  internal_exec_query and drop the dead PG::TextDecoder branch.
* local_tz memoized the first session zone; read Java's default
  TimeZone live instead.
* Track the resolved zone name as well as the :utc/:local symbol so a
  switch between two local zones is detected.
* Record the state in #configure_connection before it issues SQL, to
  avoid re-entrant recursion and a redundant first reconfigure. Use
  @configured_default_timezone rather than @default_timezone: Rails
  treats the latter as a per-connection override, and pinning it breaks
  quoting with PREPARED_STATEMENTS=false.
* setTimestampParameter now passes getDefaultTimeZone(context) instead
  of null, like the sibling date/time parsers.

Adds test/db/postgresql/default_timezone_test.rb.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With prepared statements disabled the SQL is sent without binds, so the
`?` placeholder in test_bound_timestamp_keeps_instant_when_local reaches
PostgreSQL verbatim and raises a syntax error. The test also passed with
or without the setTimestampParameter change, so it did not verify it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@ryudoawaru
ryudoawaru marked this pull request as draft October 28, 2026 07:35
@ryudoawaru
ryudoawaru marked this pull request as ready for review October 28, 2026 07:36
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