diff options
5 files changed, 123 insertions, 41 deletions
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 07206f0d01..284b38ed7b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -558,35 +558,6 @@ module ActiveRecord @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin) end - def with_multi_statements - if supports_set_server_option? - @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) - elsif !supports_multi_statements? - previous_flags = @config[:flags] - @config[:flags] = Mysql2::Client::MULTI_STATEMENTS - reconnect! - end - - yield - ensure - unless supports_multi_statements? - if supports_set_server_option? - @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) - else - @config[:flags] = previous_flags - reconnect! - end - end - end - - def supports_multi_statements? - (@config[:flags] & Mysql2::Client::MULTI_STATEMENTS) != 0 - end - - def supports_set_server_option? - @connection.respond_to?(:set_server_option) - end - def initialize_type_map(m = type_map) super diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 4106ce01be..d89eeb7f54 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -62,6 +62,42 @@ module ActiveRecord @connection.abandon_results! end + def supports_set_server_option? + @connection.respond_to?(:set_server_option) + end + + def multi_statements_enabled?(flags) + if flags.is_a?(Array) + flags.include?("MULTI_STATEMENTS") + else + (flags & Mysql2::Client::MULTI_STATEMENTS) != 0 + end + end + + def with_multi_statements + previous_flags = @config[:flags] + + unless multi_statements_enabled?(previous_flags) + if supports_set_server_option? + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + else + @config[:flags] = Mysql2::Client::MULTI_STATEMENTS + reconnect! + end + end + + yield + ensure + unless multi_statements_enabled?(previous_flags) + if supports_set_server_option? + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + else + @config[:flags] = previous_flags + reconnect! + end + end + end + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index a0ab7a92ce..c65523d8c1 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "cases/helper" +require "support/connection_helper" require "models/admin" require "models/admin/account" require "models/admin/randomly_named_c1" @@ -32,6 +33,8 @@ require "models/treasure" require "tempfile" class FixturesTest < ActiveRecord::TestCase + include ConnectionHelper + self.use_instantiated_fixtures = true self.use_transactional_tests = false @@ -122,16 +125,88 @@ class FixturesTest < ActiveRecord::TestCase ] } - ActiveRecord::Base.transaction do - con = ActiveRecord::Base.connection - assert_equal 1, con.open_transactions - con.insert_fixtures_set(fixtures) - assert_equal 1, con.open_transactions + assert_difference "TrafficLight.count" do + ActiveRecord::Base.transaction do + conn = ActiveRecord::Base.connection + assert_equal 1, conn.open_transactions + conn.insert_fixtures_set(fixtures) + assert_equal 1, conn.open_transactions + end end end end if current_adapter?(:Mysql2Adapter) + def test_bulk_insert_with_multi_statements_enabled + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection( + orig_connection.merge(flags: %w[MULTI_STATEMENTS]) + ) + + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a"] }, + ] + } + + ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do + assert_nothing_raised do + conn = ActiveRecord::Base.connection + conn.execute("SELECT 1; SELECT 2;") + conn.raw_connection.abandon_results! + end + + assert_difference "TrafficLight.count" do + ActiveRecord::Base.transaction do + conn = ActiveRecord::Base.connection + assert_equal 1, conn.open_transactions + conn.insert_fixtures_set(fixtures) + assert_equal 1, conn.open_transactions + end + end + + assert_nothing_raised do + conn = ActiveRecord::Base.connection + conn.execute("SELECT 1; SELECT 2;") + conn.raw_connection.abandon_results! + end + end + end + end + + def test_bulk_insert_with_multi_statements_disabled + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection( + orig_connection.merge(flags: []) + ) + + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a"] }, + ] + } + + ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do + assert_raises(ActiveRecord::StatementInvalid) do + conn = ActiveRecord::Base.connection + conn.execute("SELECT 1; SELECT 2;") + conn.raw_connection.abandon_results! + end + + assert_difference "TrafficLight.count" do + conn = ActiveRecord::Base.connection + conn.insert_fixtures_set(fixtures) + end + + assert_raises(ActiveRecord::StatementInvalid) do + conn = ActiveRecord::Base.connection + conn.execute("SELECT 1; SELECT 2;") + conn.raw_connection.abandon_results! + end + end + end + end + def test_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size conn = ActiveRecord::Base.connection mysql_margin = 2 diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb index bb80799044..5d36990fb9 100644 --- a/activestorage/app/models/active_storage/attachment.rb +++ b/activestorage/app/models/active_storage/attachment.rb @@ -17,13 +17,13 @@ class ActiveStorage::Attachment < ActiveRecord::Base after_create_commit :analyze_blob_later, :identify_blob after_destroy_commit :purge_dependent_blob_later - # Synchronously purges the blob (deletes it from the configured service) and deletes the attachment. + # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge]. def purge blob.purge delete end - # Deletes the attachment and queues a background job to purge the blob (delete it from the configured service). + # Deletes the attachment and {enqueues a background job}[rdoc-ref:ActiveStorage::Blob#purge_later] to purge the blob. def purge_later blob.purge_later delete diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 86f3dba524..b72f2e796d 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -193,8 +193,8 @@ class ActiveStorage::Blob < ActiveRecord::Base end - # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be - # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+ + # Deletes the files on the service associated with the blob. This should only be done if the blob is going to be + # deleted as well or you will essentially have a dead reference. It's recommended to use #purge and #purge_later # methods in most circumstances. def delete service.delete(key) @@ -203,14 +203,14 @@ class ActiveStorage::Blob < ActiveRecord::Base # Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted # blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may - # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use +#purge_later+ instead. + # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use #purge_later instead. def purge delete destroy end - # Enqueues an ActiveStorage::PurgeJob job that'll call +purge+. This is the recommended way to purge blobs when the call - # needs to be made from a transaction, a callback, or any other real-time scenario. + # Enqueues an ActiveStorage::PurgeJob to call #purge. This is the recommended way to purge blobs from a transaction, + # an Active Record callback, or in any other real-time scenario. def purge_later ActiveStorage::PurgeJob.perform_later(self) end |