aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb9
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb67
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb35
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb5
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb97
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb88
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb167
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb254
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb79
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb92
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb116
-rw-r--r--activerecord/lib/active_record/core.rb1
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb18
-rw-r--r--activerecord/lib/active_record/fixtures.rb29
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb23
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb12
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb5
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb26
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb8
-rw-r--r--activerecord/lib/active_record/railties/databases.rake7
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb11
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb55
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb88
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb41
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb59
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb49
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb7
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb13
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb20
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
64 files changed, 1001 insertions, 926 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4606c91ffd..e52c2004f3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1647,6 +1647,9 @@ module ActiveRecord
# +:inverse_of+ to avoid an extra query during validation.
# NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
# you don't want to have association presence validated, use <tt>optional: true</tt>.
+ # [:default]
+ # Provide a callable (i.e. proc or lambda) to specify that the association should
+ # be initialized with a particular record before validation.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1660,6 +1663,7 @@ module ActiveRecord
# belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
# belongs_to :user, optional: true
+ # belongs_to :account, default: -> { company.account }
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 64b2311911..0e61dbfb00 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -21,6 +21,10 @@ module ActiveRecord
self.target = record
end
+ def default(&block)
+ writer(owner.instance_exec(&block)) if reader.nil?
+ end
+
def reset
super
@updated = false
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index a1609ab0fb..2b9dd8aae8 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:polymorphic, :touch, :counter_cache, :optional]
+ super + [:polymorphic, :touch, :counter_cache, :optional, :default]
end
def self.valid_dependent_options
@@ -16,6 +16,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
super
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
add_touch_callbacks(model, reflection) if reflection.options[:touch]
+ add_default_callbacks(model, reflection) if reflection.options[:default]
end
def self.define_accessors(mixin, reflection)
@@ -118,6 +119,12 @@ module ActiveRecord::Associations::Builder # :nodoc:
model.after_destroy callback.(:changes_to_save)
end
+ def self.add_default_callbacks(model, reflection)
+ model.before_validation lambda { |o|
+ o.association(reflection.name).default(&reflection.options[:default])
+ }
+ end
+
def self.add_destroy_callbacks(model, reflection)
model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 77282e6463..62c944fce3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -280,35 +280,6 @@ module ActiveRecord
replace_on_target(record, index, skip_callbacks, &block)
end
- def replace_on_target(record, index, skip_callbacks)
- callback(:before_add, record) unless skip_callbacks
-
- begin
- if index
- record_was = target[index]
- target[index] = record
- else
- target << record
- end
-
- set_inverse_instance(record)
-
- yield(record) if block_given?
- rescue
- if index
- target[index] = record_was
- else
- target.delete(record)
- end
-
- raise
- end
-
- callback(:after_add, record) unless skip_callbacks
-
- record
- end
-
def scope
scope = super
scope.none! if null_scope?
@@ -385,15 +356,19 @@ module ActiveRecord
transaction do
add_to_target(build_record(attributes)) do |record|
yield(record) if block_given?
- insert_record(record, true, raise)
+ insert_record(record, true, raise) { @_was_loaded = loaded? }
end
end
end
end
# Do the relevant stuff to insert the given record into the association collection.
- def insert_record(record, validate = true, raise = false)
- raise NotImplementedError
+ def insert_record(record, validate = true, raise = false, &block)
+ if raise
+ record.save!(validate: validate, &block)
+ else
+ record.save(validate: validate, &block)
+ end
end
def create_scope
@@ -448,19 +423,41 @@ module ActiveRecord
end
end
- def concat_records(records, should_raise = false)
+ def concat_records(records, raise = false)
result = true
records.each do |record|
raise_on_type_mismatch!(record)
- add_to_target(record) do |rec|
- result &&= insert_record(rec, true, should_raise) unless owner.new_record?
+ add_to_target(record) do
+ result &&= insert_record(record, true, raise) { @_was_loaded = loaded? } unless owner.new_record?
end
end
result && records
end
+ def replace_on_target(record, index, skip_callbacks)
+ callback(:before_add, record) unless skip_callbacks
+
+ set_inverse_instance(record)
+
+ @_was_loaded = true
+
+ yield(record) if block_given?
+
+ if index
+ target[index] = record
+ elsif @_was_loaded || !loaded?
+ target << record
+ end
+
+ callback(:after_add, record) unless skip_callbacks
+
+ record
+ ensure
+ @_was_loaded = nil
+ end
+
def callback(method, record)
callbacks_for(method).each do |callback|
callback.call(method, owner, record)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 55bf2e0ff0..74a4d515c2 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# # #<Pet id: nil, name: "Choo-Choo">
# # ]
#
- # person.pets.select(:id, :name )
+ # person.pets.select(:id, :name)
# # => [
# # #<Pet id: 1, name: "Fancy-Fancy">,
# # #<Pet id: 2, name: "Spook">,
@@ -743,10 +743,6 @@ module ActiveRecord
# # ]
#--
- def uniq
- load_target.uniq
- end
-
def calculate(operation, column_name)
null_scope? ? scope.calculate(operation, column_name) : super
end
@@ -1121,10 +1117,23 @@ module ActiveRecord
SpawnMethods,
].flat_map { |klass|
klass.public_instance_methods(false)
- } - self.public_instance_methods(false) + [:scoping]
+ } - self.public_instance_methods(false) - [:select] + [:scoping]
delegate(*delegate_methods, to: :scope)
+ module DelegateExtending # :nodoc:
+ private
+ def method_missing(method, *args, &block)
+ extending_values = association_scope.extending_values
+ if extending_values.any? && (extending_values - self.class.included_modules).any?
+ self.class.include(*extending_values)
+ public_send(method, *args, &block)
+ else
+ super
+ end
+ end
+ end
+
private
def find_nth_with_limit(index, limit)
@@ -1145,20 +1154,16 @@ module ActiveRecord
@association.find_from_target?
end
+ def association_scope
+ @association.association_scope
+ end
+
def exec_queries
load_target
end
def respond_to_missing?(method, _)
- scope.respond_to?(method) || super
- end
-
- def method_missing(method, *args, &block)
- if scope.respond_to?(method)
- scope.public_send(method, *args, &block)
- else
- super
- end
+ association_scope.respond_to?(method) || super
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index b413eb3f9c..10ca0e47ff 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -31,12 +31,7 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
-
- if raise
- record.save!(validate: validate)
- else
- record.save(validate: validate)
- end
+ super
end
def empty?
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index c4a7fe4432..53ffb3b68d 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -39,11 +39,7 @@ module ActiveRecord
ensure_not_nested
if record.new_record? || record.has_changes_to_save?
- if raise
- record.save!(validate: validate)
- else
- return unless record.save(validate: validate)
- end
+ return unless super
end
save_through_record(record)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 6aa414ba6b..bd5003d63a 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -282,7 +282,7 @@ module ActiveRecord
#{attr_name} is not an attribute known to Active Record.
This behavior is deprecated and will be removed in the next
version of Rails. If you'd like #{attr_name} to be managed
- by Active Record, add `attribute :#{attr_name} to your class.
+ by Active Record, add `attribute :#{attr_name}` to your class.
EOW
mutations_from_database.deprecated_force_change(attr_name)
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 6bccbc06cd..607c54e481 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -181,6 +181,7 @@ module ActiveRecord
if reflection.collection?
before_save :before_save_collection_association
+ after_save :after_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
@@ -371,6 +372,10 @@ module ActiveRecord
true
end
+ def after_save_collection_association
+ @new_record_before_save = false
+ end
+
# Saves any new associated records, or all loaded autosave associations if
# <tt>:autosave</tt> is enabled on the association.
#
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 43784b70e3..1e1de1863a 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -7,17 +7,27 @@ module ActiveRecord
if collection.loaded?
size = collection.size
if size > 0
- timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
end
else
column_type = type_for_attribute(timestamp_column.to_s)
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
- query = collection
- .unscope(:select)
- .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
- .unscope(:order)
- result = connection.select_one(query)
+ if collection.limit_value || collection.offset_value
+ query = collection.spawn
+ query.select_values = [column]
+ subquery_alias = "subquery_for_cache_key"
+ subquery_column = "#{subquery_alias}.#{timestamp_column}"
+ subquery = query.arel.as(subquery_alias)
+ arel = Arel::SelectManager.new(query.engine).project(select_values % subquery_column).from(subquery)
+ else
+ query = collection.unscope(:order)
+ query.select_values = [select_values % column]
+ arel = query.arel
+ end
+
+ result = connection.select_one(arel, nil, query.bound_attributes)
if result.blank?
size = 0
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 3f2e86a98d..61bf5477aa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -506,14 +506,16 @@ module ActiveRecord
# +conn+: an AbstractAdapter object, which was obtained by earlier by
# calling #checkout on this pool.
def checkin(conn)
- synchronize do
- remove_connection_from_thread_cache conn
+ conn.lock.synchronize do
+ synchronize do
+ remove_connection_from_thread_cache conn
- conn._run_checkin_callbacks do
- conn.expire
- end
+ conn._run_checkin_callbacks do
+ conn.expire
+ end
- @available.add conn
+ @available.add conn
+ end
end
end
@@ -825,7 +827,7 @@ module ActiveRecord
# end
#
# class Book < ActiveRecord::Base
- # establish_connection "library_db"
+ # establish_connection :library_db
# end
#
# class ScaryBook < Book
@@ -857,9 +859,9 @@ module ActiveRecord
# All Active Record models use this handler to determine the connection pool that they
# should use.
#
- # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
# about the model. The model needs to pass a specification name to the handler,
- # in order to lookup the correct connection pool.
+ # in order to look up the correct connection pool.
class ConnectionHandler
def initialize
# These caches are keyed by spec.name (ConnectionSpecification#name).
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 769f488469..c6811a4802 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -137,9 +137,10 @@ module ActiveRecord
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
- def supports_statement_cache?
- false
+ def supports_statement_cache? # :nodoc:
+ true
end
+ deprecate :supports_statement_cache?
# Runs the given block in a database transaction, and returns the result
# of the block.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index e5a24b2aca..f0c0fbab6c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -61,17 +61,6 @@ module ActiveRecord
lookup_cast_type(column.sql_type)
end
- def fetch_type_metadata(sql_type)
- cast_type = lookup_cast_type(sql_type)
- SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- end
-
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
@@ -152,11 +141,18 @@ module ActiveRecord
"'#{quote_string(value.to_s)}'"
end
- private
-
- def type_casted_binds(binds)
+ def type_casted_binds(binds) # :nodoc:
+ if binds.first.is_a?(Array)
+ binds.map { |column, value| type_cast(value, column) }
+ else
binds.map { |attr| type_cast(attr.value_for_database) }
end
+ end
+
+ private
+ def lookup_cast_type(sql_type)
+ type_map.lookup(sql_type)
+ end
def id_value_for_database(value)
if primary_key = value.class.primary_key
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index c48a4acff8..a4fecc4a8e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -133,5 +133,6 @@ module ActiveRecord
end
end
end
+ SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc:
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 4682afc188..46d7f84efd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -2,8 +2,33 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
- # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
+ # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
+ class IndexDefinition # :nodoc:
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment
+
+ def initialize(
+ table, name,
+ unique = false,
+ columns = [],
+ lengths: {},
+ orders: {},
+ where: nil,
+ type: nil,
+ using: nil,
+ comment: nil
+ )
+ @table = table
+ @name = name
+ @unique = unique
+ @columns = columns
+ @lengths = lengths
+ @orders = orders
+ @where = where
+ @type = type
+ @using = using
+ @comment = comment
+ end
+ end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
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 a497a354f7..13629dee7f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -31,6 +31,8 @@ module ActiveRecord
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
+ select_values(data_source_sql, "SCHEMA")
+ rescue NotImplementedError
tables | views
end
@@ -39,12 +41,14 @@ module ActiveRecord
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
+ select_values(data_source_sql(name), "SCHEMA").any? if name.present?
+ rescue NotImplementedError
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables
- raise NotImplementedError, "#tables is not implemented"
+ select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end
# Checks to see if the table +table_name+ exists on the database.
@@ -52,12 +56,14 @@ module ActiveRecord
# table_exists?(:developers)
#
def table_exists?(table_name)
+ select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
+ rescue NotImplementedError
tables.include?(table_name.to_s)
end
# Returns an array of view names defined in the database.
def views
- raise NotImplementedError, "#views is not implemented"
+ select_values(data_source_sql(type: "VIEW"), "SCHEMA")
end
# Checks to see if the view +view_name+ exists on the database.
@@ -65,6 +71,8 @@ module ActiveRecord
# view_exists?(:ebooks)
#
def view_exists?(view_name)
+ select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
+ rescue NotImplementedError
views.include?(view_name.to_s)
end
@@ -97,10 +105,12 @@ module ActiveRecord
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
end
- # Returns an array of Column objects for the table specified by +table_name+.
- # See the concrete implementation for details on the expected parameter values.
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name)
- raise NotImplementedError, "#columns is not implemented"
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |field|
+ new_column_from_field(table_name, field)
+ end
end
# Checks to see if a column exists in a given table.
@@ -966,16 +976,6 @@ module ActiveRecord
foreign_key_for(from_table, options_or_to_table).present?
end
- def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
- return unless supports_foreign_keys?
- foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
- end
-
- def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
- foreign_key_for(from_table, options_or_to_table) || \
- raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
- end
-
def foreign_key_column_for(table_name) # :nodoc:
prefix = Base.table_name_prefix
suffix = Base.table_name_suffix
@@ -995,19 +995,6 @@ module ActiveRecord
insert_versions_sql(versions)
end
- def insert_versions_sql(versions) # :nodoc:
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
-
- if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n"
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
- sql << ";\n\n"
- sql
- else
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
- end
- end
-
def initialize_schema_migrations_table # :nodoc:
ActiveRecord::SchemaMigration.create_table
end
@@ -1253,6 +1240,10 @@ module ActiveRecord
end
end
+ def schema_creation
+ SchemaCreation.new(self)
+ end
+
def create_table_definition(*args)
TableDefinition.new(*args)
end
@@ -1261,6 +1252,17 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def fetch_type_metadata(sql_type)
+ cast_type = lookup_cast_type(sql_type)
+ SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ end
+
def index_column_names(column_names)
if column_names.is_a?(String) && /\W/.match?(column_names)
column_names
@@ -1285,6 +1287,24 @@ module ActiveRecord
end
end
+ def foreign_key_for(from_table, options_or_to_table = {})
+ return unless supports_foreign_keys?
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
+ end
+
+ def foreign_key_for!(from_table, options_or_to_table = {})
+ foreign_key_for(from_table, options_or_to_table) || \
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "CASCADE"; :cascade
+ when "SET NULL"; :nullify
+ when "RESTRICT"; :restrict
+ end
+ end
+
def validate_index_length!(table_name, new_name, internal = false)
max_index_length = internal ? index_name_length : allowed_index_name_length
@@ -1304,6 +1324,27 @@ module ActiveRecord
def can_remove_index_by_name?(options)
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
+
+ def insert_versions_sql(versions)
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
+
+ if versions.is_a?(Array)
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
+ sql << ";\n\n"
+ sql
+ else
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
+ end
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ raise NotImplementedError
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ raise NotImplementedError
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6bb072dd73..19b7821494 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -149,57 +149,67 @@ module ActiveRecord
end
def begin_transaction(options = {})
- run_commit_callbacks = !current_transaction.joinable?
- transaction =
- if @stack.empty?
- RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
- else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
- run_commit_callbacks: run_commit_callbacks)
- end
+ @connection.lock.synchronize do
+ run_commit_callbacks = !current_transaction.joinable?
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
+ run_commit_callbacks: run_commit_callbacks)
+ end
- @stack.push(transaction)
- transaction
+ @stack.push(transaction)
+ transaction
+ end
end
def commit_transaction
- transaction = @stack.last
+ @connection.lock.synchronize do
+ transaction = @stack.last
- begin
- transaction.before_commit_records
- ensure
- @stack.pop
- end
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
- transaction.commit
- transaction.commit_records
+ transaction.commit
+ transaction.commit_records
+ end
end
def rollback_transaction(transaction = nil)
- transaction ||= @stack.pop
- transaction.rollback
- transaction.rollback_records
+ @connection.lock.synchronize do
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
+ end
end
def within_new_transaction(options = {})
- transaction = begin_transaction options
- yield
- rescue Exception => error
- if transaction
- rollback_transaction
- after_failure_actions(transaction, error)
- end
- raise
- ensure
- unless error
- if Thread.current.status == "aborting"
- rollback_transaction if transaction
- else
- begin
- commit_transaction
- rescue Exception
- rollback_transaction(transaction) unless transaction.state.completed?
- raise
+ @connection.lock.synchronize do
+ begin
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
+ raise
+ ensure
+ unless error
+ if Thread.current.status == "aborting"
+ rollback_transaction if transaction
+ else
+ begin
+ commit_transaction
+ rescue Exception
+ rollback_transaction(transaction) unless transaction.state.completed?
+ raise
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ef1d9f81a9..85d6fbe8b3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -74,7 +74,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :owner, :logger
+ attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
def self.type_cast_config_to_integer(config)
@@ -93,8 +93,6 @@ module ActiveRecord
end
end
- attr_reader :prepared_statements
-
def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@@ -142,34 +140,10 @@ module ActiveRecord
end
end
- def collector
- if prepared_statements
- SQLString.new
- else
- BindCollector.new
- end
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::ToSql.new(self)
- end
-
def valid_type?(type) # :nodoc:
!native_database_types[type].nil?
end
- def schema_creation
- SchemaCreation.new self
- end
-
- # Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) # :nodoc:
- table_name = table_name.to_s
- column_definitions(table_name).map do |field|
- new_column_from_field(table_name, field)
- end
- end
-
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
@@ -447,7 +421,7 @@ module ActiveRecord
# Provides access to the underlying database driver for this adapter. For
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
- # and a PGconn object in case of PostgreSQLAdapter.
+ # and a PG::Connection object in case of PostgreSQLAdapter.
#
# This is useful for when you need to call a proprietary method such as
# PostgreSQL's lo_* methods.
@@ -483,14 +457,6 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
- end
-
- def lookup_cast_type(sql_type) # :nodoc:
- type_map.lookup(sql_type)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
visitor.accept(node, collector).value
end
@@ -637,6 +603,18 @@ module ActiveRecord
columns(table_name).detect { |c| c.name == column_name } ||
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
end
+
+ def collector
+ if prepared_statements
+ SQLString.new
+ else
+ BindCollector.new
+ end
+ end
+
+ def arel_visitor
+ Arel::Visitors::ToSql.new(self)
+ end
end
end
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 e3b6327dd8..31cf2b4dbf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,7 @@ require "active_record/connection_adapters/mysql/quoting"
require "active_record/connection_adapters/mysql/schema_creation"
require "active_record/connection_adapters/mysql/schema_definitions"
require "active_record/connection_adapters/mysql/schema_dumper"
+require "active_record/connection_adapters/mysql/schema_statements"
require "active_record/connection_adapters/mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -15,19 +16,12 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include MySQL::Quoting
include MySQL::ColumnDumper
+ include MySQL::SchemaStatements
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- MySQL::SchemaCreation.new(self)
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::MySQL.new(self)
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -54,9 +48,6 @@ module ActiveRecord
json: { name: "json" },
}
- INDEX_TYPES = [:fulltext, :spatial]
- INDEX_USINGS = [:btree, :hash]
-
class StatementPool < ConnectionAdapters::StatementPool
private def dealloc(stmt)
stmt[:stmt].close
@@ -73,14 +64,6 @@ module ActiveRecord
end
end
- CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
-
- def internal_string_options_for_primary_key # :nodoc:
- super.tap { |options|
- options[:collation] = collation.sub(/\A[^_]+/, "utf8") if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
- }
- end
-
def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
@@ -93,16 +76,8 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
- # Technically MySQL allows to create indexes with the sort order syntax
- # but at the moment (5.5) it doesn't yet implement them
def supports_index_sort_order?
- true
+ !mariadb? && version >= "8.0.1"
end
def supports_transaction_isolation?
@@ -169,10 +144,6 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(*args) #:nodoc:
- MySQL::Column.new(*args)
- end
-
# Must return the MySQL error number from the exception, if the exception has an
# error number.
def error_number(exception) # :nodoc:
@@ -310,109 +281,18 @@ module ActiveRecord
show_variable "collation_database"
end
- def tables # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- schema, name = extract_schema_qualified_name(view_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
- indexes = []
- current_index = nil
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
- each_hash(result) do |row|
- if current_index != row[:Key_name]
- next if row[:Key_name] == "PRIMARY" # skip the primary key
- current_index = row[:Key_name]
-
- mysql_index_type = row[:Index_type].downcase.to_sym
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
- end
-
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
- end
- end
-
- indexes
- end
-
- def new_column_from_field(table_name, field) # :nodoc:
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
- end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
- end
-
def table_comment(table_name) # :nodoc:
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT table_comment
FROM information_schema.tables
- WHERE table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ WHERE table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
SQL
end
@@ -512,7 +392,7 @@ module ActiveRecord
def foreign_keys(table_name)
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
@@ -525,9 +405,9 @@ module ActiveRecord
JOIN information_schema.referential_constraints rc
USING (constraint_schema, constraint_name)
WHERE fk.referenced_column_name IS NOT NULL
- AND fk.table_schema = #{quote(schema)}
- AND fk.table_name = #{quote(name)}
- AND rc.table_name = #{quote(name)}
+ AND fk.table_schema = #{scope[:schema]}
+ AND fk.table_name = #{scope[:name]}
+ AND rc.table_name = #{scope[:name]}
SQL
fk_info.map do |row|
@@ -599,14 +479,14 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
- AND table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ AND table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
ORDER BY ordinal_position
SQL
end
@@ -707,10 +587,6 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, extra = "")
- MySQL::TypeMetadata.new(super(sql_type), extra: extra)
- end
-
def add_index_length(quoted_columns, **options)
if length = options[:length]
case length
@@ -913,19 +789,12 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- end
- end
-
def create_table_info(table_name) # :nodoc:
select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(*args) # :nodoc:
- MySQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::MySQL.new(self)
end
def mismatched_foreign_key(message)
@@ -940,12 +809,6 @@ module ActiveRecord
)
end
- def extract_schema_qualified_name(string) # :nodoc:
- schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
- schema, name = @config[:database], schema unless name
- [schema, name]
- end
-
def integer_to_sql(limit) # :nodoc:
case limit
when 1; "tinyint"
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 3e0afd9761..e2ba0ba1a0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -55,13 +55,14 @@ module ActiveRecord
def extract_expression_for_virtual_column(column)
if mariadb?
create_table_info = create_table_info(column.table_name)
- if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)} AS \((?<expression>.+?)\) #{column.extra}/m =~ create_table_info
+ if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
$~[:expression].inspect
end
else
+ scope = quoted_scope(column.table_name)
sql = "SELECT generation_expression FROM information_schema.columns" \
- " WHERE table_schema = #{quote(@config[:database])}" \
- " AND table_name = #{quote(column.table_name)}" \
+ " WHERE table_schema = #{scope[:schema]}" \
+ " AND table_name = #{scope[:name]}" \
" AND column_name = #{quote(column.name)}"
select_value(sql, "SCHEMA").inspect
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
new file mode 100644
index 0000000000..f9e1e046ea
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -0,0 +1,122 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ indexes = []
+ current_index = nil
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
+ each_hash(result) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == "PRIMARY" # skip the primary key
+ current_index = row[:Key_name]
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ case mysql_index_type
+ when :fulltext, :spatial
+ index_type = mysql_index_type
+ when :btree, :hash
+ index_using = mysql_index_type
+ end
+
+ indexes << IndexDefinition.new(
+ row[:Table],
+ row[:Key_name],
+ row[:Non_unique].to_i == 0,
+ type: index_type,
+ using: index_using,
+ comment: row[:Index_comment].presence
+ )
+ end
+
+ indexes.last.columns << row[:Column_name]
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
+ indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ end
+ end
+
+ indexes
+ end
+
+ def internal_string_options_for_primary_key
+ super.tap do |options|
+ if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
+ options[:collation] = collation.sub(/\A[^_]+/, "utf8")
+ end
+ end
+ end
+
+ private
+ CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
+
+ def schema_creation
+ MySQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ MySQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
+ end
+
+ MySQL::Column.new(
+ field[:Field],
+ default,
+ type_metadata,
+ field[:Null] == "YES",
+ table_name,
+ default_function,
+ field[:Collation],
+ comment: field[:Comment].presence
+ )
+ end
+
+ def fetch_type_metadata(sql_type, extra = "")
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
+ end
+
+ def extract_foreign_key_action(specifier)
+ super unless specifier == "RESTRICT"
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+
+ sql = "SELECT table_name FROM information_schema.tables"
+ sql << " WHERE table_schema = #{scope[:schema]}"
+ sql << " AND table_name = #{scope[:name]}" if scope[:name]
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "database()"
+ scope[:name] = quote(name) if name
+ scope[:type] = quote(type) if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
+ schema, name = nil, schema unless name
+ [schema, name]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
index 24dcf852e1..9ad6a6c0d0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :extra
def initialize(type_metadata, extra: "")
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 45e400b75b..af55cfe2f6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -10,8 +10,6 @@ module ActiveRecord
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
-
- config[:username] = "root" if config[:username].nil?
config[:flags] ||= 0
if config[:flags].kind_of? Array
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 8f9d6e7f9b..702fa8175c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def deserialize(value)
return if value.nil?
return value.to_s if value.is_a?(Type::Binary::Data)
- PGconn.unescape_bytea(super)
+ PG::Connection.unescape_bytea(super)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 6663448a99..da8d0c6992 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -33,7 +33,7 @@ module ActiveRecord
# Quotes schema names for use in SQL queries.
def quote_schema_name(name)
- PGconn.quote_ident(name)
+ PG::Connection.quote_ident(name)
end
def quote_table_name_for_assignment(table, attr)
@@ -42,7 +42,7 @@ module ActiveRecord
# Quotes column names for use in SQL queries.
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze
+ @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
end
# Quote date/time values for use in SQL input.
@@ -77,6 +77,9 @@ module ActiveRecord
end
private
+ def lookup_cast_type(sql_type)
+ super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
+ end
def _quote(value)
case value
@@ -105,7 +108,7 @@ module ActiveRecord
case value
when Type::Binary::Data
# Return a bind param hash with format as binary.
- # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
# for more information
{ value: value.to_s, format: 1 }
when OID::Xml::Data, OID::Bit::Data
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index afef0da5c7..5b483ad4ab 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -54,81 +54,13 @@ module ActiveRecord
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
- # Returns the list of all tables in the schema search path.
- def tables
- select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA")
- end
-
- def data_sources # :nodoc
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- def views # :nodoc:
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- # Returns true if table exists.
- # If the schema is not specified as part of +name+ then it will only find tables within
- # the current schema search path (regardless of permissions to access tables in other schemas)
- def table_exists?(name)
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT tablename
- FROM pg_tables
- WHERE tablename = #{quote(name.identifier)}
- AND schemaname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def data_source_exists?(name) # :nodoc:
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def view_exists?(view_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(view_name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
def schema_exists?(name)
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", "SCHEMA").to_i > 0
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
end
# Verifies existence of an index with a given name.
@@ -138,8 +70,8 @@ module ActiveRecord
Passing default to #index_name_exists? is deprecated without replacement.
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
- index = Utils.extract_schema_qualified_name(index_name.to_s)
+ table = quoted_scope(table_name)
+ index = quoted_scope(index_name)
select_value(<<-SQL, "SCHEMA").to_i > 0
SELECT COUNT(*)
@@ -148,9 +80,9 @@ module ActiveRecord
INNER JOIN pg_class i ON d.indexrelid = i.oid
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
- AND i.relname = '#{index.identifier}'
- AND t.relname = '#{table.identifier}'
- AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
+ AND i.relname = #{index[:name]}
+ AND t.relname = #{table[:name]}
+ AND n.nspname = #{index[:schema]}
SQL
end
@@ -162,7 +94,7 @@ module ActiveRecord
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
+ scope = quoted_scope(table_name)
result = query(<<-SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
@@ -176,8 +108,8 @@ module ActiveRecord
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
AND d.indisprimary = 'f'
- AND t.relname = '#{table.identifier}'
- AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
+ AND t.relname = #{scope[:name]}
+ AND n.nspname = #{scope[:schema]}
ORDER BY i.relname
SQL
@@ -208,27 +140,17 @@ module ActiveRecord
]
end
- IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
- end.compact
- end
-
- def new_column_from_field(table_name, field) # :nondoc:
- column_name, type, default, notnull, oid, fmod, collation, comment = field
- oid = oid.to_i
- fmod = fmod.to_i
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
- default_value = extract_value_from_default(default)
- default_function = extract_default_function(default_value, default)
- PostgreSQLColumn.new(
- column_name,
- default_value,
- type_metadata,
- !notnull,
- table_name,
- default_function,
- collation,
- comment: comment.presence
- )
+ IndexDefinition.new(
+ table_name,
+ index_name,
+ unique,
+ columns,
+ orders: orders,
+ where: where,
+ using: using.to_sym,
+ comment: comment.presence
+ )
+ end
end
def table_options(table_name) # :nodoc:
@@ -239,22 +161,22 @@ module ActiveRecord
# Returns a comment stored in database for given table
def table_comment(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
- if name.identifier
+ scope = quoted_scope(table_name, type: "BASE TABLE")
+ if scope[:name]
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
FROM pg_catalog.pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relname = #{quote(name.identifier)}
- AND c.relkind IN ('r') -- (r)elation/table
- AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ WHERE c.relname = #{scope[:name]}
+ AND c.relkind IN (#{scope[:type]})
+ AND n.nspname = #{scope[:schema]}
SQL
end
end
# Returns the current database name.
def current_database
- select_value("select current_database()", "SCHEMA")
+ select_value("SELECT current_database()", "SCHEMA")
end
# Returns the current schema name.
@@ -430,18 +352,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT column_name
- FROM information_schema.key_column_usage kcu
- JOIN information_schema.table_constraints tc
- ON kcu.table_name = tc.table_name
- AND kcu.table_schema = tc.table_schema
- AND kcu.constraint_name = tc.constraint_name
- WHERE constraint_type = 'PRIMARY KEY'
- AND kcu.table_name = #{quote(name.identifier)}
- AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- ORDER BY kcu.ordinal_position
+ SELECT a.attname
+ FROM (
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
+ FROM pg_index
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
+ AND indisprimary
+ ) i
+ JOIN pg_attribute a
+ ON a.attrelid = i.indrelid
+ AND a.attnum = i.indkey[i.idx]
+ ORDER BY i.idx
SQL
end
@@ -579,6 +501,7 @@ module ActiveRecord
end
def foreign_keys(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
@@ -588,8 +511,8 @@ module ActiveRecord
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
JOIN pg_namespace t3 ON c.connamespace = t3.oid
WHERE c.contype = 'f'
- AND t1.relname = #{quote(table_name)}
- AND t3.nspname = ANY (current_schemas(false))
+ AND t1.relname = #{scope[:name]}
+ AND t3.nspname = #{scope[:schema]}
ORDER BY c.conname
SQL
@@ -607,14 +530,6 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "c"; :cascade
- when "n"; :nullify
- when "r"; :restrict
- end
- end
-
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
@@ -662,17 +577,84 @@ module ActiveRecord
[super, *order_columns].join(", ")
end
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
- simple_type = SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
- end
+ private
+ def schema_creation
+ PostgreSQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ PostgreSQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
+ default_value = extract_value_from_default(default)
+ default_function = extract_default_function(default_value, default)
+
+ PostgreSQLColumn.new(
+ column_name,
+ default_value,
+ type_metadata,
+ !notnull,
+ table_name,
+ default_function,
+ collation,
+ comment: comment.presence
+ )
+ end
+
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
+ simple_type = SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "c"; :cascade
+ when "n"; :nullify
+ when "r"; :restrict
+ end
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
+
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
+ sql << " WHERE n.nspname = #{scope[:schema]}"
+ sql << " AND c.relname = #{scope[:name]}" if scope[:name]
+ sql << " AND c.relkind IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'r'"
+ when "VIEW"
+ "'v','m'"
+ end
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ name = Utils.extract_schema_qualified_name(string.to_s)
+ [name.schema, name.identifier]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index 311988625f..f57179ae59 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :oid, :fmod, :array
def initialize(type_metadata, oid: nil, fmod: nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index a3f9ce6d64..aa7940188a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -19,9 +19,9 @@ module ActiveRecord
def quoted
if schema
- PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
+ PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
else
- PGconn.quote_ident(identifier)
+ PG::Connection.quote_ident(identifier)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bc04565434..f74f966fd9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -3,6 +3,7 @@ gem "pg", "~> 0.18"
require "pg"
require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/postgresql/column"
require "active_record/connection_adapters/postgresql/database_statements"
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
@@ -15,7 +16,6 @@ require "active_record/connection_adapters/postgresql/schema_dumper"
require "active_record/connection_adapters/postgresql/schema_statements"
require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
-require "active_record/connection_adapters/statement_pool"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -29,11 +29,11 @@ module ActiveRecord
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
- # Forward only valid config params to PGconn.connect.
- valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl]
+ # Forward only valid config params to PG::Connection.connect.
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
conn_params.slice!(*valid_conn_param_keys)
- # The postgres drivers don't allow the creation of an unconnected PGconn object,
+ # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
# so just pass a nil connection object for the time being.
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
end
@@ -121,20 +121,6 @@ module ActiveRecord
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- def schema_creation # :nodoc:
- PostgreSQL::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::PostgreSQL.new(self)
- end
-
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
def supports_index_sort_order?
true
end
@@ -201,8 +187,8 @@ module ActiveRecord
end
def connection_active?
- @connection.status == PGconn::CONNECTION_OK
- rescue PGError
+ @connection.status == PG::CONNECTION_OK
+ rescue PG::Error
false
end
end
@@ -247,34 +233,42 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query "SELECT 1"
+ @lock.synchronize do
+ @connection.query "SELECT 1"
+ end
true
- rescue PGError
+ rescue PG::Error
false
end
# Close then reopen the connection.
def reconnect!
- super
- @connection.reset
- configure_connection
+ @lock.synchronize do
+ super
+ @connection.reset
+ configure_connection
+ end
end
def reset!
- clear_cache!
- reset_transaction
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
- @connection.query "ROLLBACK"
+ @lock.synchronize do
+ clear_cache!
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query "ROLLBACK"
+ end
+ @connection.query "DISCARD ALL"
+ configure_connection
end
- @connection.query "DISCARD ALL"
- configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- super
- @connection.close rescue nil
+ @lock.synchronize do
+ super
+ @connection.close rescue nil
+ end
end
def native_database_types #:nodoc:
@@ -301,8 +295,8 @@ module ActiveRecord
true
end
- # Range datatypes weren't introduced until PostgreSQL 9.2
def supports_ranges?
+ # Range datatypes weren't introduced until PostgreSQL 9.2
postgresql_version >= 90200
end
@@ -376,11 +370,6 @@ module ActiveRecord
PostgreSQL::Table.new(table_name, base)
end
- def lookup_cast_type(sql_type) # :nodoc:
- oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i
- super(oid)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
OPERATION_ALIASES.fetch(operation) { operation.downcase }
end
@@ -414,7 +403,7 @@ module ActiveRecord
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
- case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
when UNIQUE_VIOLATION
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
@@ -560,7 +549,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default)
- !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
end
def load_additional_types(type_map, oids = nil)
@@ -651,7 +640,7 @@ module ActiveRecord
CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
def is_cached_plan_failure?(e)
pgerror = e.cause
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
rescue
false
@@ -690,7 +679,7 @@ module ActiveRecord
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
def connect
- @connection = PGconn.connect(@connection_parameters)
+ @connection = PG.connect(@connection_parameters)
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
@@ -777,8 +766,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(*args)
- PostgreSQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::PostgreSQL.new(self)
end
def can_perform_case_insensitive_comparison_for?(column)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
new file mode 100644
index 0000000000..e02491edb6
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -0,0 +1,92 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
+ index_sql = select_value(<<-SQL, "SCHEMA")
+ SELECT sql
+ FROM sqlite_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ UNION ALL
+ SELECT sql
+ FROM sqlite_temp_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ SQL
+
+ /\sWHERE\s+(?<where>.+)$/i =~ index_sql
+
+ columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
+ col["name"]
+ end
+
+ IndexDefinition.new(
+ table_name,
+ row["name"],
+ row["unique"] != 0,
+ columns,
+ where: where
+ )
+ end
+ end
+
+ private
+ def schema_creation
+ SQLite3::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ SQLite3::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ default = \
+ case field["dflt_value"]
+ when /^null$/i
+ nil
+ when /^'(.*)'$/m
+ $1.gsub("''", "'")
+ when /^"(.*)"$/m
+ $1.gsub('""', '"')
+ else
+ field["dflt_value"]
+ end
+
+ type_metadata = fetch_type_metadata(field["type"])
+ Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'table','view'"
+
+ sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
+ sql << " AND name = #{scope[:name]}" if scope[:name]
+ sql << " AND type IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'table'"
+ when "VIEW"
+ "'view'"
+ end
+ scope = {}
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 8b627a6d4d..7233325d5a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -5,6 +5,7 @@ require "active_record/connection_adapters/sqlite3/quoting"
require "active_record/connection_adapters/sqlite3/schema_creation"
require "active_record/connection_adapters/sqlite3/schema_definitions"
require "active_record/connection_adapters/sqlite3/schema_dumper"
+require "active_record/connection_adapters/sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -55,6 +56,7 @@ module ActiveRecord
include SQLite3::Quoting
include SQLite3::ColumnDumper
+ include SQLite3::SchemaStatements
NATIVE_DATABASE_TYPES = {
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -82,14 +84,6 @@ module ActiveRecord
SQLite3::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- SQLite3::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::SQLite.new(self)
- end
-
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@@ -111,12 +105,6 @@ module ActiveRecord
sqlite_version >= "3.8.0"
end
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
def requires_reloading?
true
end
@@ -265,92 +253,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(view_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def new_column_from_field(table_name, field) # :nondoc:
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/m
- field["dflt_value"] = $1.gsub("''", "'")
- when /^"(.*)"$/m
- field["dflt_value"] = $1.gsub('""', '"')
- end
-
- collation = field["collation"]
- sql_type = field["type"]
- type_metadata = fetch_type_metadata(sql_type)
- new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
- sql = <<-SQL
- SELECT sql
- FROM sqlite_master
- WHERE name=#{quote(row['name'])} AND type='index'
- UNION ALL
- SELECT sql
- FROM sqlite_temp_master
- WHERE name=#{quote(row['name'])} AND type='index'
- SQL
- index_sql = exec_query(sql).first["sql"]
- match = /\sWHERE\s+(.+)$/i.match(index_sql)
- where = match[1] if match
- IndexDefinition.new(
- table_name,
- row["name"],
- row["unique"] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
- col["name"]
- }, nil, nil, where)
- end
- end
-
def primary_keys(table_name) # :nodoc:
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
@@ -535,7 +437,7 @@ module ActiveRecord
end
def sqlite_version
- @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)"))
end
def translate_exception(exception, message)
@@ -596,16 +498,8 @@ module ActiveRecord
end
end
- def create_table_definition(*args)
- SQLite3::TableDefinition.new(*args)
- end
-
- def extract_foreign_key_action(specifier)
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- when "RESTRICT"; :restrict
- end
+ def arel_visitor
+ Arel::Visitors::SQLite.new(self)
end
def configure_connection
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0028dc0edb..8f78330d4a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -559,7 +559,6 @@ module ActiveRecord
@marked_for_destruction = false
@destroyed_by_association = nil
@new_record = true
- @txn = nil
@_start_transaction_state = {}
@transaction_state = nil
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 08d42f3dd4..3a9625092e 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,16 +1,14 @@
-
module ActiveRecord
module DynamicMatchers #:nodoc:
- def respond_to_missing?(name, include_private = false)
- if self == Base
- super
- else
- match = Method.match(self, name)
- match && match.valid? || super
- end
- end
-
private
+ def respond_to_missing?(name, _)
+ if self == Base
+ super
+ else
+ match = Method.match(self, name)
+ match && match.valid? || super
+ end
+ end
def method_missing(name, *arguments, &block)
match = Method.match(self, name)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e79167d568..c19216702c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -70,13 +70,32 @@ module ActiveRecord
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
- # using a special dynamic method, which has the same name as the model, and accepts the
- # name of the fixture to instantiate:
+ # using a special dynamic method, which has the same name as the model.
#
- # test "find" do
+ # Passing in a fixture name to this dynamic method returns the fixture matching this name:
+ #
+ # test "find one" do
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
# end
#
+ # Passing in multiple fixture names returns all fixtures matching these names:
+ #
+ # test "find all by name" do
+ # assert_equal 2, web_sites(:rubyonrails, :google).length
+ # end
+ #
+ # Passing in no arguments returns all fixtures:
+ #
+ # test "find all" do
+ # assert_equal 2, web_sites.length
+ # end
+ #
+ # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
+ #
+ # test "find by name that does not exist" do
+ # assert_raise(StandardError) { web_sites(:reddit) }
+ # end
+ #
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
# following tests:
#
@@ -909,6 +928,8 @@ module ActiveRecord
define_method(accessor_name) do |*fixture_names|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
+ return_single_record = fixture_names.size == 1
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
@fixture_cache[fs_name] ||= {}
@@ -923,7 +944,7 @@ module ActiveRecord
end
end
- instances.size == 1 ? instances.first : instances
+ return_single_record ? instances.first : instances
end
private accessor_name
end
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 174f716152..1a937dbcf7 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveRecord
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2659c60f1f..78ce9f8291 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -47,8 +47,6 @@ module ActiveRecord
# self.locking_column = :lock_person
# end
#
- # Please note that the optimistic locking will be ignored if you update the
- # locking column's value.
module Optimistic
extend ActiveSupport::Concern
@@ -80,13 +78,11 @@ module ActiveRecord
def _update_record(attribute_names = self.attribute_names)
return super unless locking_enabled?
-
- lock_col = self.class.locking_column
-
- return super if attribute_names.include?(lock_col)
return 0 if attribute_names.empty?
begin
+ lock_col = self.class.locking_column
+
previous_lock_value = read_attribute_before_type_cast(lock_col)
increment_lock
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index ea101946f4..2297c77835 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -44,17 +44,17 @@ module ActiveRecord
private
def type_casted_binds(binds, casted_binds)
- casted_binds || binds.map { |attr| type_cast attr.value_for_database }
+ casted_binds || ActiveRecord::Base.connection.type_casted_binds(binds)
end
- def render_bind(attr, type_casted_value)
- value = if attr.type.binary? && attr.value
- "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
- else
- type_casted_value
+ def render_bind(attr, value)
+ if attr.is_a?(Array)
+ attr = attr.first
+ elsif attr.type.binary? && attr.value
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
end
- [attr.name, value]
+ [attr && attr.name, value]
end
def colorize_payload_name(name, payload_name)
@@ -89,10 +89,6 @@ module ActiveRecord
def logger
ActiveRecord::Base.logger
end
-
- def type_cast(value)
- ActiveRecord::Base.connection.type_cast(value)
- end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 3eb9171a5f..51c82f4ced 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1022,6 +1022,11 @@ module ActiveRecord
new(:up, migrations(migrations_paths), nil)
end
+ def schema_migrations_table_name
+ SchemaMigration.table_name
+ end
+ deprecate :schema_migrations_table_name
+
def get_all_versions(connection = Base.connection)
if SchemaMigration.table_exists?
SchemaMigration.all_versions.map(&:to_i)
@@ -1099,13 +1104,21 @@ module ActiveRecord
def move(direction, migrations_paths, steps)
migrator = new(direction, migrations(migrations_paths))
- start_index = migrator.migrations.index(migrator.current_migration)
- if start_index
- finish = migrator.migrations[start_index + steps]
- version = finish ? finish.version : 0
- send(direction, migrations_paths, version)
+ if current_version != 0 && !migrator.current_migration
+ raise UnknownMigrationVersionError.new(current_version)
end
+
+ start_index =
+ if current_version == 0
+ 0
+ else
+ migrator.migrations.index(migrator.current_migration)
+ end
+
+ finish = migrator.migrations[start_index + steps]
+ version = finish ? finish.version : 0
+ send(direction, migrations_paths, version)
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 03103bba98..f9cf59b283 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -92,10 +92,6 @@ module ActiveRecord
send(method, args, &block)
end
- def respond_to_missing?(*args) # :nodoc:
- super || delegate.respond_to?(*args)
- end
-
ReversibleAndIrreversibleMethods.each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def create_table(*args, &block)
@@ -225,10 +221,14 @@ module ActiveRecord
[:add_foreign_key, reversed_args]
end
+ def respond_to_missing?(method, _)
+ super || delegate.respond_to?(method)
+ end
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
- if @delegate.respond_to?(method)
- @delegate.send(method, *args, &block)
+ if delegate.respond_to?(method)
+ delegate.public_send(method, *args, &block)
else
super
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 85032ce470..188dd0acef 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -11,7 +11,10 @@ module ActiveRecord
const_get(name)
end
- V5_1 = Current
+ V5_2 = Current
+
+ class V5_1 < V5_2
+ end
class V5_0 < V5_1
module TableDefinition
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 2bb7ed6d5e..26966f9433 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -4,7 +4,7 @@ module ActiveRecord
[]
end
- def delete_all(_conditions = nil)
+ def delete_all
0
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7ceb7d1a55..f652c7c3a1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -100,6 +100,10 @@ module ActiveRecord
!(@new_record || @destroyed)
end
+ ##
+ # :call-seq:
+ # save(*args)
+ #
# Saves the model.
#
# If the model is new, a record gets created in the database, otherwise
@@ -121,12 +125,16 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save(*args)
- create_or_update(*args)
+ def save(*args, &block)
+ create_or_update(*args, &block)
rescue ActiveRecord::RecordInvalid
false
end
+ ##
+ # :call-seq:
+ # save!(*args)
+ #
# Saves the model.
#
# If the model is new, a record gets created in the database, otherwise
@@ -150,8 +158,8 @@ module ActiveRecord
# being updated.
#
# Unless an error is raised, returns true.
- def save!(*args)
- create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
+ def save!(*args, &block)
+ create_or_update(*args, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
end
# Deletes the record in the database and freezes this instance to
@@ -550,9 +558,9 @@ module ActiveRecord
self.class.unscoped.where(self.class.primary_key => id)
end
- def create_or_update(*args)
+ def create_or_update(*args, &block)
_raise_readonly_record_error if readonly?
- result = new_record? ? _create_record : _update_record(*args)
+ result = new_record? ? _create_record(&block) : _update_record(*args, &block)
result != false
end
@@ -567,6 +575,9 @@ module ActiveRecord
rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database
@_trigger_update_callback = rows_affected > 0
end
+
+ yield(self) if block_given?
+
rows_affected
end
@@ -579,6 +590,9 @@ module ActiveRecord
self.id ||= new_id if self.class.primary_key
@new_record = false
+
+ yield(self) if block_given?
+
id
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 36689f6559..c4a22398f0 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -9,7 +9,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :merge, to: :all
+ :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
delegate :pluck, :ids, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 0276d41494..73518ca144 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -166,5 +166,13 @@ end_warning
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
+
+ initializer "active_record.clear_active_connections" do
+ config.after_initialize do
+ ActiveSupport.on_load(:active_record) do
+ clear_active_connections!
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1c7206aca4..711099e9e1 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -77,6 +77,8 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
task redo: [:environment, :load_config] do
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
+
if ENV["VERSION"]
db_namespace["migrate:down"].invoke
db_namespace["migrate:up"].invoke
@@ -91,16 +93,17 @@ db_namespace = namespace :db do
# desc 'Runs the "up" for a given migration VERSION.'
task up: [:environment, :load_config] do
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace["_dump"].invoke
end
# desc 'Runs the "down" for a given migration VERSION.'
task down: [:environment, :load_config] do
+ raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required - To go down one migration, run db:rollback" unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace["_dump"].invoke
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 24ca8b0be4..1a9e0a4a40 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -212,7 +212,7 @@ module ActiveRecord
end
def constraints
- chain.map(&:scopes).flatten
+ chain.flat_map(&:scopes)
end
def counter_cache_column
@@ -1105,7 +1105,7 @@ module ActiveRecord
end
def alias_name
- Arel::Table.new(table_name)
+ Arel::Table.new(table_name, type_caster: klass.type_caster)
end
def all_includes; yield; end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 61ee09bcc8..5775eda5a5 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -261,10 +261,6 @@ module ActiveRecord
coder.represent_seq(nil, records)
end
- def as_json(options = nil) #:nodoc:
- records.as_json(options)
- end
-
# Returns size of the records.
def size
loaded? ? @records.length : count(:all)
@@ -639,7 +635,9 @@ module ActiveRecord
end
def inspect
- entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
+ subject = loaded? ? records : self
+ entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
+
entries[10] = "..." if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index f4cdaf3948..9cabd1af13 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,11 +37,8 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- if block_given?
- to_a.count { |*block_args| yield(*block_args) }
- else
- calculate(:count, column_name)
- end
+ return super() if block_given?
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -75,8 +72,8 @@ module ActiveRecord
# #calculate for examples with options.
#
# Person.sum(:age) # => 4562
- def sum(column_name = nil, &block)
- return super(&block) if block_given?
+ def sum(column_name = nil)
+ return super() if block_given?
calculate(:sum, column_name)
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index d3ba724507..257ae04ff4 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -25,6 +25,8 @@ module ActiveRecord
def inherited(child_class)
child_class.initialize_relation_delegate_cache
+ delegate = child_class.relation_delegate_class(ActiveRecord::Associations::CollectionProxy)
+ delegate.include ActiveRecord::Associations::CollectionProxy::DelegateExtending
super
end
end
@@ -36,9 +38,9 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
+ delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
- :to_sentence, :to_formatted_s,
+ :to_sentence, :to_formatted_s, :as_json,
:shuffle, :split, :index, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
@@ -109,12 +111,10 @@ module ActiveRecord
end
end
- def respond_to_missing?(method, include_private = false)
- super || @klass.respond_to?(method, include_private) ||
- arel.respond_to?(method, include_private)
- end
-
private
+ def respond_to_missing?(method, _)
+ super || @klass.respond_to?(method) || arel.respond_to?(method)
+ end
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 5d24f5f5ca..a1459c87c6 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -312,16 +312,7 @@ module ActiveRecord
relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
- relation = relation.except(:select, :distinct).select(ONE_AS_ONE).limit(1)
-
- case conditions
- when Array, Hash
- relation = relation.where(conditions)
- else
- unless conditions == :none
- relation = relation.where(primary_key => conditions)
- end
- end
+ relation = construct_relation_for_exists(relation, conditions)
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
rescue ::RangeError
@@ -391,6 +382,19 @@ module ActiveRecord
end
end
+ def construct_relation_for_exists(relation, conditions)
+ relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+
+ case conditions
+ when Array, Hash
+ relation.where!(conditions)
+ else
+ relation.where!(primary_key => conditions) unless conditions == :none
+ end
+
+ relation
+ end
+
def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
@@ -401,8 +405,7 @@ module ActiveRecord
end
def apply_join_dependency(relation, join_dependency)
- relation = relation.except(:includes, :eager_load, :preload)
- relation = relation.joins join_dependency
+ relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency)
if using_limitable_reflections?(join_dependency.reflections)
relation
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 18ae10a652..183fe91c05 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,13 +1,14 @@
+require "active_record/relation/predicate_builder/array_handler"
+require "active_record/relation/predicate_builder/base_handler"
+require "active_record/relation/predicate_builder/basic_object_handler"
+require "active_record/relation/predicate_builder/range_handler"
+require "active_record/relation/predicate_builder/relation_handler"
+
+require "active_record/relation/predicate_builder/association_query_value"
+require "active_record/relation/predicate_builder/polymorphic_array_value"
+
module ActiveRecord
class PredicateBuilder # :nodoc:
- require "active_record/relation/predicate_builder/array_handler"
- require "active_record/relation/predicate_builder/association_query_handler"
- require "active_record/relation/predicate_builder/base_handler"
- require "active_record/relation/predicate_builder/basic_object_handler"
- require "active_record/relation/predicate_builder/polymorphic_array_handler"
- require "active_record/relation/predicate_builder/range_handler"
- require "active_record/relation/predicate_builder/relation_handler"
-
delegate :resolve_column_aliases, to: :table
def initialize(table)
@@ -20,8 +21,6 @@ module ActiveRecord
register_handler(RangeHandler::RangeWithBinds, RangeHandler.new)
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
- register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
- register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -92,9 +91,27 @@ module ActiveRecord
attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
result[column_name] = attrs
binds += bvs
- next
- when value.is_a?(Relation)
- binds += value.bound_attributes
+ when table.associated_with?(column_name)
+ # Find the foreign key when using queries such as:
+ # Post.where(author: author)
+ #
+ # For polymorphic relationships, find the foreign key and type:
+ # PriceEstimate.where(estimate_of: treasure)
+ associated_table = table.associated_table(column_name)
+ if associated_table.polymorphic_association?
+ case value.is_a?(Array) ? value.first : value
+ when Base, Relation
+ value = [value] unless value.is_a?(Array)
+ klass = PolymorphicArrayValue
+ end
+ end
+
+ klass ||= AssociationQueryValue
+ result[column_name] = klass.new(associated_table, value).queries.map do |query|
+ attrs, bvs = create_binds_for_hash(query)
+ binds.concat(bvs)
+ attrs
+ end
when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype)
first = value.begin
last = value.end
@@ -112,17 +129,10 @@ module ActiveRecord
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
binds << build_bind_param(column_name, value)
+ elsif value.is_a?(Relation)
+ binds.concat(value.bound_attributes)
end
end
-
- # Find the foreign key when using queries such as:
- # Post.where(author: author)
- #
- # For polymorphic relationships, find the foreign key and type:
- # PriceEstimate.where(estimate_of: treasure)
- if table.associated_with?(column_name)
- result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
- end
end
[result, binds]
@@ -155,7 +165,6 @@ module ActiveRecord
end
def can_be_bound?(column_name, value)
- return if table.associated_with?(column_name)
case value
when Array, Range
table.type(column_name).respond_to?(:subtype)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 88b6c37d43..1068e700e2 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -6,11 +6,11 @@ module ActiveRecord
end
def call(attribute, value)
+ return attribute.in([]) if value.empty?
+ return queries_predicates(value) if value.all? { |v| v.is_a?(Hash) }
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
-
- return attribute.in([]) if values.empty? && nils.empty?
-
ranges, values = values.partition { |v| v.is_a?(Range) }
values_predicate =
@@ -26,7 +26,7 @@ module ActiveRecord
array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ array_predicates.inject(&:or)
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
@@ -40,6 +40,17 @@ module ActiveRecord
other
end
end
+
+ private
+ def queries_predicates(queries)
+ if queries.size > 1
+ queries.map do |query|
+ Arel::Nodes::And.new(predicate_builder.build_from_hash(query))
+ end.inject(&:or)
+ else
+ predicate_builder.build_from_hash(queries.first)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
deleted file mode 100644
index 29860ec677..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class AssociationQueryHandler # :nodoc:
- def self.value_for(table, column, value)
- associated_table = table.associated_table(column)
- klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
- PolymorphicArrayValue
- else
- AssociationQueryValue
- end
-
- klass.new(associated_table, value)
- end
-
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- queries = {}
-
- table = value.associated_table
- if value.base_class
- queries[table.association_foreign_type.to_s] = value.base_class.name
- end
-
- queries[table.association_foreign_key.to_s] = value.ids
- predicate_builder.build_from_hash(queries)
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :predicate_builder
- end
-
- class AssociationQueryValue # :nodoc:
- attr_reader :associated_table, :value
-
- def initialize(associated_table, value)
- @associated_table = associated_table
- @value = value
- end
-
- def ids
- case value
- when Relation
- value.select(primary_key)
- when Array
- value.map { |v| convert_to_id(v) }
- else
- convert_to_id(value)
- end
- end
-
- def base_class
- if associated_table.polymorphic_association?
- @base_class ||= polymorphic_base_class_from_value
- end
- end
-
- private
-
- def primary_key
- associated_table.association_primary_key(base_class)
- end
-
- def polymorphic_base_class_from_value
- case value
- when Relation
- value.klass.base_class
- when Base
- value.class.base_class
- end
- end
-
- def convert_to_id(value)
- case value
- when Base
- value._read_attribute(primary_key)
- else
- value
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
new file mode 100644
index 0000000000..2fe0f81cab
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryValue # :nodoc:
+ attr_reader :associated_table, :value
+
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def queries
+ [associated_table.association_foreign_key.to_s => ids]
+ end
+
+ private
+ def ids
+ case value
+ when Relation
+ value.select_values.empty? ? value.select(primary_key) : value
+ when Array
+ value.map { |v| convert_to_id(v) }
+ else
+ convert_to_id(value)
+ end
+ end
+
+ def primary_key
+ associated_table.association_primary_key
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key)
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
deleted file mode 100644
index 335124c952..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class PolymorphicArrayHandler # :nodoc:
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- table = value.associated_table
- queries = value.type_to_ids_mapping.map do |type, ids|
- { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
- end
-
- predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
-
- if predicates.size > 1
- type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
- type_and_ids_predicates.inject(&:or)
- else
- predicates.first
- end
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :predicate_builder
- end
-
- class PolymorphicArrayValue # :nodoc:
- attr_reader :associated_table, :values
-
- def initialize(associated_table, values)
- @associated_table = associated_table
- @values = values
- end
-
- def type_to_ids_mapping
- default_hash = Hash.new { |hsh, key| hsh[key] = [] }
- values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
- end
-
- private
-
- def primary_key(value)
- associated_table.association_primary_key(base_class(value))
- end
-
- def base_class(value)
- value.class.base_class
- end
-
- def convert_to_id(value)
- value._read_attribute(primary_key(value))
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
new file mode 100644
index 0000000000..9bb2f8c8dc
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayValue # :nodoc:
+ attr_reader :associated_table, :values
+
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def queries
+ type_to_ids_mapping.map do |type, ids|
+ {
+ associated_table.association_foreign_type.to_s => type,
+ associated_table.association_foreign_key.to_s => ids.size > 1 ? ids : ids.first
+ }
+ end
+ end
+
+ private
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ case value
+ when Base
+ value.class.base_class
+ when Relation
+ value.klass.base_class
+ end
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key(value))
+ when Relation
+ value.select(primary_key(value))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4ee413c805..1178dec706 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1130,7 +1130,12 @@ module ActiveRecord
arel_attribute(arg).asc
when Hash
arg.map { |field, dir|
- arel_attribute(field).send(dir.downcase)
+ case field
+ when Arel::Nodes::SqlLiteral
+ field.send(dir.downcase)
+ else
+ arel_attribute(field).send(dir.downcase)
+ end
}
else
arg
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 417b24c7bb..119910ee79 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -148,10 +148,10 @@ module ActiveRecord
(binds_index...(binds_index + binds_contains)).each do |i|
except_binds[i] = true
end
-
- binds_index += binds_contains
end
+ binds_index += binds_contains if binds_contains
+
except
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2bbfd01698..94d63765c9 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -47,9 +47,18 @@ module ActiveRecord
@options = options
end
- def header(stream)
- define_params = @version ? "version: #{@version}" : ""
+ # turns 20170404131909 into "2017_04_04_131909"
+ def formatted_version
+ stringified = @version.to_s
+ return stringified unless stringified.length == 14
+ stringified.insert(4, "_").insert(7, "_").insert(10, "_")
+ end
+ def define_params
+ @version ? "version: #{formatted_version}" : ""
+ end
+
+ def header(stream)
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 82604a915f..45110a79cd 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -162,9 +162,11 @@ module ActiveRecord
end
def migrate
- verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
+
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- scope = ENV["SCOPE"]
+ scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 920830b9cf..c05f0a8fbb 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -104,7 +104,7 @@ module ActiveRecord
def grant_statement
<<-SQL
-GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+GRANT ALL PRIVILEGES ON `#{configuration['database']}`.*
TO '#{configuration['username']}'@'localhost'
IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
SQL
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 5155ced0e2..f1af90c1e8 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -1,8 +1,11 @@
+require "tempfile"
+
module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
+ SQL_COMMENT_BEGIN = "--".freeze
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -65,6 +68,7 @@ module ActiveRecord
end
args << configuration["database"]
run_cmd("pg_dump", args, "dumping")
+ remove_sql_header_comments(filename)
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
@@ -110,6 +114,22 @@ module ActiveRecord
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
end
+
+ def remove_sql_header_comments(filename)
+ removing_comments = true
+ tempfile = Tempfile.open("uncommented_structure.sql")
+ begin
+ File.foreach(filename) do |line|
+ unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
+ tempfile << line
+ removing_comments = false
+ end
+ end
+ ensure
+ tempfile.close
+ end
+ FileUtils.mv(tempfile.path, filename)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 690deee508..45795fa287 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -123,7 +123,7 @@ module ActiveRecord
# # statement will cause a PostgreSQL error, even though the unique
# # constraint is no longer violated:
# Number.create(i: 1)
- # # => "PGError: ERROR: current transaction is aborted, commands
+ # # => "PG::Error: ERROR: current transaction is aborted, commands
# # ignored until end of transaction block"
# end
#
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 6af05c1860..edbd20a6c1 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module Type
class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
include ActiveModel::Type::Helpers::Mutable
attr_reader :subtype, :coder