From 869d802c4886357f15f79478184b60397c34f56c Mon Sep 17 00:00:00 2001 From: Kevin McPhillips Date: Wed, 3 Apr 2024 11:32:24 -0400 Subject: [PATCH 1/2] Raise a descriptive error if the MySQL adapter fails to parse the version string. --- activerecord/CHANGELOG.md | 4 +++ .../abstract_mysql_adapter.rb | 8 +++-- activerecord/lib/active_record/errors.rb | 5 +++ .../abstract_mysql_adapter/connection_test.rb | 33 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 9c946097f2..75195bbc8a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Raise an `ActiveRecord::ActiveRecordError` error when the MySQL database returns an invalid version string. + + *Kevin McPhillips* + * `ActiveRecord::Base.transaction` now yields an `ActiveRecord::Transation` object. This allows to register callbacks on it. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 63ce81d169..8d1e6318bf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -680,7 +680,7 @@ def build_insert_sql(insert) # :nodoc: def check_version # :nodoc: if database_version < "5.5.8" - raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." + raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." end end @@ -1023,7 +1023,11 @@ def mismatched_foreign_key(message, sql:, binds:, connection_pool:) end def version_string(full_version_string) - full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] + if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/) + matches[1] + else + raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}" + end end end end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 8d58b2155d..5864a40026 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -575,4 +575,9 @@ class ConnectionFailed < QueryAborted # values, such as request parameters or model attributes to query methods. class UnknownAttributeReference < ActiveRecordError end + + # DatabaseVersionError will be raised when the database version is not supported, or when + # the database version cannot be determined. + class DatabaseVersionError < ActiveRecordError + end end diff --git a/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb b/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb index 7203efcde7..14e5b43b84 100644 --- a/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb +++ b/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb @@ -216,6 +216,39 @@ def test_release_non_existent_advisory_lock "expected release_advisory_lock to return false when there was no lock to release" end + def test_version_string + @connection.stub(:get_full_version, "8.0.35-0ubuntu0.22.04.1") do + assert_equal "8.0.35", @connection.get_database_version.to_s + end + + @connection.stub(:get_full_version, "5.7.0") do + assert_equal "5.7.0", @connection.get_database_version.to_s + end + end + + def test_version_string_invalid + @connection.stub(:get_full_version, "some-database-proxy") do + error = assert_raises(ActiveRecord::DatabaseVersionError) do + @connection.get_database_version + end + assert_equal "Unable to parse MySQL version from \"some-database-proxy\"", error.message + end + + @connection.stub(:get_full_version, "") do + error = assert_raises(ActiveRecord::DatabaseVersionError) do + @connection.get_database_version + end + assert_equal "Unable to parse MySQL version from \"\"", error.message + end + + @connection.stub(:get_full_version, nil) do + error = assert_raises(ActiveRecord::DatabaseVersionError) do + @connection.get_database_version + end + assert_equal "Unable to parse MySQL version from nil", error.message + end + end + private def cause_server_side_disconnect @connection.update("set @@wait_timeout=1") From d9995cc00cde82d0ed9cdedc9d9ff9e4440a19fb Mon Sep 17 00:00:00 2001 From: Kevin McPhillips Date: Wed, 3 Apr 2024 14:21:19 -0400 Subject: [PATCH 2/2] Add test for MariaDB 5.5.5- prefix in db version parsing regex. --- .../adapters/abstract_mysql_adapter/connection_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb b/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb index 14e5b43b84..9e58543c3f 100644 --- a/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb +++ b/activerecord/test/cases/adapters/abstract_mysql_adapter/connection_test.rb @@ -226,6 +226,12 @@ def test_version_string end end + def test_version_string_with_mariadb + @connection.stub(:get_full_version, "5.5.5-10.6.5-MariaDB-1:10.6.5+maria~focal") do + assert_equal "10.6.5", @connection.get_database_version.to_s + end + end + def test_version_string_invalid @connection.stub(:get_full_version, "some-database-proxy") do error = assert_raises(ActiveRecord::DatabaseVersionError) do