diff options
Diffstat (limited to 'activerecord')
21 files changed, 125 insertions, 22 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8642227a2b..184e881b25 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* 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/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/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/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/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/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/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/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/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/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/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index e1f7a0b7c5..df84a40f63 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", []) 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/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/finder_test.rb b/activerecord/test/cases/finder_test.rb index 3752fd42e3..1f2058cc0a 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -283,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 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) |