Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/arjdbc/postgresql/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ def set_client_encoding(encoding)
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
# This is called on `connection.connect` and should not be called manually.
def configure_connection
# Record the zone state first: the statements below re-enter
# #internal_exec_query -> #update_typemap_for_default_timezone.
# (Not @default_timezone: Rails treats that as a per-connection override.)
@configured_default_timezone = ActiveRecord.default_timezone
@configured_local_tz = @configured_default_timezone == :local ? local_tz : nil

#if encoding = config[:encoding]
# The client_encoding setting is set by the driver and should not be altered.
# If the driver detects a change it will abort the connection.
Expand Down Expand Up @@ -532,6 +538,13 @@ def execute_batch(statements, name = nil)
execute(combine_multi_statements(statements), name)
end

# ActiveRecord's query path goes through internal_exec_query, not
# exec_no_cache/exec_cache, so pick up default_timezone changes here.
def internal_exec_query(...)
update_typemap_for_default_timezone
super
end

# from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
:close, :declare, :fetch, :move, :set, :show
Expand Down Expand Up @@ -794,7 +807,8 @@ def extract_pg_identifier_from_name(name)
end

def local_tz
@local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
# not memoized: the process zone can change at runtime (ENV['TZ'] / Java default)
java.util.TimeZone.getDefault.getID
end

def bind_params_length
Expand Down
19 changes: 6 additions & 13 deletions lib/arjdbc/postgresql/oid_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,21 +231,14 @@ def load_types_queries(initializer, oids)
end
end

# The JDBC driver returns timestamps in the session time zone set by
# #configure_connection, so when ActiveRecord.default_timezone (or the
# process zone, while :local) changes at runtime, re-run it.
def update_typemap_for_default_timezone
if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
decoder_class = ActiveRecord.default_timezone == :utc ?
PG::TextDecoder::TimestampUtc :
PG::TextDecoder::TimestampWithoutTimeZone
local = ActiveRecord.default_timezone == :local ? local_tz : nil
return if @configured_default_timezone == ActiveRecord.default_timezone && @configured_local_tz == local

@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
@connection.type_map_for_results.add_coder(@timestamp_decoder)

@default_timezone = ActiveRecord.default_timezone

# if default timezone has changed, we need to reconfigure the connection
# (specifically, the session time zone)
configure_connection
end
configure_connection if @raw_connection
end

def extract_scale(sql_type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ protected void setTimestampParameter(final ThreadContext context,
final int index, IRubyObject value,
final IRubyObject attribute, final int type) throws SQLException {
// PGJDBC uses strings internally anyway, so using Timestamp doesn't do any good
String tsString = PgDateTimeUtils.timestampValueToString(context, value, null, true);
String tsString = PgDateTimeUtils.timestampValueToString(context, value, getDefaultTimeZone(context), true);
statement.setObject(index, tsString, Types.OTHER);
}

Expand Down
42 changes: 42 additions & 0 deletions test/db/postgresql/default_timezone_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'test_helper'
require 'db/postgres'

class PostgresDefaultTimezoneTest < Test::Unit::TestCase

def setup
@orig_tz = java.util.TimeZone.getDefault
@orig_default = ActiveRecord.default_timezone
end

def teardown
java.util.TimeZone.setDefault(@orig_tz)
ActiveRecord.default_timezone = @orig_default
connection.select_value('SELECT 1') # re-sync session zone
end

def test_session_zone_follows_default_timezone_at_runtime
ActiveRecord.default_timezone = :utc
assert_equal 'UTC', session_zone

ActiveRecord.default_timezone = :local
set_process_zone 'Asia/Tokyo'
assert_equal 'Asia/Tokyo', session_zone

# symbol stays :local, only the zone changes
set_process_zone 'America/New_York'
assert_equal 'America/New_York', session_zone

ActiveRecord.default_timezone = :utc
assert_equal 'UTC', session_zone
end

private

def connection; ActiveRecord::Base.connection; end

def session_zone; connection.select_value('SHOW TIME ZONE'); end

def set_process_zone(id)
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone(id))
end
end
Loading