diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 9c094e7d0f942..ac5b3de682ded 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -390,7 +390,7 @@ def commit_db_transaction() end def rollback_db_transaction exec_rollback_db_transaction rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed - reconnect! + # Connection's gone; that counts as a rollback end def exec_rollback_db_transaction() end # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index f9cbbd6637b6d..44adf0d950f92 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -414,13 +414,6 @@ def materialize_transactions @has_unmaterialized_transactions = false end end - - # As a logical simplification for now, we assume anything that requests - # materialization is about to dirty the transaction. Note this is just - # an assumption about the caller, not a direct property of this method. - # It can go away later when callers are able to handle dirtiness for - # themselves. - dirty_current_transaction end def commit_transaction @@ -446,8 +439,12 @@ def commit_transaction def rollback_transaction(transaction = nil) @connection.lock.synchronize do - transaction ||= @stack.pop - transaction.rollback + transaction ||= @stack.last + begin + transaction.rollback + ensure + @stack.pop if @stack.last == transaction + end transaction.rollback_records end end @@ -502,6 +499,9 @@ def within_new_transaction(isolation: nil, joinable: true) begin commit_transaction + rescue ActiveRecord::ConnectionFailed + transaction.state.invalidate! unless transaction.state.completed? + raise rescue Exception rollback_transaction(transaction) unless transaction.state.completed? raise diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0d7f91e3f8330..6a5551c2e4dcc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -89,34 +89,53 @@ def self.quoted_table_names # :nodoc: @quoted_table_names ||= {} end - def initialize(connection, logger = nil, config = {}) # :nodoc: + def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc: super() - @raw_connection = connection - @owner = nil - @instrumenter = ActiveSupport::Notifications.instrumenter - @logger = logger - @config = config - @pool = ActiveRecord::ConnectionAdapters::NullPool.new - @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @raw_connection = nil + @unconfigured_connection = nil + + if config_or_deprecated_connection.is_a?(Hash) + @config = config_or_deprecated_connection.symbolize_keys + @logger = ActiveRecord::Base.logger + + if deprecated_logger || deprecated_connection_options || deprecated_config + raise ArgumentError, "when initializing an ActiveRecord adapter with a config hash, that should be the only argument" + end + else + # Soft-deprecated for now; we'll probably warn in future. + + @unconfigured_connection = config_or_deprecated_connection + @logger = deprecated_logger || ActiveRecord::Base.logger + if deprecated_config + @config = (deprecated_config || {}).symbolize_keys + @connection_parameters = deprecated_connection_options + else + @config = (deprecated_connection_options || {}).symbolize_keys + @connection_parameters = nil + end + end + + @owner = nil + @instrumenter = ActiveSupport::Notifications.instrumenter + @pool = ActiveRecord::ConnectionAdapters::NullPool.new + @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) @visitor = arel_visitor @statements = build_statement_pool @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new @prepared_statements = self.class.type_cast_config_to_boolean( - config.fetch(:prepared_statements, true) + @config.fetch(:prepared_statements, true) ) @advisory_locks_enabled = self.class.type_cast_config_to_boolean( - config.fetch(:advisory_locks, true) + @config.fetch(:advisory_locks, true) ) - @default_timezone = self.class.validate_default_timezone(config[:default_timezone]) + @default_timezone = self.class.validate_default_timezone(@config[:default_timezone]) @raw_connection_dirty = false @verified = false - - configure_connection end EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc: @@ -147,7 +166,7 @@ def use_metadata_table? end def connection_retries - (@config[:connection_retries] || 3).to_i + (@config[:connection_retries] || 1).to_i end def default_timezone @@ -314,7 +333,14 @@ def adapter_name # Does the database for this adapter exist? def self.database_exists?(config) - raise NotImplementedError + new(config).database_exists? + end + + def database_exists? + connect! + true + rescue ActiveRecord::NoDatabaseError + false end # Does this adapter support DDL rollbacks in transactions? That is, would @@ -598,6 +624,7 @@ def reconnect!(restore_transactions: false) def disconnect! clear_cache!(new_connection: true) reset_transaction + @raw_connection_dirty = false end # Immediately forget this connection ever existed. Unlike disconnect!, @@ -658,10 +685,30 @@ def requires_reloading? # This is done under the hood by calling #active?. If the connection # is no longer active, then this method will reconnect to the database. def verify! - reconnect!(restore_transactions: true) unless active? + unless active? + if @unconfigured_connection + @lock.synchronize do + if @unconfigured_connection + @raw_connection = @unconfigured_connection + @unconfigured_connection = nil + configure_connection + @verified = true + return + end + end + end + + reconnect!(restore_transactions: true) + end + @verified = true end + def connect! + verify! + self + end + def clean! # :nodoc: @raw_connection_dirty = false @verified = nil @@ -864,6 +911,8 @@ def reconnect_can_restore_state? # def with_raw_connection(allow_retry: false, uses_transaction: true) @lock.synchronize do + connect! if @raw_connection.nil? && reconnect_can_restore_state? + materialize_transactions if uses_transaction retries_available = allow_retry ? connection_retries : 0 @@ -909,6 +958,13 @@ def with_raw_connection(allow_retry: false, uses_transaction: true) end end + unless retryable_query_error?(translated_exception) + # Barring a known-retryable error inside the query (regardless of + # whether we were in a _position_ to retry it), we should infer that + # there's likely a real problem with the connection. + @verified = false + end + raise translated_exception ensure dirty_current_transaction if uses_transaction @@ -942,7 +998,7 @@ def reconnect # to both be thread-safe and not rely upon actual server communication. # This is useful for e.g. string escaping methods. def any_raw_connection - @raw_connection + @raw_connection || valid_raw_connection end # Similar to any_raw_connection, but ensures it is validated and 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 bd82f033c9fd7..960ce35cc165d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -51,10 +51,6 @@ def dealloc(stmt) end end - def initialize(connection, logger, connection_options, config) - super(connection, logger, config) - end - def get_database_version # :nodoc: full_version_string = get_full_version version_string = version_string(full_version_string) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 843f18d5a8654..8effce78e2261 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -10,21 +10,7 @@ module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects. def mysql2_connection(config) - config = config.symbolize_keys - config[:flags] ||= 0 - - if config[:flags].kind_of? Array - config[:flags].push "FOUND_ROWS" - else - config[:flags] |= Mysql2::Client::FOUND_ROWS - end - - ConnectionAdapters::Mysql2Adapter.new( - ConnectionAdapters::Mysql2Adapter.new_client(config), - logger, - nil, - config, - ) + ConnectionAdapters::Mysql2Adapter.new(config) end end @@ -55,16 +41,25 @@ def new_client(config) end end - def initialize(connection, logger, connection_options, config) - check_prepared_statements_deprecation(config) - superclass_config = config.reverse_merge(prepared_statements: false) - super(connection, logger, connection_options, superclass_config) - end + def initialize(...) + super + + @config[:flags] ||= 0 - def self.database_exists?(config) - !!ActiveRecord::Base.mysql2_connection(config) - rescue ActiveRecord::NoDatabaseError - false + if @config[:flags].kind_of? Array + @config[:flags].push "FOUND_ROWS" + else + @config[:flags] |= Mysql2::Client::FOUND_ROWS + end + + unless @config.key?(:prepared_statements) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The default value of `prepared_statements` for the mysql2 adapter will be changed from +false+ to +true+ in Rails 7.2. + MSG + @config[:prepared_statements] = false + end + + @connection_parameters ||= @config end def supports_json? @@ -120,7 +115,7 @@ def quote_string(string) #++ def active? - @raw_connection.ping + !!@raw_connection&.ping end alias :reset! :reconnect! @@ -129,30 +124,23 @@ def active? # Otherwise, this method does nothing. def disconnect! super - @raw_connection.close + @raw_connection&.close + @raw_connection = nil end def discard! # :nodoc: super - @raw_connection.automatic_close = false + @raw_connection&.automatic_close = false @raw_connection = nil end private - def check_prepared_statements_deprecation(config) - if !config.key?(:prepared_statements) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - The default value of `prepared_statements` for the mysql2 adapter will be changed from +false+ to +true+ in Rails 7.2. - MSG - end - end - def connect - @raw_connection = self.class.new_client(@config) + @raw_connection = self.class.new_client(@connection_parameters) end def reconnect - @raw_connection.close + @raw_connection&.close connect end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1eef27f2cbfd1..42899dc807dfb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -23,22 +23,7 @@ module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects def postgresql_connection(config) - conn_params = config.symbolize_keys.compact - - # Map ActiveRecords param names to PGs. - conn_params[:user] = conn_params.delete(:username) if conn_params[:username] - conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] - - # Forward only valid config params to PG::Connection.connect. - valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] - conn_params.slice!(*valid_conn_param_keys) - - ConnectionAdapters::PostgreSQLAdapter.new( - ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params), - logger, - conn_params, - config, - ) + ConnectionAdapters::PostgreSQLAdapter.new(config) end end @@ -286,26 +271,31 @@ def dealloc(key) end # Initializes and connects a PostgreSQL adapter. - def initialize(connection, logger, connection_parameters, config) - @connection_parameters = connection_parameters || {} + def initialize(...) + super + + conn_params = @config.compact + + # Map ActiveRecords param names to PGs. + conn_params[:user] = conn_params.delete(:username) if conn_params[:username] + conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] + + # Forward only valid config params to PG::Connection.connect. + valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] + conn_params.slice!(*valid_conn_param_keys) + + @connection_parameters = conn_params @max_identifier_length = nil @type_map = nil - super(connection, logger, config) - @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end - def self.database_exists?(config) - !!ActiveRecord::Base.postgresql_connection(config) - rescue ActiveRecord::NoDatabaseError - false - end - # Is this connection alive and ready for queries? def active? @lock.synchronize do + return false unless @raw_connection @raw_connection.query ";" end true @@ -327,6 +317,8 @@ def reload_type_map # :nodoc: def reset! @lock.synchronize do + return connect! unless @raw_connection + unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE @raw_connection.query "ROLLBACK" end @@ -341,13 +333,14 @@ def reset! def disconnect! @lock.synchronize do super - @raw_connection.close rescue nil + @raw_connection&.close rescue nil + @raw_connection = nil end end def discard! # :nodoc: super - @raw_connection.socket_io.reopen(IO::NULL) rescue nil + @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil @raw_connection = nil end @@ -885,9 +878,13 @@ def connect end def reconnect - @raw_connection.reset - rescue PG::ConnectionBad - connect + begin + @raw_connection&.reset + rescue PG::ConnectionBad + @raw_connection = nil + end + + connect unless @raw_connection end # Configures the encoding, verbosity, schema search path, and time zone of the connection. @@ -1019,7 +1016,7 @@ def add_pg_encoders end def update_typemap_for_default_timezone - if @mapped_default_timezone != default_timezone && @timestamp_decoder + if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder decoder_class = default_timezone == :utc ? PG::TextDecoder::TimestampUtc : PG::TextDecoder::TimestampWithoutTimeZone @@ -1032,6 +1029,8 @@ def update_typemap_for_default_timezone # if default timezone has changed, we need to reconfigure the connection # (specifically, the session time zone) reconfigure_connection_timezone + + true end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb index bf90afb5e736f..2911d954c746e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb @@ -78,14 +78,16 @@ def begin_isolated_db_transaction(isolation) # :nodoc: raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache? - ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = @raw_connection.get_first_value("PRAGMA read_uncommitted") - @raw_connection.read_uncommitted = true - begin_db_transaction + with_raw_connection(allow_retry: true, uses_transaction: false) do |conn| + ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted") + conn.read_uncommitted = true + begin_db_transaction + end end def begin_db_transaction # :nodoc: log("begin transaction", "TRANSACTION") do - with_raw_connection do |conn| + with_raw_connection(allow_retry: true, uses_transaction: false) do |conn| conn.transaction end end @@ -93,7 +95,7 @@ def begin_db_transaction # :nodoc: def commit_db_transaction # :nodoc: log("commit transaction", "TRANSACTION") do - with_raw_connection do |conn| + with_raw_connection(allow_retry: true, uses_transaction: false) do |conn| conn.commit end end @@ -102,7 +104,7 @@ def commit_db_transaction # :nodoc: def exec_rollback_db_transaction # :nodoc: log("rollback transaction", "TRANSACTION") do - with_raw_connection do |conn| + with_raw_connection(allow_retry: true, uses_transaction: false) do |conn| conn.rollback end end @@ -123,7 +125,7 @@ def reset_read_uncommitted read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] return unless read_uncommitted - @raw_connection.read_uncommitted = read_uncommitted + @raw_connection&.read_uncommitted = read_uncommitted end def execute_batch(statements, name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index eea3f6a7d6081..6c0d7c9064de7 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -16,35 +16,7 @@ module ActiveRecord module ConnectionHandling # :nodoc: def sqlite3_connection(config) - config = config.symbolize_keys - - # Require database. - unless config[:database] - raise ArgumentError, "No database file specified. Missing argument: database" - end - - # Allow database path relative to Rails.root, but only if the database - # path is not the special path that tells sqlite to build a database only - # in memory. - if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:") - config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) - dirname = File.dirname(config[:database]) - Dir.mkdir(dirname) unless File.directory?(dirname) - end - - config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless config.key?(:strict) - db = SQLite3::Database.new( - config[:database].to_s, - config.merge(results_as_hash: true) - ) - - ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) - rescue Errno::ENOENT => error - if error.message.include?("No such file or directory") - raise ActiveRecord::NoDatabaseError - else - raise - end + ConnectionAdapters::SQLite3Adapter.new(config) end end @@ -58,6 +30,18 @@ module ConnectionAdapters # :nodoc: class SQLite3Adapter < AbstractAdapter ADAPTER_NAME = "SQLite" + class << self + def new_client(config) + ::SQLite3::Database.new(config[:database].to_s, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end + include SQLite3::Quoting include SQLite3::SchemaStatements include SQLite3::DatabaseStatements @@ -96,19 +80,39 @@ def dealloc(stmt) end end - def initialize(connection, logger, connection_options, config) - @memory_database = config[:database] == ":memory:" - super(connection, logger, config) - end + def initialize(...) + super - def self.database_exists?(config) - config = config.symbolize_keys - if config[:database] == ":memory:" - true + @memory_database = false + case @config[:database].to_s + when "" + raise ArgumentError, "No database file specified. Missing argument: database" + when ":memory:" + @memory_database = true + when /\Afile:/ else - database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database] - File.exist?(database_file) + # Otherwise we have a path relative to Rails.root + @config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root) + dirname = File.dirname(@config[:database]) + unless File.directory?(dirname) + begin + Dir.mkdir(dirname) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end end + + @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict) + @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true) + end + + def database_exists? + @config[:database] == ":memory:" || File.exist?(@config[:database].to_s) end def supports_ddl_transactions? @@ -171,7 +175,7 @@ def supports_concurrent_connections? end def active? - !@raw_connection.closed? + @raw_connection && !@raw_connection.closed? end alias :reset! :reconnect! @@ -180,7 +184,9 @@ def active? # method does nothing. def disconnect! super - @raw_connection.close rescue nil + + @raw_connection&.close rescue nil + @raw_connection = nil end def supports_index_sort_order? @@ -616,10 +622,7 @@ def build_statement_pool end def connect - @raw_connection = ::SQLite3::Database.new( - @config[:database].to_s, - @config.merge(results_as_hash: true) - ) + @raw_connection = self.class.new_client(@connection_parameters) end def reconnect diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 8f2bd7556c1dd..204d9278f6ec3 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -645,8 +645,9 @@ def teardown assert_equal 1, invocations # the whole transaction block is not retried - # After the (outermost) transaction block failed, it reconnected - assert_predicate @connection, :active? + # After the (outermost) transaction block failed, the connection is + # ready to reconnect on next use, but hasn't done so yet + assert_not_predicate @connection, :active? assert_operator Post.count, :>, 0 end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index c9a794ee7f771..974e669b5ebe6 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -52,21 +52,16 @@ def test_successful_reconnection_after_timeout_with_verify assert_predicate @connection, :active? end - def test_execute_after_disconnect + def test_execute_after_disconnect_reconnects @connection.disconnect! - error = assert_raise(ActiveRecord::ConnectionNotEstablished) do - @connection.execute("SELECT 1") - end - assert_kind_of Mysql2::Error, error.cause + assert_equal 3, @connection.select_value("SELECT 1+2") end - def test_quote_after_disconnect + def test_quote_after_disconnect_reconnects @connection.disconnect! - assert_raise(ActiveRecord::ConnectionNotEstablished) do - @connection.quote("string") - end + assert_equal "'string'", @connection.quote("string") end def test_active_after_disconnect diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index 1fa8a9b4cfa23..35b46d84ea521 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -12,7 +12,7 @@ def setup def test_connection_error assert_raises ActiveRecord::ConnectionNotEstablished do - ActiveRecord::Base.mysql2_connection(socket: File::NULL) + ActiveRecord::Base.mysql2_connection(socket: File::NULL, prepared_statements: false).connect! end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 91a36a9171da7..2ad03084cb93b 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -17,7 +17,7 @@ def setup def test_connection_error assert_raises ActiveRecord::ConnectionNotEstablished do - ActiveRecord::Base.postgresql_connection(host: File::NULL) + ActiveRecord::Base.postgresql_connection(host: File::NULL).connect! end end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index 70ee767bbd57e..373c723c6fac5 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -43,7 +43,7 @@ def test_dealloc_does_not_raise_on_inactive_connection end def test_prepared_statements_do_not_get_stuck_on_query_interruption - pg_connection = ActiveRecord::Base.connection.instance_variable_get(:@raw_connection) + pg_connection = ActiveRecord::Base.connection.connect!.instance_variable_get(:@raw_connection) pg_connection.stub(:get_last_result, -> { raise "random error" }) do assert_raises(RuntimeError) do Developer.where(name: "David").last diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 148b6ed41a4b5..4b90c20dc323c 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -103,15 +103,15 @@ def test_primary_key_returns_nil_for_no_pk def test_connection_no_db assert_raises(ArgumentError) do - Base.sqlite3_connection { } + Base.sqlite3_connection({}) end end def test_bad_timeout - assert_raises(TypeError) do - Base.sqlite3_connection database: ":memory:", + assert_raises(ActiveRecord::StatementInvalid, /TypeError/) do + Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", - timeout: "usa" + timeout: "usa").connect! end end @@ -120,6 +120,7 @@ def test_nil_timeout conn = Base.sqlite3_connection database: ":memory:", adapter: "sqlite3", timeout: nil + conn.connect! assert conn, "made a connection" end @@ -280,6 +281,7 @@ def test_tables_logs_name sql = <<~SQL SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table') SQL + @conn.connect! assert_logged [[sql.squish, "SCHEMA", []]] do @conn.tables end @@ -574,6 +576,8 @@ def test_statement_closed db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary") db = ::SQLite3::Database.new(db_config.database) + @conn.connect! + statement = ::SQLite3::Statement.new(db, "CREATE TABLE statement_test (number integer not null)") statement.stub(:step, -> { raise ::SQLite3::BusyException.new("busy") }) do @@ -593,6 +597,7 @@ def test_db_is_not_readonly_when_readonly_option_is_false conn = Base.sqlite3_connection database: ":memory:", adapter: "sqlite3", readonly: false + conn.connect! assert_not_predicate conn.raw_connection, :readonly? end @@ -600,6 +605,7 @@ def test_db_is_not_readonly_when_readonly_option_is_false def test_db_is_not_readonly_when_readonly_option_is_unspecified conn = Base.sqlite3_connection database: ":memory:", adapter: "sqlite3" + conn.connect! assert_not_predicate conn.raw_connection, :readonly? end @@ -608,6 +614,7 @@ def test_db_is_readonly_when_readonly_option_is_true conn = Base.sqlite3_connection database: ":memory:", adapter: "sqlite3", readonly: true + conn.connect! assert_predicate conn.raw_connection, :readonly? end @@ -616,6 +623,7 @@ def test_writes_are_not_permitted_to_readonly_databases conn = Base.sqlite3_connection database: ":memory:", adapter: "sqlite3", readonly: true + conn.connect! assert_raises(ActiveRecord::StatementInvalid, /SQLite3::ReadOnlyException/) do conn.execute("CREATE TABLE test(id integer)") diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb index cfc9853abae93..c0458a63f3eb8 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb @@ -12,6 +12,7 @@ def test_sqlite_creates_directory @conn = Base.sqlite3_connection database: dir.join("db/foo.sqlite3"), adapter: "sqlite3", timeout: 100 + @conn.connect! assert Dir.exist? dir.join("db") assert File.exist? dir.join("db/foo.sqlite3") diff --git a/activerecord/test/cases/adapters/sqlite3/transaction_test.rb b/activerecord/test/cases/adapters/sqlite3/transaction_test.rb index 66a61aa6106b3..9ca8f78f3aa01 100644 --- a/activerecord/test/cases/adapters/sqlite3/transaction_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/transaction_test.rb @@ -59,6 +59,7 @@ class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase test "reset the read_uncommitted PRAGMA when a transaction is rolled back" do with_connection(flags: shared_cache_flags) do |conn| + conn.connect! conn.transaction(joinable: false, isolation: :read_uncommitted) do assert_not(read_uncommitted?(conn)) conn.transaction_manager.materialize_transactions @@ -73,6 +74,7 @@ class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase test "reset the read_uncommitted PRAGMA when a transaction is committed" do with_connection(flags: shared_cache_flags) do |conn| + conn.connect! conn.transaction(joinable: false, isolation: :read_uncommitted) do assert_not(read_uncommitted?(conn)) conn.transaction_manager.materialize_transactions @@ -85,6 +87,7 @@ class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase test "set the read_uncommitted PRAGMA to its previous value" do with_connection(flags: shared_cache_flags) do |conn| + conn.connect! conn.transaction(joinable: false, isolation: :read_uncommitted) do conn.instance_variable_get(:@raw_connection).read_uncommitted = true assert(read_uncommitted?(conn)) diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb index 614d347015c03..80c4d78f55931 100644 --- a/activerecord/test/cases/disconnected_test.rb +++ b/activerecord/test/cases/disconnected_test.rb @@ -20,12 +20,17 @@ def setup end unless in_memory_db? - test "can't execute statements while disconnected" do + test "reconnects to execute statements when disconnected" do @connection.execute "SELECT count(*) from products" + first_connection = @connection.instance_variable_get(:@raw_connection).__id__ + @connection.disconnect! - assert_raises(ActiveRecord::ConnectionNotEstablished) do - @connection.execute "SELECT count(*) from products" - end + assert_nil @connection.instance_variable_get(:@raw_connection) + + @connection.execute "SELECT count(*) from products" + second_connection = @connection.instance_variable_get(:@raw_connection).__id__ + + assert_not_equal second_connection, first_connection end end end diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index e46cab0b39be7..ff77fc8509bb2 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -114,7 +114,7 @@ class Paperback < ActiveRecord::Base; end end teardown do - @connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks" + @connection.execute "DROP VIEW paperbacks" if @connection&.view_exists? "paperbacks" end def test_reading