diff options
Diffstat (limited to 'activerecord/lib/active_record')
14 files changed, 76 insertions, 51 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 3c872d6c1b..bfd1c8402c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -144,7 +144,11 @@ module ActiveRecord # Executes the truncate statement. def truncate(table_name, name = nil) - raise NotImplementedError + execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name + end + + def truncate_tables(*table_names) # :nodoc: + table_names.each { |table_name| truncate(table_name) } end # Executes update +sql+ statement in the context of this connection using diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 202187a047..7aad306d50 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -384,6 +384,11 @@ module ActiveRecord false end + # Does this adapter support optimizer hints? + def supports_optimizer_hints? + false + end + def supports_lazy_transactions? false end 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 a518b897a0..5ceddf449c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -103,6 +103,11 @@ module ActiveRecord mariadb? || version >= "5.7.5" end + # See https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html for more details. + def supports_optimizer_hints? + !mariadb? && version >= "5.7.7" + end + def supports_advisory_locks? true end @@ -269,10 +274,6 @@ module ActiveRecord show_variable "collation_database" end - def truncate(table_name, name = nil) - execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name - end - def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 41633872e2..208934385f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -143,6 +143,12 @@ module ActiveRecord end end + def truncate_tables(*table_names) # :nodoc: + unless table_names.empty? + execute "TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}" + end + end + # Begins a transaction. def begin_db_transaction execute "BEGIN" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 672f11cfcb..29f764e8f4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -259,10 +259,6 @@ module ActiveRecord @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end - def truncate(table_name, name = nil) - exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, [] - end - # Is this connection alive and ready for queries? def active? @lock.synchronize do @@ -351,6 +347,13 @@ module ActiveRecord postgresql_version >= 90400 end + def supports_optimizer_hints? + unless defined?(@has_pg_hint_plan) + @has_pg_hint_plan = extension_available?("pg_hint_plan") + end + @has_pg_hint_plan + end + def supports_lazy_transactions? true end @@ -381,9 +384,12 @@ module ActiveRecord } end + def extension_available?(name) + query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA") + end + def extension_enabled?(name) - res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA") - res.cast_values.first + query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA") end def extensions diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 3004caf82d..8ee7e4c763 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -155,10 +155,6 @@ module ActiveRecord @connection.close rescue nil end - def truncate(table_name, name = nil) - execute "DELETE FROM #{quote_table_name(table_name)}", name - end - def supports_index_sort_order? true end @@ -279,6 +275,10 @@ module ActiveRecord end end + def truncate(table_name, name = nil) # :nodoc: + execute "DELETE FROM #{quote_table_name(table_name)}", name + end + def begin_db_transaction #:nodoc: log("begin transaction", nil) { @connection.transaction } end diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb index 5baaea344b..3833cb2fcf 100644 --- a/activerecord/lib/active_record/insert_all.rb +++ b/activerecord/lib/active_record/insert_all.rb @@ -92,7 +92,15 @@ module ActiveRecord def values_list columns = connection.schema_cache.columns_hash(model.table_name) + + column_names = columns.keys.to_set keys = insert_all.keys.to_set + unknown_columns = keys - column_names + + unless unknown_columns.empty? + raise UnknownAttributeError.new(model.new, unknown_columns.first) + end + types = keys.map { |key| [ key, connection.lookup_cast_type_from_column(columns[key]) ] }.to_h values_list = insert_all.inserts.map do |attributes| diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 7a8d0cb663..86021f0a80 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -14,7 +14,7 @@ module ActiveRecord :find_each, :find_in_batches, :in_batches, :select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, :or, - :having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only, + :having, :create_with, :distinct, :references, :none, :unscope, :optimizer_hints, :merge, :except, :only, :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :pick, :ids ].freeze # :nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ac37f73b76..37179774fa 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,7 +5,7 @@ module ActiveRecord class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, :order, :joins, :left_outer_joins, :references, - :extending, :unscope] + :extending, :unscope, :optimizer_hints] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :skip_query_cache] diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0f2cff4c9c..2603fac8b2 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -901,6 +901,29 @@ module ActiveRecord self end + # Specify optimizer hints to be used in the SELECT statement. + # + # Example (for MySQL): + # + # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)") + # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics` + # + # Example (for PostgreSQL with pg_hint_plan): + # + # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)") + # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics" + def optimizer_hints(*args) + check_if_method_has_arguments!(:optimizer_hints, args) + spawn.optimizer_hints!(*args) + end + + def optimizer_hints!(*args) # :nodoc: + args.flatten! + + self.optimizer_hints_values += args + self + end + # Reverse the existing order clause on the relation. # # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC' @@ -977,6 +1000,7 @@ module ActiveRecord build_select(arel) + arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty? arel.distinct(distinct_value) arel.from(build_from) unless from_clause.empty? arel.lock(lock_value) if lock_value diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index a8433fa0db..5a46003732 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -185,14 +185,17 @@ module ActiveRecord def truncate_tables(configuration) ActiveRecord::Base.connected_to(database: { truncation: configuration }) do table_names = ActiveRecord::Base.connection.tables - internal_table_names = [ + table_names -= [ ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name ] - class_for_adapter(configuration["adapter"]).new(configuration).truncate_tables(*table_names.without(*internal_table_names)) + ActiveRecord::Base.connection.disable_referential_integrity do + ActiveRecord::Base.connection.truncate_tables(*table_names) + end unless table_names.empty? end end + private :truncate_tables def truncate_all(environment = env) ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config| diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index f2b4ead98d..1c1b29b5e1 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -31,16 +31,6 @@ module ActiveRecord connection.recreate_database configuration["database"], creation_options end - def truncate_tables(*table_names) - return if table_names.empty? - - ActiveRecord::Base.connection.disable_referential_integrity do - table_names.each do |table_name| - ActiveRecord::Base.connection.truncate(table_name) - end - end - end - def charset connection.charset end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index dc368eb97d..8acb11f75f 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -48,18 +48,6 @@ module ActiveRecord create true end - def truncate_tables(*table_names) - return if table_names.empty? - - ActiveRecord::Base.connection.disable_referential_integrity do - quoted_table_names = table_names.map do |table_name| - ActiveRecord::Base.connection.quote_table_name(table_name) - end - - ActiveRecord::Base.connection.execute "TRUNCATE TABLE #{quoted_table_names.join(", ")}" - end - end - def structure_dump(filename, extra_flags) set_psql_env diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index cedbae6b7f..a82cea80ca 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -33,16 +33,6 @@ module ActiveRecord create end - def truncate_tables(*table_names) - return if table_names.empty? - - ActiveRecord::Base.connection.disable_referential_integrity do - table_names.each do |table_name| - ActiveRecord::Base.connection.truncate(table_name) - end - end - end - def charset connection.encoding end |