aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md12
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb10
-rw-r--r--activerecord/lib/active_record/database_configurations.rb12
-rw-r--r--activerecord/lib/active_record/errors.rb12
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake31
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb13
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb4
-rw-r--r--activerecord/lib/arel/predications.rb4
-rw-r--r--activerecord/lib/arel/visitors/visitor.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb27
-rw-r--r--activerecord/test/cases/adapters/mysql2/set_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb12
-rw-r--r--activerecord/test/cases/adapters/sqlite3/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb16
-rw-r--r--activerecord/test/cases/annotate_test.rb46
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb24
-rw-r--r--activerecord/test/cases/autosave_association_test.rb57
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb7
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb50
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb14
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/query_cache_test.rb17
-rw-r--r--activerecord/test/cases/relation_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb1
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/book.rb6
-rw-r--r--activerecord/test/models/mouse.rb6
-rw-r--r--activerecord/test/models/ship.rb3
-rw-r--r--activerecord/test/models/squeak.rb6
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb4
-rw-r--r--activerecord/test/schema/schema.rb10
66 files changed, 532 insertions, 198 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8642227a2b..727ddd6bb7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Make currency symbols optional for money column type in PostgreSQL
+
+ *Joel Schneider*
+
+* Add support for beginless ranges, introduced in Ruby 2.7.
+
+ *Josh Goodall*
+
+* Add database_exists? method to connection adapters to check if a database exists.
+
+ *Guilherme Mansur*
+
* Loading the schema for a model that has no `table_name` raises a `TableNotSpecified` error.
*Guilherme Mansur*, *Eugene Kenny*
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index a7709b444d..734ebb45ae 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -302,7 +302,7 @@ module ActiveRecord
def validate_single_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.reader
- association_valid?(reflection, record) if record
+ association_valid?(reflection, record) if record && record.changed_for_autosave?
end
# Validate the associated records if <tt>:validate</tt> or
@@ -409,7 +409,7 @@ module ActiveRecord
saved = record.save(validate: false)
end
- raise ActiveRecord::Rollback unless saved
+ raise(RecordInvalid.new(association.owner)) unless saved
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2af6d09b53..282c9fcf30 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -12,7 +12,6 @@ require "active_support/core_ext/hash/slice"
require "active_support/core_ext/string/behavior"
require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/introspection"
-require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/class/subclasses"
require "active_record/attribute_decorators"
require "active_record/define_callbacks"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 9b3f5260f7..36001efdd5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -426,7 +426,7 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a cache keyed by a thread.
def connection
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
@@ -435,7 +435,7 @@ module ActiveRecord
# #connection or #with_connection methods. Connections obtained through
# #checkout will not be detected by #active_connection?
def active_connection?
- @thread_cached_conns[connection_cache_key(Thread.current)]
+ @thread_cached_conns[connection_cache_key(current_thread)]
end
# Signal that the thread is finished with the current connection.
@@ -730,6 +730,10 @@ module ActiveRecord
thread
end
+ def current_thread
+ @lock_thread || Thread.current
+ end
+
# Take control of all existing connections so a "group" action such as
# reload/disconnect can be performed safely. It is no longer enough to
# wrap it in +synchronize+ because some pool's actions are allowed
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 75e959045e..d932f068f2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/deprecation"
-
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseLimits
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 6fec4dbd81..768122b4d2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -33,17 +33,17 @@ module ActiveRecord
end
def enable_query_cache!
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
connection.enable_query_cache! if active_connection?
end
def disable_query_cache!
- @query_cache_enabled.delete connection_cache_key(Thread.current)
+ @query_cache_enabled.delete connection_cache_key(current_thread)
connection.disable_query_cache! if active_connection?
end
def query_cache_enabled
- @query_cache_enabled[connection_cache_key(Thread.current)]
+ @query_cache_enabled[connection_cache_key(current_thread)]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 79dc98607a..13f94a4722 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -2,7 +2,6 @@
require "active_record/migration/join_table"
require "active_support/core_ext/string/access"
-require "active_support/deprecation"
require "digest/sha2"
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c0ead17b3b..dc970c384b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -6,7 +6,6 @@ require "active_record/connection_adapters/sql_type_metadata"
require "active_record/connection_adapters/abstract/schema_dumper"
require "active_record/connection_adapters/abstract/schema_creation"
require "active_support/concurrency/load_interlock_aware_monitor"
-require "active_support/deprecation"
require "arel/collectors/bind"
require "arel/collectors/composite"
require "arel/collectors/sql_string"
@@ -106,6 +105,14 @@ module ActiveRecord
Regexp.union(*parts)
end
+ def self.quoted_column_names # :nodoc:
+ @quoted_column_names ||= {}
+ end
+
+ def self.quoted_table_names # :nodoc:
+ @quoted_table_names ||= {}
+ end
+
def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@@ -116,7 +123,6 @@ module ActiveRecord
@config = config
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
@idle_since = Concurrent.monotonic_time
- @quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
@statements = build_statement_pool
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
@@ -264,6 +270,11 @@ module ActiveRecord
self.class::ADAPTER_NAME
end
+ # Does the database for this adapter exist?
+ def self.database_exists?(config)
+ raise NotImplementedError
+ end
+
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
def supports_ddl_transactions?
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 70292e6d63..405fecb603 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -619,7 +619,11 @@ module ActiveRecord
when ER_QUERY_INTERRUPTED
QueryCanceled.new(message, sql: sql, binds: binds)
else
- super
+ if exception.is_a?(Mysql2::Error::TimeoutError)
+ ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
+ else
+ super
+ end
end
end
@@ -737,7 +741,7 @@ module ActiveRecord
end.compact.join(", ")
# ...and send them all in one query
- execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
end
def column_definitions(table_name) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index dfed5471f4..0069f5871c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -5,11 +5,11 @@ module ActiveRecord
module MySQL
module Quoting # :nodoc:
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
+ self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
end
def unquoted_true
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 234fb25fdf..bcd300f3db 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -41,13 +41,15 @@ module ActiveRecord
case column.sql_type
when /\Atimestamp\b/
:timestamp
+ when /\A(?:enum|set)\b/
+ column.sql_type
else
super
end
end
def schema_limit(column)
- super unless /\A(?:tiny|medium|long)?(?:text|blob)/.match?(column.sql_type)
+ super unless /\A(?:enum|set|(?:tiny|medium|long)?(?:text|blob))\b/.match?(column.sql_type)
end
def schema_precision(column)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 53510c62c2..1df9ac32c9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -8,6 +8,8 @@ require "mysql2"
module ActiveRecord
module ConnectionHandling # :nodoc:
+ ER_BAD_DB_ERROR = 1049
+
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
@@ -22,7 +24,7 @@ module ActiveRecord
client = Mysql2::Client.new(config)
ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
rescue Mysql2::Error => error
- if error.message.include?("Unknown database")
+ if error.error_number == ER_BAD_DB_ERROR
raise ActiveRecord::NoDatabaseError
else
raise
@@ -42,6 +44,12 @@ module ActiveRecord
configure_connection
end
+ def self.database_exists?(config)
+ !!ActiveRecord::Base.mysql2_connection(config)
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
+
def supports_json?
!mariadb? && database_version >= "5.7.8"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index b4ea6c42d3..f1ecf6df30 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -24,6 +24,16 @@ module ActiveRecord
super.sub(/\[\]\z/, "")
end
+ def init_with(coder)
+ @serial = coder["serial"]
+ super
+ end
+
+ def encode_with(coder)
+ coder["serial"] = @serial
+ super
+ end
+
def ==(other)
other.is_a?(Column) &&
super &&
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index 6434377b57..357493dfc0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -26,9 +26,9 @@ module ActiveRecord
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
case value
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ when /^-?\D*[\d,]+\.\d{2}$/ # (1)
value.gsub!(/[^-\d.]/, "")
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ when /^-?\D*[\d.]+,\d{2}$/ # (2)
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 0c800dca83..07b66de366 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -30,7 +30,7 @@ module ActiveRecord
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name) # :nodoc:
- @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
+ self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
end
# Quotes schema names for use in SQL queries.
@@ -44,7 +44,7 @@ module ActiveRecord
# Quotes column names for use in SQL queries.
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
+ self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
end
# Quote date/time values for use in SQL input.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6b18a12bce..0a7c6d8ac4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -46,7 +46,7 @@ module ActiveRecord
conn = PG.connect(conn_params)
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
rescue ::PG::Error => error
- if error.message.include?("does not exist")
+ if error.message.include?(conn_params[:dbname])
raise ActiveRecord::NoDatabaseError
else
raise
@@ -259,6 +259,12 @@ module ActiveRecord
@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
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 5da26171bb..7d54fcf9a0 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -36,12 +36,13 @@ module ActiveRecord
def init_with(coder)
@columns = coder["columns"]
- @columns_hash = {}
@primary_keys = coder["primary_keys"]
@data_sources = coder["data_sources"]
@indexes = coder["indexes"] || {}
@version = coder["version"]
@database_version = coder["database_version"]
+
+ derive_columns_hash_and_deduplicate_values
end
def primary_keys(table_name)
@@ -127,15 +128,19 @@ module ActiveRecord
def marshal_load(array)
@version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
@indexes ||= {}
- @columns_hash = {}
- @columns = deep_deduplicate(@columns)
- @primary_keys = deep_deduplicate(@primary_keys)
- @data_sources = deep_deduplicate(@data_sources)
- @indexes = deep_deduplicate(@indexes)
+ derive_columns_hash_and_deduplicate_values
end
private
+ def derive_columns_hash_and_deduplicate_values
+ @columns = deep_deduplicate(@columns)
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
+ @primary_keys = deep_deduplicate(@primary_keys)
+ @data_sources = deep_deduplicate(@data_sources)
+ @indexes = deep_deduplicate(@indexes)
+ end
+
def deep_deduplicate(value)
case value
when Hash
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index 58787cf9db..9b74a774e5 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -13,11 +13,11 @@ module ActiveRecord
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
+ self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
end
def quote_column_name(name)
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
def quoted_time(value)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index da971fdba7..f4847eb6c0 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -98,6 +98,16 @@ module ActiveRecord
configure_connection
end
+ def self.database_exists?(config)
+ config = config.symbolize_keys
+ if config[:database] == ":memory:"
+ return true
+ else
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
+ File.exist?(database_file)
+ end
+ end
+
def supports_ddl_transactions?
true
end
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index 44b5cfc738..bf31bb7c22 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -141,7 +141,7 @@ module ActiveRecord
config_without_url.delete "url"
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
- elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
+ elsif config["database"] || config["adapter"] || ENV["DATABASE_URL"]
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
else
config.each_pair.map do |sub_spec_name, sub_config|
@@ -153,11 +153,11 @@ module ActiveRecord
def build_url_config(url, configs)
env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
- if original_config = configs.find(&:for_current_env?)
- if original_config.url_config?
- configs
- else
- configs.map do |config|
+ if configs.find(&:for_current_env?)
+ configs.map do |config|
+ if config.url_config?
+ config
+ else
ActiveRecord::DatabaseConfigurations::UrlConfig.new(config.env_name, config.spec_name, url, config.config)
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index c8c06375a3..20cc987d6e 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -353,16 +353,24 @@ module ActiveRecord
class IrreversibleOrderError < ActiveRecordError
end
+ # Superclass for errors that have been aborted (either by client or server).
+ class QueryAborted < StatementInvalid
+ end
+
# LockWaitTimeout will be raised when lock wait timeout exceeded.
class LockWaitTimeout < StatementInvalid
end
# StatementTimeout will be raised when statement timeout exceeded.
- class StatementTimeout < StatementInvalid
+ class StatementTimeout < QueryAborted
end
# QueryCanceled will be raised when canceling statement due to user request.
- class QueryCanceled < StatementInvalid
+ class QueryCanceled < QueryAborted
+ end
+
+ # AdapterTimeout will be raised when database clients times out while waiting from the server.
+ class AdapterTimeout < QueryAborted
end
# UnknownAttributeReference is raised when an unknown and potentially unsafe
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 9db017cded..7edfec9903 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1071,7 +1071,7 @@ module ActiveRecord
def get_all_versions
if schema_migration.table_exists?
- schema_migration.all_versions
+ schema_migration.all_versions.map(&:to_i)
else
[]
end
@@ -1247,7 +1247,7 @@ module ActiveRecord
end
def load_migrated
- @migrated_versions = Set.new(@schema_migration.all_versions)
+ @migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
end
private
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index cab2369b71..ab107742ed 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -2,7 +2,6 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/redefine_method"
-require "active_support/core_ext/object/try"
require "active_support/core_ext/hash/indifferent_access"
module ActiveRecord
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d17acc408c..4d9acc911b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,6 +2,8 @@
require "active_record"
+databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
+
db_namespace = namespace :db do
desc "Set the environment value for the database"
task "environment:set" => :load_config do
@@ -23,7 +25,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Create #{spec_name} database for current environment"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -42,7 +44,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Drop #{spec_name} database for current environment"
task spec_name => [:load_config, :check_protected_environments] do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -101,7 +103,7 @@ db_namespace = namespace :db do
end
namespace :migrate do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Migrate #{spec_name} database for current environment"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -110,7 +112,7 @@ db_namespace = namespace :db do
end
end
- # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
+ desc "Rolls back the database one migration and re-migrates up (options: STEP=x, VERSION=x)."
task redo: :load_config do
raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
@@ -126,7 +128,7 @@ db_namespace = namespace :db do
# desc 'Resets your database using your migrations for the current environment'
task reset: ["db:drop", "db:create", "db:migrate"]
- # desc 'Runs the "up" for a given migration VERSION.'
+ desc 'Runs the "up" for a given migration VERSION.'
task up: :load_config do
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
@@ -142,7 +144,7 @@ db_namespace = namespace :db do
end
namespace :up do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
task spec_name => :load_config do
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
@@ -160,7 +162,7 @@ db_namespace = namespace :db do
end
end
- # desc 'Runs the "down" for a given migration VERSION.'
+ desc 'Runs the "down" for a given migration VERSION.'
task down: :load_config do
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down")
@@ -176,7 +178,7 @@ db_namespace = namespace :db do
end
namespace :down do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
task spec_name => :load_config do
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
@@ -203,7 +205,7 @@ db_namespace = namespace :db do
end
namespace :status do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Display status of migrations for #{spec_name} database"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -228,7 +230,7 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
- # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
+ desc "Drops and recreates the database from db/schema.rb for the current environment and loads the seeds."
task reset: [ "db:drop", "db:setup" ]
# desc "Retrieves the charset for the current environment's database"
@@ -266,7 +268,7 @@ db_namespace = namespace :db do
end
namespace :abort_if_pending_migrations do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
# desc "Raises an error if there are pending migrations for #{spec_name} database"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -295,10 +297,11 @@ db_namespace = namespace :db do
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- ActiveRecord::Tasks::DatabaseTasks.migrate
-
# Skipped when no database
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ if ActiveRecord::Base.dump_schema_after_migration
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+ end
rescue ActiveRecord::NoDatabaseError
ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.spec_name)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 0be9ba7d7b..0a14a33c1d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -340,7 +340,7 @@ module ActiveRecord
}
relation = except(:group).distinct!(false)
- relation.group_values = group_aliases
+ relation.group_values = group_fields
relation.select_values = select_values
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6deb9c7da8..1dbf4808fd 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -354,7 +354,7 @@ module ActiveRecord
conditions = sanitize_forbidden_attributes(conditions)
if distinct_value && offset_value
- relation = limit(1)
+ relation = except(:order).limit!(1)
else
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 87a53966e4..6a181882ae 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -952,7 +952,7 @@ module ActiveRecord
def optimizer_hints!(*args) # :nodoc:
args.flatten!
- self.optimizer_hints_values += args
+ self.optimizer_hints_values |= args
self
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2f7cc07221..f4b1f536b3 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -146,7 +146,11 @@ HEADER
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
type, colspec = column_spec(column)
- tbl.print " t.#{type} #{column.name.inspect}"
+ if type.is_a?(Symbol)
+ tbl.print " t.#{type} #{column.name.inspect}"
+ else
+ tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
+ end
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
tbl.puts
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 58b21d2cc8..dec7fee986 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
def all_versions
- order(:version).pluck(:version).map(&:to_i)
+ order(:version).pluck(:version)
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index aecc9350e8..a78bebf764 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -141,10 +141,19 @@ module ActiveRecord
end
end
- def for_each
+ def setup_initial_database_yaml
return {} unless defined?(Rails)
- databases = Rails.application.config.load_database_yaml
+ begin
+ Rails.application.config.load_database_yaml
+ rescue
+ $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
+
+ {}
+ end
+ end
+
+ def for_each(databases)
database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
# if this is a single database application we don't want tasks for each primary database
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 0d9917a4db..a7e04007a9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -3,6 +3,8 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
+ ER_DB_CREATE_EXISTS = 1007
+
delegate :connection, :establish_connection, to: ActiveRecord::Base
def initialize(configuration)
@@ -14,7 +16,7 @@ module ActiveRecord
connection.create_database configuration["database"], creation_options
establish_connection configuration
rescue ActiveRecord::StatementInvalid => error
- if error.message.include?("database exists")
+ if error.cause.error_number == ER_DB_CREATE_EXISTS
raise DatabaseAlreadyExists
else
raise
diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb
index dece478615..895d394363 100644
--- a/activerecord/lib/arel/predications.rb
+++ b/activerecord/lib/arel/predications.rb
@@ -37,7 +37,7 @@ module Arel # :nodoc: all
def between(other)
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
self.in([])
- elsif open_ended?(other.begin)
+ elsif other.begin.nil? || open_ended?(other.begin)
if other.end.nil? || open_ended?(other.end)
not_in([])
elsif other.exclude_end?
@@ -85,7 +85,7 @@ Passing a range to `#in` is deprecated. Call `#between`, instead.
def not_between(other)
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
not_in([])
- elsif open_ended?(other.begin)
+ elsif other.begin.nil? || open_ended?(other.begin)
if other.end.nil? || open_ended?(other.end)
self.in([])
elsif other.exclude_end?
diff --git a/activerecord/lib/arel/visitors/visitor.rb b/activerecord/lib/arel/visitors/visitor.rb
index d65ac820bc..9066307aed 100644
--- a/activerecord/lib/arel/visitors/visitor.rb
+++ b/activerecord/lib/arel/visitors/visitor.rb
@@ -15,7 +15,7 @@ module Arel # :nodoc: all
attr_reader :dispatch
def self.dispatch_cache
- Hash.new do |hash, klass|
+ @dispatch_cache ||= Hash.new do |hash, klass|
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/annotate_test.rb b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
deleted file mode 100644
index b512540073..0000000000
--- a/activerecord/test/cases/adapters/mysql2/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/post"
-
-class Mysql2AnnotateTest < ActiveRecord::Mysql2TestCase
- fixtures :posts
-
- def test_annotate_wraps_content_in_an_inline_comment
- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
- posts = Post.select(:id).annotate("foo")
- assert posts.first
- end
- end
-
- def test_annotate_is_sanitized
- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
- posts = Post.select(:id).annotate("*/foo/*")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
- posts = Post.select(:id).annotate("**//foo//**")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/ /\* bar \*/}) do
- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
- assert posts.first
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 832f5d61d1..1168b3677e 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -1,11 +1,20 @@
# frozen_string_literal: true
require "cases/helper"
+require "support/schema_dumping_helper"
class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
class EnumTest < ActiveRecord::Base
end
+ def setup
+ EnumTest.connection.create_table :enum_tests, id: false, force: true do |t|
+ t.column :enum_column, "enum('text','blob','tiny','medium','long','unsigned','bigint')"
+ end
+ end
+
def test_enum_limit
column = EnumTest.columns_hash["enum_column"]
assert_equal 8, column.limit
@@ -20,4 +29,9 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
column = EnumTest.columns_hash["enum_column"]
assert_not_predicate column, :bigint?
end
+
+ def test_schema_dumping
+ schema = dump_table_schema "enum_tests"
+ assert_match %r{t\.column "enum_column", "enum\('text','blob','tiny','medium','long','unsigned','bigint'\)"$}, schema
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index e1f7a0b7c5..cfc1823773 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -20,6 +20,18 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
end
end
+ def test_database_exists_returns_false_if_database_does_not_exist
+ config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
+ assert_not ActiveRecord::ConnectionAdapters::Mysql2Adapter.database_exists?(config),
+ "expected database to not exist"
+ end
+
+ def test_database_exists_returns_true_when_the_database_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert ActiveRecord::ConnectionAdapters::Mysql2Adapter.database_exists?(config),
+ "expected database #{config[:database]} to exist"
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
@@ -213,6 +225,21 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
end
end
+ def test_read_timeout_exception
+ ActiveRecord::Base.establish_connection(
+ ActiveRecord::Base.configurations[:arunit].merge("read_timeout" => 1)
+ )
+
+ error = assert_raises(ActiveRecord::AdapterTimeout) do
+ ActiveRecord::Base.connection.execute("SELECT SLEEP(2)")
+ end
+ assert_kind_of ActiveRecord::QueryAborted, error
+
+ assert_equal Mysql2::Error::TimeoutError, error.cause.class
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
private
def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block)
super(@conn, "ex", definition, &block)
diff --git a/activerecord/test/cases/adapters/mysql2/set_test.rb b/activerecord/test/cases/adapters/mysql2/set_test.rb
new file mode 100644
index 0000000000..89107e142f
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/set_test.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class Mysql2SetTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ class SetTest < ActiveRecord::Base
+ end
+
+ def setup
+ SetTest.connection.create_table :set_tests, id: false, force: true do |t|
+ t.column :set_column, "set('text','blob','tiny','medium','long','unsigned','bigint')"
+ end
+ end
+
+ def test_should_not_be_unsigned
+ column = SetTest.columns_hash["set_column"]
+ assert_not_predicate column, :unsigned?
+ end
+
+ def test_should_not_be_bigint
+ column = SetTest.columns_hash["set_column"]
+ assert_not_predicate column, :bigint?
+ end
+
+ def test_schema_dumping
+ schema = dump_table_schema "set_tests"
+ assert_match %r{t\.column "set_column", "set\('text','blob','tiny','medium','long','unsigned','bigint'\)"$}, schema
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index 52e283f247..2041cc308f 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -92,7 +92,7 @@ module ActiveRecord
test "raises StatementTimeout when statement timeout exceeded" do
skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
- assert_raises(ActiveRecord::StatementTimeout) do
+ error = assert_raises(ActiveRecord::StatementTimeout) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
latch2 = Concurrent::CountDownLatch.new
@@ -117,10 +117,11 @@ module ActiveRecord
thread.join
end
end
+ assert_kind_of ActiveRecord::QueryAborted, error
end
test "raises QueryCanceled when canceling statement due to user request" do
- assert_raises(ActiveRecord::QueryCanceled) do
+ error = assert_raises(ActiveRecord::QueryCanceled) do
s = Sample.create!(value: 1)
latch = Concurrent::CountDownLatch.new
@@ -144,6 +145,7 @@ module ActiveRecord
thread.join
end
end
+ assert_kind_of ActiveRecord::QueryAborted, error
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/annotate_test.rb b/activerecord/test/cases/adapters/postgresql/annotate_test.rb
deleted file mode 100644
index 42a2861511..0000000000
--- a/activerecord/test/cases/adapters/postgresql/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/post"
-
-class PostgresqlAnnotateTest < ActiveRecord::PostgreSQLTestCase
- fixtures :posts
-
- def test_annotate_wraps_content_in_an_inline_comment
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("foo")
- assert posts.first
- end
- end
-
- def test_annotate_is_sanitized
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("*/foo/*")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("**//foo//**")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do
- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
- assert posts.first
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 1aa0348879..ff2ab22a80 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -54,8 +54,12 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
type = PostgresqlMoney.type_for_attribute("wealth")
assert_equal(12345678.12, type.cast(+"$12,345,678.12"))
assert_equal(12345678.12, type.cast(+"$12.345.678,12"))
+ assert_equal(12345678.12, type.cast(+"12,345,678.12"))
+ assert_equal(12345678.12, type.cast(+"12.345.678,12"))
assert_equal(-1.15, type.cast(+"-$1.15"))
assert_equal(-2.25, type.cast(+"($2.25)"))
+ assert_equal(-1.15, type.cast(+"-1.15"))
+ assert_equal(-2.25, type.cast(+"(2.25)"))
end
def test_schema_dumping
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 68bc87eaf8..d99593817a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -24,6 +24,18 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_false_when_the_database_does_not_exist
+ config = { database: "non_extant_database", adapter: "postgresql" }
+ assert_not ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.database_exists?(config),
+ "expected database #{config[:database]} to not exist"
+ end
+
+ def test_database_exists_returns_true_when_the_database_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.database_exists?(config),
+ "expected database #{config[:database]} to exist"
+ end
+
def test_primary_key
with_example_table do
assert_equal "id", @connection.primary_key("ex")
diff --git a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb b/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
deleted file mode 100644
index 6567a5eca3..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/post"
-
-class SQLite3AnnotateTest < ActiveRecord::SQLite3TestCase
- fixtures :posts
-
- def test_annotate_wraps_content_in_an_inline_comment
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("foo")
- assert posts.first
- end
- end
-
- def test_annotate_is_sanitized
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("*/foo/*")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("**//foo//**")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do
- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
- assert posts.first
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 508f7d8945..b6d72c7bcd 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -30,6 +30,17 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_false_when_the_database_does_not_exist
+ assert_not SQLite3Adapter.database_exists?(adapter: "sqlite3", database: "non_extant_db"),
+ "expected non_extant_db to not exist"
+ end
+
+ def test_database_exists_returns_true_when_databae_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert SQLite3Adapter.database_exists?(config),
+ "expected #{config[:database]} to exist"
+ end
+
unless in_memory_db?
def test_connect_with_url
original_connection = ActiveRecord::Base.remove_connection
@@ -53,6 +64,11 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_true_for_an_in_memory_db
+ assert SQLite3Adapter.database_exists?(database: ":memory:"),
+ "Expected in memory database to exist"
+ end
+
def test_column_types
owner = Owner.create!(name: "hello".encode("ascii-8bit"))
owner.reload
diff --git a/activerecord/test/cases/annotate_test.rb b/activerecord/test/cases/annotate_test.rb
new file mode 100644
index 0000000000..4d71d28f83
--- /dev/null
+++ b/activerecord/test/cases/annotate_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class AnnotateTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+
+ private
+ def regexp_escape_table_name(name)
+ Regexp.escape(Post.connection.quote_table_name(name))
+ end
+end
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
index c7bd0a053b..7ebb90c6fd 100644
--- a/activerecord/test/cases/arel/attributes/attribute_test.rb
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -638,6 +638,18 @@ module Arel
)
end
+ if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly starting at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(eval("..0")) # eval for backwards compatibility
+
+ node.must_equal Nodes::LessThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
it "can be constructed with a range implicitly ending at Infinity" do
attribute = Attribute.new nil, nil
@@ -839,6 +851,18 @@ module Arel
)
end
+ if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly starting at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(eval("..0")) # eval for backwards compatibility
+
+ node.must_equal Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
it "can be constructed with a range implicitly ending at Infinity" do
attribute = Attribute.new nil, nil
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 7e61ac9d8b..3528ac045f 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "models/author"
+require "models/book"
require "models/bird"
require "models/post"
require "models/comment"
@@ -12,12 +13,14 @@ require "models/developer"
require "models/computer"
require "models/invoice"
require "models/line_item"
+require "models/mouse"
require "models/order"
require "models/parrot"
require "models/pirate"
require "models/project"
require "models/ship"
require "models/ship_part"
+require "models/squeak"
require "models/tag"
require "models/tagging"
require "models/treasure"
@@ -385,6 +388,20 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert_predicate auditlog, :valid?
end
+
+ def test_validation_does_not_validate_non_dirty_association_target
+ mouse = Mouse.create!(name: "Will")
+ Squeak.create!(mouse: mouse)
+
+ mouse.name = nil
+ mouse.save! validate: false
+
+ squeak = Squeak.last
+
+ assert_equal true, squeak.valid?
+ assert_equal true, squeak.mouse.present?
+ assert_equal true, squeak.valid?
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
@@ -1671,6 +1688,10 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
super
@pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
@pirate.birds.create(name: "cookoo")
+
+ @author = Author.new(name: "DHH")
+ @author.published_books.build(name: "Rework", isbn: "1234")
+ @author.published_books.build(name: "Remote", isbn: "1234")
end
test "should automatically validate associations" do
@@ -1679,6 +1700,42 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
assert_not_predicate @pirate, :valid?
end
+
+ test "rollbacks whole transaction and raises ActiveRecord::RecordInvalid when associations fail to #save! due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ exception = assert_raises(ActiveRecord::RecordInvalid) do
+ @author.save!
+ end
+
+ assert_equal("Validation failed: Published books is invalid", exception.message)
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
+
+ test "rollbacks whole transaction when associations fail to #save due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ assert_nothing_raised do
+ result = @author.save
+
+ assert_not(result)
+ end
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
end
class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index cf6e280898..0d0bf39f79 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -146,7 +146,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("posts.id"))}/i) do
Post.find_in_batches(batch_size: 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 85685d1d00..720446b39d 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -93,7 +93,7 @@ if ActiveRecord::Base.connection.prepared_statements
def test_statement_cache_with_in_clause
@connection.clear_cache!
- topics = Topic.where(id: [1, 3])
+ topics = Topic.where(id: [1, 3]).order(:id)
assert_equal [1, 3], topics.map(&:id)
assert_not_includes statement_cache, to_sql_key(topics.arel)
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 525085bb28..dbd1d03c4c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -139,6 +139,13 @@ class CalculationsTest < ActiveRecord::TestCase
end
end
+ def test_should_not_use_alias_for_grouped_field
+ assert_sql(/GROUP BY #{Regexp.escape(Account.connection.quote_table_name("accounts.firm_id"))}/i) do
+ c = Account.group(:firm_id).order("accounts_firm_id").sum(:credit_limit)
+ assert_equal [1, 2, 6, 9], c.keys.compact
+ end
+ end
+
def test_should_order_by_grouped_field
c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 27589966af..843242a897 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def test_establish_connection_uses_spec_name
old_config = ActiveRecord::Base.configurations
- config = { "readonly" => { "adapter" => "sqlite3" } }
+ config = { "readonly" => { "adapter" => "sqlite3", "pool" => "5" } }
ActiveRecord::Base.configurations = config
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 515bf5df06..6372abbf3f 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -244,6 +244,25 @@ module ActiveRecord
assert_equal expected, actual
end
+ def test_no_url_sub_key_with_database_url_doesnt_trample_other_envs
+ ENV["DATABASE_URL"] = "postgres://localhost/baz"
+
+ config = { "default_env" => { "database" => "foo" }, "other_env" => { "url" => "postgres://foohost/bardb" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "database" => "baz",
+ "adapter" => "postgresql",
+ "host" => "localhost"
+ },
+ "other_env" =>
+ { "adapter" => "postgresql",
+ "database" => "bardb",
+ "host" => "foohost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
def test_merge_no_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
@@ -273,6 +292,37 @@ module ActiveRecord
}
assert_equal expected, actual
end
+
+ def test_merge_no_conflicts_with_database_url_and_adapter
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = { "default_env" => { "adapter" => "postgresql", "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url_and_numeric_pool
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = { "default_env" => { "pool" => 5 } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => 5
+ }
+ }
+
+ assert_equal expected, actual
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index bc823fd072..774380d7e0 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -40,7 +40,7 @@ if current_adapter?(:Mysql2Adapter)
end
def test_enum_type_with_value_matching_other_type
- assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')"
+ assert_lookup_type :string, "ENUM('unicode', '8bit', 'none', 'time')"
end
def test_binary_types
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index ca114d468e..1f2058cc0a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -245,7 +245,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_does_not_select_columns_without_alias
- assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
+ c = Topic.connection
+ assert_sql(/SELECT 1 AS one FROM #{Regexp.escape(c.quote_table_name("topics"))}/i) do
Topic.exists?
end
end
@@ -282,6 +283,11 @@ class FinderTest < ActiveRecord::TestCase
assert_not Post.select(:body).distinct.offset(4).exists?
end
+ def test_exists_with_distinct_and_offset_and_eagerload_and_order
+ assert Post.eager_load(:comments).distinct.offset(10).merge(Comment.order(post_id: :asc)).exists?
+ assert_not Post.eager_load(:comments).distinct.offset(11).merge(Comment.order(post_id: :asc)).exists?
+ end
+
# Ensure +exists?+ runs without an error by excluding distinct value.
# See https://github.com/rails/rails/pull/26981.
def test_exists_with_order_and_distinct
@@ -517,6 +523,7 @@ class FinderTest < ActiveRecord::TestCase
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.first
assert_equal expected, Topic.limit(5).first
+ assert_equal expected, Topic.order(nil).first
end
def test_model_class_responds_to_first_bang
@@ -540,6 +547,7 @@ class FinderTest < ActiveRecord::TestCase
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.second
assert_equal expected, Topic.limit(5).second
+ assert_equal expected, Topic.order(nil).second
end
def test_model_class_responds_to_second_bang
@@ -563,6 +571,7 @@ class FinderTest < ActiveRecord::TestCase
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.third
assert_equal expected, Topic.limit(5).third
+ assert_equal expected, Topic.order(nil).third
end
def test_model_class_responds_to_third_bang
@@ -586,6 +595,7 @@ class FinderTest < ActiveRecord::TestCase
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fourth
assert_equal expected, Topic.limit(5).fourth
+ assert_equal expected, Topic.order(nil).fourth
end
def test_model_class_responds_to_fourth_bang
@@ -609,6 +619,7 @@ class FinderTest < ActiveRecord::TestCase
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fifth
assert_equal expected, Topic.limit(5).fifth
+ assert_equal expected, Topic.order(nil).fifth
end
def test_model_class_responds_to_fifth_bang
@@ -777,6 +788,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal expected, clients.first(2)
assert_equal expected, clients.limit(5).first(2)
+ assert_equal expected, clients.order(nil).first(2)
end
def test_implicit_order_column_is_configurable
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 629167e9ed..01e4878c3f 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -471,9 +471,9 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_eager_load_belongs_to_primary_key_quoting
- con = Account.connection
+ c = Account.connection
bind_param = Arel::Nodes::BindParam.new(nil)
- assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = (?:#{Regexp.quote(bind_param.to_sql)}|1)/) do
+ assert_sql(/#{Regexp.escape(c.quote_table_name("companies.id"))} = (?:#{Regexp.escape(bind_param.to_sql)}|1)/i) do
Account.all.merge!(includes: :firm).find(1)
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 53a4963909..79bd6906d1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -536,6 +536,23 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
+ test "query cache is enabled in threads with shared connection" do
+ ActiveRecord::Base.connection_pool.lock_thread = true
+
+ assert_cache :off
+
+ thread_a = Thread.new do
+ middleware { |env|
+ assert_cache :clean
+ [200, {}, nil]
+ }.call({})
+ end
+
+ thread_a.join
+
+ ActiveRecord::Base.connection_pool.lock_thread = false
+ end
+
private
def with_temporary_connection_pool
old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index e0743de94b..e74fb1a098 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -363,6 +363,13 @@ module ActiveRecord
assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql
end
+ def test_does_not_duplicate_optimizer_hints_on_merge
+ escaped_table = Post.connection.quote_table_name("posts")
+ expected = "SELECT /*+ OMGHINT */ #{escaped_table}.* FROM #{escaped_table}"
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
+ assert_equal expected, query
+ end
+
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
def type
:string
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 5df1e3ccf9..1a20fe5dc2 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -308,9 +308,9 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function_other_predicates
- topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order
+ topics = Topic.order("author_name, length(title), id").reverse_order
assert_equal topics(:second).title, topics.first.title
- topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order
+ topics = Topic.order("length(author_name), id, length(title)").reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 49e9be9565..bb7184c5fc 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -33,6 +33,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
schema_info = ActiveRecord::Base.connection.dump_schema_information
assert_match(/20100201010101.*20100301010101/m, schema_info)
+ assert_includes schema_info, "20100101010101"
ensure
ActiveRecord::SchemaMigration.delete_all
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 2645776ab7..4dd8a4a82b 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -51,7 +51,7 @@ class I18nValidationTest < ActiveRecord::TestCase
test "validates_uniqueness_of on generated message #{name}" do
Topic.validates_uniqueness_of :title, validation_options
@topic.title = unique_topic.title
- assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(value: "unique!")]) do
+ assert_called_with(ActiveModel::Error, :generate_message, [:title, :taken, @topic, generate_message_options.merge(value: "unique!")]) do
@topic.valid?
@topic.errors.messages
end
@@ -61,7 +61,7 @@ class I18nValidationTest < ActiveRecord::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_associated on generated message #{name}" do
Topic.validates_associated :replies, validation_options
- assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(value: replied_topic.replies)]) do
+ assert_called_with(ActiveModel::Error, :generate_message, [:replies, :invalid, replied_topic, generate_message_options.merge(value: replied_topic.replies)]) do
replied_topic.save
replied_topic.errors.messages
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index b52b643ad7..da7e4139b1 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -116,6 +116,7 @@ class Author < ActiveRecord::Base
has_many :tags_with_primary_key, through: :posts
has_many :books
+ has_many :published_books, class_name: "PublishedBook"
has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book"
has_many :subscriptions, through: :books
has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index afdda1a81e..43b82e6047 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -24,3 +24,9 @@ class Book < ActiveRecord::Base
"do publish work..."
end
end
+
+class PublishedBook < ActiveRecord::Base
+ self.table_name = "books"
+
+ validates_uniqueness_of :isbn
+end
diff --git a/activerecord/test/models/mouse.rb b/activerecord/test/models/mouse.rb
new file mode 100644
index 0000000000..75a55c125d
--- /dev/null
+++ b/activerecord/test/models/mouse.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Mouse < ActiveRecord::Base
+ has_many :squeaks, autosave: true
+ validates :name, presence: true
+end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 7973219a79..6bab7a1eb9 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -27,7 +27,8 @@ class ShipWithoutNestedAttributes < ActiveRecord::Base
has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id
has_many :parts, class_name: "ShipPart", foreign_key: :ship_id
- validates :name, presence: true
+ validates :name, presence: true, if: -> { true }
+ validates :name, presence: true, if: -> { true }
end
class Prisoner < ActiveRecord::Base
diff --git a/activerecord/test/models/squeak.rb b/activerecord/test/models/squeak.rb
new file mode 100644
index 0000000000..e0a643c238
--- /dev/null
+++ b/activerecord/test/models/squeak.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Squeak < ActiveRecord::Base
+ belongs_to :mouse
+ accepts_nested_attributes_for :mouse
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index b143035213..911ac808c6 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -62,10 +62,6 @@ ActiveRecord::Schema.define do
t.binary :binary_column, limit: 1
end
- create_table :enum_tests, id: false, force: true do |t|
- t.column :enum_column, "ENUM('text','blob','tiny','medium','long','unsigned','bigint')"
- end
-
execute "DROP PROCEDURE IF EXISTS ten"
execute <<~SQL
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index eed18a7b89..dd0ff759b6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -115,7 +115,7 @@ ActiveRecord::Schema.define do
t.column :font_size, :integer, **default_zero
t.column :difficulty, :integer, **default_zero
t.column :cover, :string, default: "hard"
- t.string :isbn
+ t.string :isbn, **case_sensitive_options
t.datetime :published_on
t.index [:author_id, :name], unique: true
t.index :isbn, where: "published_on IS NOT NULL", unique: true
@@ -563,6 +563,10 @@ ActiveRecord::Schema.define do
t.string :type
end
+ create_table :mice, force: true do |t|
+ t.string :name
+ end
+
create_table :movies, force: true, id: false do |t|
t.primary_key :movieid
t.string :name
@@ -843,6 +847,10 @@ ActiveRecord::Schema.define do
end
end
+ create_table :squeaks, force: true do |t|
+ t.integer :mouse_id
+ end
+
create_table :prisoners, force: true do |t|
t.belongs_to :ship
end