From b31ac661466a288ce62a013eaa77a22a705db80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ja=CC=84nis=20Baiz=CC=8Ca?= Date: Tue, 22 Oct 2024 13:03:57 +0300 Subject: [PATCH 1/5] Catch HOUR_OF_DAY error If the time stored in the missing daylight saving hour, MySQL JDBC driver starting from version 8.0.23 will raise this error (https://dev.mysql.com/doc/relnotes/connector-j/en/news-8-0-23.html) If the date time value stored in the column actually is UTC time, then will return string representation of the value, not the time converted to the local time zone. --- .../arjdbc/mysql/MySQLRubyJdbcConnection.java | 33 +++++++++++++++++ test/simple.rb | 35 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java b/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java index 140f722ee..4b2f4fa0d 100644 --- a/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +++ b/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java @@ -217,6 +217,39 @@ protected IRubyObject timeToRuby(final ThreadContext context, return DateTimeUtils.newDummyTime(context, value, getDefaultTimeZone(context)); } + @Override + protected IRubyObject timestampToRuby(final ThreadContext context, + final Ruby runtime, final ResultSet resultSet, final int column) + throws SQLException { + + final Timestamp value; + try { + value = resultSet.getTimestamp(column); + } + catch (SQLException e) { + if (e.getMessage().contains("HOUR_OF_DAY")) { + return stringToRuby(context, runtime, resultSet, column); + } + else { + throw e; + } + } + if ( value == null ) { + return resultSet.wasNull() ? context.nil : RubyString.newEmptyString(runtime); + } + + if ( rawDateTime != null && rawDateTime) { + return RubyString.newString(runtime, DateTimeUtils.timestampToString(value)); + } + + // NOTE: with 'raw' String AR's Type::DateTime does put the time in proper time-zone + // while when returning a Time object it just adjusts usec (apply_seconds_precision) + // yet for custom SELECTs to work (SELECT created_at ... ) and for compatibility we + // should be returning Time (by default) - AR does this by adjusting mysql2/pg returns + + return DateTimeUtils.newTime(context, value, getDefaultTimeZone(context)); + } + @Override protected IRubyObject streamToRuby(final ThreadContext context, final Ruby runtime, final ResultSet resultSet, final int column) diff --git a/test/simple.rb b/test/simple.rb index b08d20eaa..2582e6c57 100644 --- a/test/simple.rb +++ b/test/simple.rb @@ -321,7 +321,42 @@ def test_time_with_default_timezone_local end end end + end + + def test_time_in_dst_change_hour_utc + with_system_tz 'Europe/Prague' do + Time.use_zone 'Europe/Prague' do + with_timezone_config default: :utc do + id = DbType.connection.insert( + "INSERT INTO db_types (sample_datetime) + values ('2024-03-31 03:30:00')" + ) + saved_time = DbType.find(id).sample_datetime + + assert_equal Time.utc(2024, 3, 31, 3, 30), saved_time + assert_equal 'UTC', saved_time.zone + end + end + end + end + + def test_time_in_dst_change_hour_local + skip "with_system_tz not working in tomcat" if ActiveRecord::Base.connection.raw_connection.jndi? + + with_system_tz 'Europe/Prague' do + Time.use_zone 'Europe/Prague' do + with_timezone_config default: :local do + id = DbType.connection.insert( + "INSERT INTO db_types (sample_datetime) + values ('2024-03-31 02:30:00')" + ) + saved_time = DbType.find(id).sample_datetime + assert_equal Time.local(2024, 3, 31, 1, 30), saved_time + assert_not_equal 'UTC', saved_time.zone + end + end + end end # From 856ad9ca1fd0edf9a1a74a38f5f3108a1b67a865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ja=CC=84nis=20Baiz=CC=8Ca?= Date: Wed, 23 Oct 2024 11:55:02 +0300 Subject: [PATCH 2/5] In tests recreate DB connection with actual time zone When time zone is changed, JDBC connection still will contain initial time zone. Therefore we need to recreate connection with the newly set TZ --- test/simple.rb | 6 +++--- test/test_helper.rb | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/test/simple.rb b/test/simple.rb index 2582e6c57..51bb86561 100644 --- a/test/simple.rb +++ b/test/simple.rb @@ -329,11 +329,11 @@ def test_time_in_dst_change_hour_utc with_timezone_config default: :utc do id = DbType.connection.insert( "INSERT INTO db_types (sample_datetime) - values ('2024-03-31 03:30:00')" + values ('2024-03-31 02:30:00')" ) saved_time = DbType.find(id).sample_datetime - assert_equal Time.utc(2024, 3, 31, 3, 30), saved_time + assert_equal Time.utc(2024, 3, 31, 2, 30), saved_time assert_equal 'UTC', saved_time.zone end end @@ -352,7 +352,7 @@ def test_time_in_dst_change_hour_local ) saved_time = DbType.find(id).sample_datetime - assert_equal Time.local(2024, 3, 31, 1, 30), saved_time + assert_equal Time.local(2024, 3, 31, 3, 30), saved_time assert_not_equal 'UTC', saved_time.zone end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 178b17e4f..9114d3718 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -330,11 +330,18 @@ def with_java_tz(new_tz) java.util.TimeZone.setDefault new_tz org.joda.time.DateTimeZone.setDefault org.joda.time.DateTimeZone.forTimeZone(new_tz) java.lang.System.setProperty 'user.timezone', new_tz.getID + connection_properties = ActiveRecord::Base.remove_connection + prev_config = connection_properties.configuration_hash + new_config = prev_config.deep_dup + new_config[:properties] ||= {} + new_config[:properties]['serverTimezone'] = new_tz.getID + ActiveRecord::Base.establish_connection new_config yield ensure java.util.TimeZone.setDefault old_tz org.joda.time.DateTimeZone.setDefault old_jd old_user_tz ? java.lang.System.setProperty('user.timezone', old_user_tz) : java.lang.System.clearProperty('user.timezone') + ActiveRecord::Base.establish_connection prev_config end end From 7e216de403595ad1a0ab4a33a0d61e60b75bae1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ja=CC=84nis=20Baiz=CC=8Ca?= Date: Wed, 23 Oct 2024 11:56:23 +0300 Subject: [PATCH 3/5] Add missing skip in DST test --- test/simple.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/simple.rb b/test/simple.rb index 51bb86561..491282359 100644 --- a/test/simple.rb +++ b/test/simple.rb @@ -324,6 +324,8 @@ def test_time_with_default_timezone_local end def test_time_in_dst_change_hour_utc + skip "with_system_tz not working in tomcat" if ActiveRecord::Base.connection.raw_connection.jndi? + with_system_tz 'Europe/Prague' do Time.use_zone 'Europe/Prague' do with_timezone_config default: :utc do From c623fe2867d54ebc3025b39ea6ab3babe32474d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ja=CC=84nis=20Baiz=CC=8Ca?= Date: Wed, 23 Oct 2024 11:57:13 +0300 Subject: [PATCH 4/5] Add pending to test in PostgreSQL case With AR default timezone set to :local, PostgreSQL driver will try to convert missing DST time and will fail. --- test/db/postgresql/simple_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/db/postgresql/simple_test.rb b/test/db/postgresql/simple_test.rb index da50c42cd..666773739 100644 --- a/test/db/postgresql/simple_test.rb +++ b/test/db/postgresql/simple_test.rb @@ -79,6 +79,16 @@ def test_big_decimal end end + if Time.respond_to?(:zone) + # @override + def test_time_in_dst_change_hour_local + if ENV['CI'] + pend 'TODO: CI: with AR default timezone set to :local PostgreSQL driver will not convert DST missing hour time' + end + super + end + end + def test_encoding assert_not_nil connection.encoding end From 11361a78d52c109add7db2d5ebd56d458b8bd951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ja=CC=84nis=20Baiz=CC=8Ca?= Date: Wed, 23 Oct 2024 13:38:20 +0300 Subject: [PATCH 5/5] Remove pending test as the time zone will be changed for the DB connection --- test/db/mysql/simple_test.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/db/mysql/simple_test.rb b/test/db/mysql/simple_test.rb index 4a73cb1a8..4245128c2 100644 --- a/test/db/mysql/simple_test.rb +++ b/test/db/mysql/simple_test.rb @@ -115,14 +115,6 @@ def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timez super end - # @override - def test_time_with_default_timezone_local - if ENV['CI'] - pend 'TODO: CI: when we start off with UTC the MySQL driver does not handle the time-zone switch right' - end - super - end - # NOTE: all of the above pends are not crucial # we're really pushing the limits with switching TZ in the system ... esp. for the MySQL driver