mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
4cf7559280
Sometimes cascading association deletions can cause timeouts due to an IO issue. Perhaps a model has associations that are destroyed on deletion which in turn trigger other deletions and this can continue down a complex tree. Along this tree you may also hit other IO operations. Such deep deletions can lead to server timeouts while awaiting completion and really the user may not notice all the changes on their side immediately making them wait unnecesarially or worse causing a timeout during the operation. We now allow associations supporting the `dependent:` key to take `:destroy_async`, which schedules a background job to destroy associations. Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com> Co-authored-by: Rafael Mendonça França <rafael@franca.dev> Co-authored-by: Cory Gwin @gwincr11 <gwincr11@github.com>
207 lines
5.6 KiB
Ruby
207 lines
5.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "config"
|
|
|
|
require "stringio"
|
|
|
|
require "active_record"
|
|
require "cases/test_case"
|
|
require "active_support/dependencies"
|
|
require "active_support/logger"
|
|
require "active_support/core_ext/kernel/singleton_class"
|
|
|
|
require "support/config"
|
|
require "support/connection"
|
|
|
|
# TODO: Move all these random hacks into the ARTest namespace and into the support/ dir
|
|
|
|
Thread.abort_on_exception = true
|
|
|
|
# Show backtraces for deprecated behavior for quicker cleanup.
|
|
ActiveSupport::Deprecation.debug = true
|
|
|
|
# Disable available locale checks to avoid warnings running the test suite.
|
|
I18n.enforce_available_locales = false
|
|
|
|
# Connect to the database
|
|
ARTest.connect
|
|
|
|
# Quote "type" if it's a reserved word for the current connection.
|
|
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name("type")
|
|
|
|
def current_adapter?(*types)
|
|
types.any? do |type|
|
|
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
|
|
ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters.const_get(type))
|
|
end
|
|
end
|
|
|
|
def in_memory_db?
|
|
current_adapter?(:SQLite3Adapter) &&
|
|
ActiveRecord::Base.connection_pool.db_config.database == ":memory:"
|
|
end
|
|
|
|
def mysql_enforcing_gtid_consistency?
|
|
current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
|
|
end
|
|
|
|
def supports_default_expression?
|
|
if current_adapter?(:PostgreSQLAdapter)
|
|
true
|
|
elsif current_adapter?(:Mysql2Adapter)
|
|
conn = ActiveRecord::Base.connection
|
|
!conn.mariadb? && conn.database_version >= "8.0.13"
|
|
end
|
|
end
|
|
|
|
%w[
|
|
supports_savepoints?
|
|
supports_partial_index?
|
|
supports_partitioned_indexes?
|
|
supports_expression_index?
|
|
supports_insert_returning?
|
|
supports_insert_on_duplicate_skip?
|
|
supports_insert_on_duplicate_update?
|
|
supports_insert_conflict_target?
|
|
supports_optimizer_hints?
|
|
supports_datetime_with_precision?
|
|
].each do |method_name|
|
|
define_method method_name do
|
|
ActiveRecord::Base.connection.public_send(method_name)
|
|
end
|
|
end
|
|
|
|
def with_env_tz(new_tz = "US/Eastern")
|
|
old_tz, ENV["TZ"] = ENV["TZ"], new_tz
|
|
yield
|
|
ensure
|
|
old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
|
|
end
|
|
|
|
def with_timezone_config(cfg)
|
|
verify_default_timezone_config
|
|
|
|
old_default_zone = ActiveRecord::Base.default_timezone
|
|
old_awareness = ActiveRecord::Base.time_zone_aware_attributes
|
|
old_zone = Time.zone
|
|
|
|
if cfg.has_key?(:default)
|
|
ActiveRecord::Base.default_timezone = cfg[:default]
|
|
end
|
|
if cfg.has_key?(:aware_attributes)
|
|
ActiveRecord::Base.time_zone_aware_attributes = cfg[:aware_attributes]
|
|
end
|
|
if cfg.has_key?(:zone)
|
|
Time.zone = cfg[:zone]
|
|
end
|
|
yield
|
|
ensure
|
|
ActiveRecord::Base.default_timezone = old_default_zone
|
|
ActiveRecord::Base.time_zone_aware_attributes = old_awareness
|
|
Time.zone = old_zone
|
|
end
|
|
|
|
# This method makes sure that tests don't leak global state related to time zones.
|
|
EXPECTED_ZONE = nil
|
|
EXPECTED_DEFAULT_TIMEZONE = :utc
|
|
EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES = false
|
|
def verify_default_timezone_config
|
|
if Time.zone != EXPECTED_ZONE
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `Time.zone` was leaked.
|
|
Expected: #{EXPECTED_ZONE}
|
|
Got: #{Time.zone}
|
|
MSG
|
|
end
|
|
if ActiveRecord::Base.default_timezone != EXPECTED_DEFAULT_TIMEZONE
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `ActiveRecord::Base.default_timezone` was leaked.
|
|
Expected: #{EXPECTED_DEFAULT_TIMEZONE}
|
|
Got: #{ActiveRecord::Base.default_timezone}
|
|
MSG
|
|
end
|
|
if ActiveRecord::Base.time_zone_aware_attributes != EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `ActiveRecord::Base.time_zone_aware_attributes` was leaked.
|
|
Expected: #{EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES}
|
|
Got: #{ActiveRecord::Base.time_zone_aware_attributes}
|
|
MSG
|
|
end
|
|
end
|
|
|
|
def enable_extension!(extension, connection)
|
|
return false unless connection.supports_extensions?
|
|
return connection.reconnect! if connection.extension_enabled?(extension)
|
|
|
|
connection.enable_extension extension
|
|
connection.commit_db_transaction if connection.transaction_open?
|
|
connection.reconnect!
|
|
end
|
|
|
|
def disable_extension!(extension, connection)
|
|
return false unless connection.supports_extensions?
|
|
return true unless connection.extension_enabled?(extension)
|
|
|
|
connection.disable_extension extension
|
|
connection.reconnect!
|
|
end
|
|
|
|
def clean_up_connection_handler
|
|
ActiveRecord::Base.connection_handlers = { ActiveRecord::Base.writing_role => ActiveRecord::Base.default_connection_handler }
|
|
end
|
|
|
|
def load_schema
|
|
# silence verbose schema loading
|
|
original_stdout = $stdout
|
|
$stdout = StringIO.new
|
|
|
|
adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
|
|
adapter_specific_schema_file = SCHEMA_ROOT + "/#{adapter_name}_specific_schema.rb"
|
|
|
|
load SCHEMA_ROOT + "/schema.rb"
|
|
|
|
if File.exist?(adapter_specific_schema_file)
|
|
load adapter_specific_schema_file
|
|
end
|
|
|
|
ActiveRecord::FixtureSet.reset_cache
|
|
ensure
|
|
$stdout = original_stdout
|
|
end
|
|
|
|
load_schema
|
|
|
|
class SQLSubscriber
|
|
attr_reader :logged
|
|
attr_reader :payloads
|
|
|
|
def initialize
|
|
@logged = []
|
|
@payloads = []
|
|
end
|
|
|
|
def start(name, id, payload)
|
|
@payloads << payload
|
|
@logged << [payload[:sql].squish, payload[:name], payload[:binds]]
|
|
end
|
|
|
|
def finish(name, id, payload); end
|
|
end
|
|
|
|
module InTimeZone
|
|
private
|
|
def in_time_zone(zone)
|
|
old_zone = Time.zone
|
|
old_tz = ActiveRecord::Base.time_zone_aware_attributes
|
|
|
|
Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil
|
|
ActiveRecord::Base.time_zone_aware_attributes = !zone.nil?
|
|
yield
|
|
ensure
|
|
Time.zone = old_zone
|
|
ActiveRecord::Base.time_zone_aware_attributes = old_tz
|
|
end
|
|
end
|