aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb14
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb7
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb12
-rw-r--r--activerecord/lib/active_record/attribute.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb7
-rw-r--r--activerecord/lib/active_record/callbacks.rb6
-rw-r--r--activerecord/lib/active_record/coders/json.rb2
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb86
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb97
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb65
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb125
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb82
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb10
-rw-r--r--activerecord/lib/active_record/connection_handling.rb38
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb11
-rw-r--r--activerecord/lib/active_record/migration.rb18
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb15
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb5
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb6
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb7
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb14
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb3
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb1
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb56
-rw-r--r--activerecord/lib/active_record/suppressor.rb3
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb10
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/serialized.rb8
-rw-r--r--activerecord/lib/active_record/type/time.rb12
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb3
61 files changed, 789 insertions, 364 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 77d17fc975..5a973fa801 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -318,7 +318,7 @@ module ActiveRecord
# create_other(attributes={}) | X | | X
# create_other!(attributes={}) | X | | X
#
- # ===Collection associations (one-to-many / many-to-many)
+ # === Collection associations (one-to-many / many-to-many)
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
@@ -1326,7 +1326,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. true by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
@@ -1456,7 +1457,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated object when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated object.
@@ -1580,7 +1582,8 @@ module ActiveRecord
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction, when
# saving the parent object.
@@ -1766,7 +1769,8 @@ module ActiveRecord
# So if a Person class makes a #has_and_belongs_to_many association to Project,
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction, when
# saving the parent object.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index f7edfbfb5f..62e867a353 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -217,7 +217,8 @@ module ActiveRecord
unless record.is_a?(reflection.klass)
fresh_class = reflection.class_name.safe_constantize
unless fresh_class && record.is_a?(fresh_class)
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 882f1225fc..15844de0bc 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -64,11 +64,11 @@ module ActiveRecord
foreign_key = join_keys.foreign_key
value = transform_value(owner[foreign_key])
- scope = scope.where(table.name.to_sym => { key => value })
+ scope = scope.where(table.name => { key => value })
if reflection.type
polymorphic_type = transform_value(owner.class.base_class.name)
- scope = scope.where(table.name.to_sym => { reflection.type => polymorphic_type })
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
end
scope
@@ -124,8 +124,7 @@ module ActiveRecord
scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
reflection = chain_head
- loop do
- break unless reflection
+ while reflection
table = reflection.alias_name
unless reflection == chain_tail
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 41698c5360..24997370b2 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -61,6 +61,7 @@ module ActiveRecord
def update_counters_on_replace(record)
if require_counter_update? && different_target?(record)
+ owner.instance_variable_set :@_after_replace_counter_called, true
record.increment!(reflection.counter_cache_column)
decrement_counters
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 346329c610..3121e70a04 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,6 +33,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
+ elsif (@_after_replace_counter_called ||= false)
+ @_after_replace_counter_called = false
elsif attribute_changed?(foreign_key) && !new_record?
if reflection.polymorphic?
model = attribute(reflection.foreign_type).try(:constantize)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2dca6b612e..00355f3e89 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -246,9 +246,12 @@ module ActiveRecord
end
end
- # Count all records using SQL. Construct options and pass them with
- # scope to the target class's +count+.
+ # Returns the number of records. If no arguments are given, it counts all
+ # columns using SQL. If one argument is given, it counts only the passed
+ # column using SQL. If a block is given, it counts the number of records
+ # yielding a true value.
def count(column_name = nil)
+ return super if block_given?
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index b9aed05135..9350064028 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -715,12 +715,13 @@ module ActiveRecord
end
alias uniq distinct
- # Count all records using SQL.
+ # Count all records.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
+ # # This will perform the count using SQL.
# person.pets.count # => 3
# person.pets
# # => [
@@ -728,8 +729,13 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil)
- @association.count(column_name)
+ #
+ # Passing a block will select all of a person's pets in SQL and then
+ # perform the count using Ruby.
+ #
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
+ def count(column_name = nil, &block)
+ @association.count(column_name, &block)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 3c4c8f10ec..24231dc9e1 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -170,7 +170,7 @@ module ActiveRecord
super(name, nil, Type::Value.new)
end
- def value
+ def type_cast(*)
nil
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index e0ceafc617..519de271c3 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -67,12 +67,14 @@ module ActiveRecord
#
# A default can also be provided.
#
+ # # db/schema.rb
# create_table :store_listings, force: true do |t|
# t.string :my_string, default: "original default"
# end
#
# StoreListing.new.my_string # => "original default"
#
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :my_string, :string, default: "new default"
# end
@@ -89,6 +91,7 @@ module ActiveRecord
#
# \Attributes do not need to be backed by a database column.
#
+ # # app/models/my_model.rb
# class MyModel < ActiveRecord::Base
# attribute :my_string, :string
# attribute :my_int_array, :integer, array: true
@@ -131,7 +134,7 @@ module ActiveRecord
# # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
- # # /app/models/store_listing.rb
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :price_in_cents, :money
# end
@@ -167,8 +170,10 @@ module ActiveRecord
# end
# end
#
+ # # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
+ # # app/models/product.rb
# class Product < ActiveRecord::Base
# currency_converter = ConversionRatesFromTheInternet.new
# attribute :price_in_bitcoins, :money, currency_converter: currency_converter
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 1f1b11eb68..95de6937af 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -53,9 +53,9 @@ module ActiveRecord
# end
#
# class Firm < ActiveRecord::Base
- # # Destroys the associated clients and people when the firm is destroyed
- # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
- # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
# end
#
# == Inheritable callback queues
diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb
index 75d3bfe625..cb185a881e 100644
--- a/activerecord/lib/active_record/coders/json.rb
+++ b/activerecord/lib/active_record/coders/json.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def self.load(json)
- ActiveSupport::JSON.decode(json) unless json.nil?
+ ActiveSupport::JSON.decode(json) unless json.blank?
end
end
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 5dcc98424a..8d41c0d799 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -16,7 +16,7 @@ module ActiveRecord
query = collection
.unscope(:select)
- .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
.unscope(:order)
result = connection.select_one(query)
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 e389d818fd..f3abd01290 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -298,7 +298,7 @@ module ActiveRecord
def run
return unless frequency
Thread.new(frequency, pool) { |t, p|
- while true
+ loop do
sleep t
p.reap
end
@@ -618,7 +618,7 @@ module ActiveRecord
timeout_time = Time.now + (@checkout_timeout * 2)
@available.with_a_bias_for(Thread.current) do
- while true
+ loop do
synchronize do
return if collected_conns.size == @connections.size && @now_connecting == 0
remaining_timeout = timeout_time - Time.now
@@ -778,8 +778,7 @@ module ActiveRecord
end
# ConnectionHandler is a collection of ConnectionPool objects. It is used
- # for keeping separate connection pools for Active Record models that connect
- # to different databases.
+ # for keeping separate connection pools that connect to different databases.
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
@@ -821,17 +820,16 @@ module ActiveRecord
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
# 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
+ # about the model. The model, needs to pass a specification name to the handler,
+ # in order to lookup the correct connection pool.
class ConnectionHandler
def initialize
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
- # alone would lead to memory leaks in development mode as all previous
- # instances of the class would stay in memory.
+ # These caches are keyed by spec.name (ConnectionSpecification#name).
@owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
h[k] = Concurrent::Map.new(:initial_capacity => 2)
end
- @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
- h[k] = Concurrent::Map.new
- end
end
def connection_pool_list
@@ -839,10 +837,8 @@ module ActiveRecord
end
alias :connection_pools :connection_pool_list
- def establish_connection(owner, spec)
- @class_to_pool.clear
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
+ def establish_connection(spec)
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
end
# Returns true if there are any active connections among the connection
@@ -873,18 +869,18 @@ module ActiveRecord
# active or defined connection: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
# for (not necessarily the current class).
- def retrieve_connection(klass) #:nodoc:
- pool = retrieve_connection_pool(klass)
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ def retrieve_connection(spec_name) #:nodoc:
+ pool = retrieve_connection_pool(spec_name)
+ raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
conn = pool.connection
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
conn
end
# Returns true if a connection that's accessible to this class has
# already been opened.
- def connected?(klass)
- conn = retrieve_connection_pool(klass)
+ def connected?(spec_name)
+ conn = retrieve_connection_pool(spec_name)
conn && conn.connected?
end
@@ -892,9 +888,8 @@ module ActiveRecord
# connection and the defined connection (if they exist). The result
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
- def remove_connection(owner)
- if pool = owner_to_pool.delete(owner.name)
- @class_to_pool.clear
+ def remove_connection(spec_name)
+ if pool = owner_to_pool.delete(spec_name)
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
@@ -910,14 +905,20 @@ module ActiveRecord
# #fetch is significantly slower than #[]. So in the nil case, no caching will
# take place, but that's ok since the nil case is not the common one that we wish
# to optimise for.
- def retrieve_connection_pool(klass)
- class_to_pool[klass.name] ||= begin
- until pool = pool_for(klass)
- klass = klass.superclass
- break unless klass <= Base
+ def retrieve_connection_pool(spec_name)
+ owner_to_pool.fetch(spec_name) do
+ # Check if a connection was previously established in an ancestor process,
+ # which may have been forked.
+ if ancestor_pool = pool_from_any_process_for(spec_name)
+ # A connection was established in an ancestor process that must have
+ # subsequently forked. We can't reuse the connection, but we can copy
+ # the specification and establish a new connection with it.
+ establish_connection(ancestor_pool.spec).tap do |pool|
+ pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
+ end
+ else
+ owner_to_pool[spec_name] = nil
end
-
- class_to_pool[klass.name] = pool
end
end
@@ -927,28 +928,9 @@ module ActiveRecord
@owner_to_pool[Process.pid]
end
- def class_to_pool
- @class_to_pool[Process.pid]
- end
-
- def pool_for(owner)
- owner_to_pool.fetch(owner.name) {
- if ancestor_pool = pool_from_any_process_for(owner)
- # A connection was established in an ancestor process that must have
- # subsequently forked. We can't reuse the connection, but we can copy
- # the specification and establish a new connection with it.
- establish_connection(owner, ancestor_pool.spec).tap do |pool|
- pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
- end
- else
- owner_to_pool[owner.name] = nil
- end
- }
- end
-
- def pool_from_any_process_for(owner)
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
- owner_to_pool && owner_to_pool[owner.name]
+ def pool_from_any_process_for(spec_name)
+ owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
+ owner_to_pool && owner_to_pool[spec_name]
end
end
end
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 824040775d..507a925d32 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -66,8 +66,8 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).rows
end
- undef_method :select_rows
# Executes the SQL statement in the context of this connection and returns
# the raw result from the connection adapter.
@@ -75,13 +75,14 @@ module ActiveRecord
# method may be manually memory managed. Consider using the exec_query
# wrapper instead.
def execute(sql, name = nil)
+ raise NotImplementedError
end
- undef_method :execute
# Executes +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ raise NotImplementedError
end
# Executes insert +sql+ statement in the context of this connection using
@@ -220,9 +221,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql2 and postgresql adapters support setting the transaction
- # isolation level. However, support is disabled for MySQL versions below 5,
- # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
- # which means the isolation level gets persisted outside the transaction.
+ # isolation level.
def transaction(requires_new: nil, isolation: nil, joinable: true)
if !requires_new && current_transaction.joinable?
if isolation
@@ -292,9 +291,6 @@ module ActiveRecord
exec_rollback_to_savepoint(name)
end
- def exec_rollback_to_savepoint(name = nil) #:nodoc:
- end
-
def default_sequence_name(table, column)
nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 2eeefb13d7..860ef17dca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -146,6 +146,10 @@ module ActiveRecord
end
end
+ def quoted_time(value) # :nodoc:
+ quoted_date(value).sub(/\A2000-01-01 /, '')
+ end
+
def prepare_binds_for_database(binds) # :nodoc:
binds.map(&:value_for_database)
end
@@ -166,6 +170,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
+ when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
when *types_which_need_no_typecasting
value
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index c0662f8473..3a06f75292 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module ConnectionAdapters
- module Savepoints #:nodoc:
- def supports_savepoints?
- true
+ module Savepoints
+ def current_savepoint_name
+ current_transaction.savepoint_name
end
def create_savepoint(name = current_savepoint_name)
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 0ba4d94e3c..6add697eeb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -53,8 +53,8 @@ module ActiveRecord
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
- create_sql << "(#{statements.join(', ')}) " if statements.present?
- create_sql << "#{o.options}"
+ create_sql << "(#{statements.join(', ')})" if statements.present?
+ add_table_options!(create_sql, table_options(o))
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end
@@ -82,6 +82,19 @@ module ActiveRecord
"DROP CONSTRAINT #{quote_column_name(name)}"
end
+ def table_options(o)
+ table_options = {}
+ table_options[:comment] = o.comment
+ table_options[:options] = o.options
+ table_options
+ end
+
+ def add_table_options!(create_sql, options)
+ if options_sql = options[:options]
+ create_sql << " #{options_sql}"
+ end
+ end
+
def column_options(o)
column_options = {}
column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +105,7 @@ module ActiveRecord
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
column_options[:collation] = o.collation
+ column_options[:comment] = o.comment
column_options
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 4f97c7c065..bbb0e9249d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,14 +3,14 @@ module ActiveRecord
# 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
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -207,9 +207,9 @@ module ActiveRecord
include ColumnMethods
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
- def initialize(name, temporary, options, as = nil)
+ def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
@columns_hash = {}
@indexes = {}
@foreign_keys = []
@@ -218,6 +218,7 @@ module ActiveRecord
@options = options
@as = as
@name = name
+ @comment = comment
end
def primary_keys(name = nil) # :nodoc:
@@ -330,6 +331,9 @@ module ActiveRecord
end
def foreign_key(table_name, options = {}) # :nodoc:
+ table_name_prefix = ActiveRecord::Base.table_name_prefix
+ table_name_suffix = ActiveRecord::Base.table_name_suffix
+ table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
foreign_keys.push([table_name, options])
end
@@ -373,6 +377,7 @@ module ActiveRecord
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column.collation = options[:collation]
+ column.comment = options[:comment]
column
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 4880d216d6..677a4c6bd0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -16,7 +16,7 @@ module ActiveRecord
def column_spec_for_primary_key(column)
return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
- spec.merge!(prepare_column_options(column))
+ spec.merge!(prepare_column_options(column).except!(:null))
end
# This can be overridden on an Adapter level basis to support other
@@ -46,12 +46,14 @@ module ActiveRecord
spec[:collation] = collation
end
+ spec[:comment] = column.comment.inspect if column.comment.present?
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation]
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
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 020d9bbdca..99a3e99bdc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -18,6 +18,11 @@ module ActiveRecord
nil
end
+ # Returns the table comment that's stored in database metadata.
+ def table_comment(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -254,8 +259,8 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
+ def create_table(table_name, comment: nil, **options)
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -283,6 +288,14 @@ module ActiveRecord
end
end
+ if supports_comments? && !supports_comments_in_create?
+ change_table_comment(table_name, comment) if comment
+
+ td.columns.each do |column|
+ change_column_comment(table_name, column.name, column.comment) if column.comment
+ end
+ end
+
result
end
@@ -329,12 +342,13 @@ module ActiveRecord
column_options = options.delete(:column_options) || {}
column_options.reverse_merge!(null: false)
+ type = column_options.delete(:type) || :integer
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
- td.integer t1_column, column_options
- td.integer t2_column, column_options
+ td.send type, t1_column, column_options
+ td.send type, t2_column, column_options
yield td if block_given?
end
end
@@ -776,7 +790,8 @@ module ActiveRecord
# [<tt>:type</tt>]
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
- # Add an appropriate index. Defaults to false.
+ # Add an appropriate index. Defaults to false.
+ # See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
# [<tt>:polymorphic</tt>]
@@ -796,6 +811,14 @@ module ActiveRecord
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
+ # ====== Create a supplier_id column with a unique index
+ #
+ # add_reference(:products, :supplier, index: { unique: true })
+ #
+ # ====== Create a supplier_id column with a named index
+ #
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
+ #
# ====== Create a supplier_id column and appropriate foreign key
#
# add_reference(:products, :supplier, foreign_key: true)
@@ -962,11 +985,23 @@ module ActiveRecord
end
def dump_schema_information #:nodoc:
+ versions = ActiveRecord::SchemaMigration.order('version').pluck(:version)
+ insert_versions_sql(versions)
+ end
+
+ def insert_versions_sql(versions) # :nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- sql = "INSERT INTO #{sm_table} (version) VALUES "
- sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ')
- sql << ";\n\n"
+ if supports_multi_insert?
+ sql = "INSERT INTO #{sm_table} (version) VALUES "
+ sql << versions.map {|v| "('#{v}')" }.join(', ')
+ sql << ";\n\n"
+ sql
+ else
+ versions.map { |version|
+ "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
+ }.join "\n\n"
+ end
end
# Should not be called normally, but this operation is non-destructive.
@@ -1003,7 +1038,7 @@ module ActiveRecord
if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
end
- execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| "('#{v}')"}.join(', ') }"
+ execute insert_versions_sql(inserting)
end
end
@@ -1075,15 +1110,19 @@ module ActiveRecord
Table.new(table_name, base)
end
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
- column_names = Array(column_name)
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
+ if column_name.is_a?(String) && /\W/ === column_name
+ column_names = column_name
+ else
+ column_names = Array(column_name)
+ end
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
- index_name ||= index_name(table_name, column: column_names)
+ index_name ||= index_name(table_name, index_name_options(column_names))
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if options.key?(:algorithm)
@@ -1106,13 +1145,23 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options, algorithm, using]
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
+ # Changes the comment for a table or removes it if +nil+.
+ def change_table_comment(table_name, comment)
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
+ end
+
+ # Changes the comment for a column or removes it if +nil+.
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1129,6 +1178,8 @@ module ActiveRecord
# Overridden by the MySQL adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
+ return [column_names] if column_names.is_a?(String)
+
option_strings = Hash[column_names.map {|name| [name, '']}]
# add index sort order if supported
@@ -1140,6 +1191,8 @@ module ActiveRecord
end
def index_name_for_remove(table_name, options = {})
+ return options[:name] if can_remove_index_by_name?(options)
+
# if the adapter doesn't support the indexes call the best we can do
# is return the default index name for the options provided
return index_name(table_name, options) unless respond_to?(:indexes)
@@ -1147,7 +1200,7 @@ module ActiveRecord
checks = []
if options.is_a?(Hash)
- checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name)
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
column_names = Array(options[:column]).map(&:to_s)
else
column_names = Array(options).map(&:to_s)
@@ -1194,14 +1247,22 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary = false, options = nil, as = nil)
- TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args)
+ TableDefinition.new(*args)
end
def create_alter_table(name)
AlterTable.new create_table_definition(name)
end
+ def index_name_options(column_names) # :nodoc:
+ if column_names.is_a?(String)
+ column_names = column_names.scan(/\w+/).join('_')
+ end
+
+ { column: column_names }
+ end
+
def foreign_key_name(table_name, options) # :nodoc:
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
@@ -1223,6 +1284,10 @@ module ActiveRecord
default_or_changes
end
end
+
+ def can_remove_index_by_name?(options)
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
+ 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 069346253a..d4b9e301bc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -67,6 +67,7 @@ module ActiveRecord
include QueryCache
include ActiveSupport::Callbacks
include ColumnDumper
+ include Savepoints
SIMPLE_INT = /\A\d+\z/
@@ -104,9 +105,15 @@ module ActiveRecord
@config = config
@pool = nil
@schema_cache = SchemaCache.new self
- @visitor = nil
- @prepared_statements = false
@quoted_column_names, @quoted_table_names = {}, {}
+ @visitor = arel_visitor
+
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
+ else
+ @prepared_statements = false
+ end
end
class Version
@@ -142,8 +149,12 @@ module ActiveRecord
end
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::ToSql.new(self)
+ end
+
def valid_type?(type)
- true
+ false
end
def schema_creation
@@ -237,6 +248,11 @@ module ActiveRecord
false
end
+ # Does this adapter support expression indices?
+ def supports_expression_index?
+ false
+ end
+
# Does this adapter support explain?
def supports_explain?
false
@@ -278,6 +294,21 @@ module ActiveRecord
false
end
+ # Does this adapter support metadata comments on database objects (tables, columns, indexes)?
+ def supports_comments?
+ false
+ end
+
+ # Can comments for tables, columns, and indexes be specified in create/alter table statements?
+ def supports_comments_in_create?
+ false
+ end
+
+ # Does this adapter support multi-value insert?
+ def supports_multi_insert?
+ true
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -379,12 +410,6 @@ module ActiveRecord
@connection
end
- def create_savepoint(name = nil)
- end
-
- def release_savepoint(name = nil)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
if value.nil?
table[attribute].eq(value)
@@ -406,10 +431,6 @@ module ActiveRecord
end
private :can_perform_case_insensitive_comparison_for?
- def current_savepoint_name
- current_transaction.savepoint_name
- end
-
# Check the connection back in to the connection pool
def close
pool.checkin self
@@ -433,6 +454,24 @@ module ActiveRecord
visitor.accept(node, collector).value
end
+ def combine_bind_parameters(
+ from_clause: [],
+ join_clause: [],
+ where_clause: [],
+ having_clause: [],
+ limit: nil,
+ offset: nil
+ ) # :nodoc:
+ result = from_clause + join_clause + where_clause + having_clause
+ if limit
+ result << limit
+ end
+ if offset
+ result << offset
+ end
+ result
+ end
+
protected
def initialize_type_map(m) # :nodoc:
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 1797af4e97..fdd6bffa13 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
require 'active_record/connection_adapters/mysql/column'
require 'active_record/connection_adapters/mysql/explain_pretty_printer'
require 'active_record/connection_adapters/mysql/quoting'
@@ -14,16 +15,19 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include MySQL::Quoting
include MySQL::ColumnDumper
- include Savepoints
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
end
- def schema_creation
+ 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>
@@ -52,17 +56,16 @@ module ActiveRecord
INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]
+ class StatementPool < ConnectionAdapters::StatementPool
+ private def dealloc(stmt)
+ stmt[:stmt].close
+ end
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @visitor = Arel::Visitors::MySQL.new self
-
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
if version < '5.0.0'
raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
@@ -77,10 +80,14 @@ module ActiveRecord
}
end
- def version
+ def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
+ def mariadb? # :nodoc:
+ full_version =~ /mariadb/i
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -94,6 +101,12 @@ 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?
@@ -133,11 +146,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
+ select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
+ select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -156,8 +169,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation)
+ def new_column(*args) #:nodoc:
+ MySQL::Column.new(*args)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -179,6 +192,14 @@ module ActiveRecord
end
end
+ # CONNECTION MANAGEMENT ====================================
+
+ # Clears the prepared statements cache.
+ def clear_cache!
+ reload_type_map
+ @statements.clear
+ end
+
#--
# DATABASE STATEMENTS ======================================
#++
@@ -192,11 +213,6 @@ module ActiveRecord
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
end
- def clear_cache!
- super
- reload_type_map
- end
-
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
log(sql, name) { @connection.query(sql) }
@@ -331,8 +347,7 @@ module ActiveRecord
def data_source_exists?(table_name)
return false unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ 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)}"
@@ -347,8 +362,7 @@ module ActiveRecord
def view_exists?(view_name) # :nodoc:
return false unless view_name.present?
- schema, name = view_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A view was provided without a schema
+ 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)}"
@@ -369,7 +383,7 @@ module ActiveRecord
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)
+ 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]
@@ -390,12 +404,20 @@ module ActiveRecord
else
default, default_function = field[:Default], nil
end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation])
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
end
end
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ def table_comment(table_name) # :nodoc:
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT table_comment
+ FROM information_schema.tables
+ WHERE table_name=#{quote(table_name)}
+ SQL
+ end
+
+ def create_table(table_name, **options) #:nodoc:
+ super(table_name, options: 'ENGINE=InnoDB', **options)
end
def bulk_change_table(table_name, operations) #:nodoc:
@@ -478,11 +500,21 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ execute add_sql_comment!(sql, comment)
+ end
+
+ def add_sql_comment!(sql, comment) # :nodoc:
+ sql << " COMMENT #{quote(comment)}" if comment
+ sql
end
def foreign_keys(table_name)
+ raise ArgumentError unless table_name.present?
+
+ schema, name = extract_schema_qualified_name(table_name)
+
fk_info = select_all <<-SQL.strip_heredoc
SELECT fk.referenced_table_name as 'to_table'
,fk.referenced_column_name as 'primary_key'
@@ -490,8 +522,8 @@ module ActiveRecord
,fk.constraint_name as 'name'
FROM information_schema.key_column_usage fk
WHERE fk.referenced_column_name is not null
- AND fk.table_schema = '#{@config[:database]}'
- AND fk.table_name = '#{table_name}'
+ AND fk.table_schema = #{quote(schema)}
+ AND fk.table_name = #{quote(name)}
SQL
create_table_info = create_table_info(table_name)
@@ -517,7 +549,12 @@ module ActiveRecord
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
# strip AUTO_INCREMENT
- raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+
+ # strip COMMENT
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
+
+ raw_table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -553,8 +590,7 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ schema, name = extract_schema_qualified_name(table_name)
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
SELECT column_name
@@ -697,6 +733,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when 1452
InvalidForeignKey.new(message)
+ when 1406
+ ValueTooLong.new(message)
else
super
end
@@ -782,10 +820,6 @@ module ActiveRecord
subselect.from subsubselect.as('__active_record_temp')
end
- def mariadb?
- full_version =~ /mariadb/i
- end
-
def supports_rename_index?
mariadb? ? false : version >= '5.7.6'
end
@@ -866,8 +900,14 @@ module ActiveRecord
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- MySQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ MySQL::TableDefinition.new(*args)
+ 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:
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 2e718b29fa..28f0c8686a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,11 +1,9 @@
-require 'set'
-
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation
+ attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -15,7 +13,7 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
@name = name.freeze
@table_name = table_name
@sql_type_metadata = sql_type_metadata
@@ -23,6 +21,7 @@ module ActiveRecord
@default = default
@default_function = default_function
@collation = collation
+ @comment = comment
end
def has_default?
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 4bc6447368..901c98b22b 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -3,10 +3,10 @@ require 'uri'
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
- attr_reader :config, :adapter_method
+ attr_reader :name, :config, :adapter_method
- def initialize(config, adapter_method)
- @config, @adapter_method = config, adapter_method
+ def initialize(name, config, adapter_method)
+ @name, @config, @adapter_method = name, config, adapter_method
end
def initialize_dup(original)
@@ -164,7 +164,7 @@ module ActiveRecord
# spec.config
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
#
- def spec(config)
+ def spec(config, name = nil)
spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -179,7 +179,14 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
- ConnectionSpecification.new(spec, adapter_method)
+
+ name ||=
+ if config.is_a?(Symbol)
+ config.to_s
+ else
+ "primary"
+ end
+ ConnectionSpecification.new(name, spec, adapter_method)
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
new file mode 100644
index 0000000000..13c9b6cbd9
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -0,0 +1,125 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module DatabaseStatements
+ # Returns an ActiveRecord::Result instance.
+ def select_all(arel, name = nil, binds = [], preparable: nil)
+ result = if ExplainRegistry.collect? && prepared_statements
+ unprepared_statement { super }
+ else
+ super
+ end
+ @connection.next_result while @connection.more_results?
+ result
+ end
+
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation(arel, binds)
+ @connection.query_options.merge!(as: :hash)
+ select_result(to_sql(arel, binds), name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.first
+ end
+ ensure
+ @connection.query_options.merge!(as: :array)
+ end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil, binds = [])
+ select_result(sql, name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.to_a
+ end
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ super
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) do |result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ end
+ end
+
+ def exec_delete(sql, name, binds)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { @connection.affected_rows }
+ else
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
+ end
+ end
+ alias :exec_update :exec_delete
+
+ protected
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
+ private
+
+ def select_result(sql, name = nil, binds = [])
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { |result| yield result }
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
+ end
+ end
+
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+
+ log(sql, name, binds) do
+ if cache_stmt
+ cache = @statements[sql] ||= {
+ stmt: @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ else
+ stmt = @connection.prepare(sql)
+ end
+
+ begin
+ result = stmt.execute(*type_casted_binds)
+ rescue Mysql2::Error => e
+ if cache_stmt
+ @statements.delete(sql)
+ else
+ stmt.close
+ end
+ raise e
+ end
+
+ ret = yield stmt, result
+ result.free if result
+ stmt.close unless cache_stmt
+ ret
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 1e2c859af9..fd2dc2aee8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -2,6 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class SchemaCreation < AbstractAdapter::SchemaCreation
+ delegate :add_sql_comment!, to: :@conn
+ private :add_sql_comment!
+
private
def visit_DropForeignKey(name)
@@ -22,6 +25,10 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def add_table_options!(create_sql, options)
+ add_sql_comment!(super, options[:comment])
+ end
+
def column_options(o)
column_options = super
column_options[:charset] = o.charset
@@ -29,13 +36,15 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- if options[:charset]
- sql << " CHARACTER SET #{options[:charset]}"
+ if charset = options[:charset]
+ sql << " CHARACTER SET #{charset}"
end
- if options[:collation]
- sql << " COLLATE #{options[:collation]}"
+
+ if collation = options[:collation]
+ sql << " COLLATE #{collation}"
end
- super
+
+ add_sql_comment!(super, options[:comment])
end
def add_column_position!(sql, options)
@@ -44,12 +53,13 @@ module ActiveRecord
elsif options[:after]
sql << " AFTER #{quote_column_name(options[:after])}"
end
+
sql
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment)
end
end
end
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 be40df4101..2ba9657f24 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -7,7 +7,7 @@ module ActiveRecord
spec = { id: :bigint.inspect }
spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
else
- spec = super.except!(:null)
+ spec = super
end
spec[:unsigned] = 'true' if column.unsigned?
spec
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index e7541748de..22d35f1db5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_record/connection_adapters/mysql/database_statements'
gem 'mysql2', '>= 0.3.18', '< 0.5'
require 'mysql2'
+raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3'
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -35,9 +37,11 @@ module ActiveRecord
class Mysql2Adapter < AbstractMysqlAdapter
ADAPTER_NAME = 'Mysql2'.freeze
+ include MySQL::DatabaseStatements
+
def initialize(connection, logger, connection_options, config)
super
- @prepared_statements = false
+ @prepared_statements = false unless config.key?(:prepared_statements)
configure_connection
end
@@ -45,6 +49,18 @@ module ActiveRecord
!mariadb? && version >= '5.7.8'
end
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
@@ -95,55 +111,6 @@ module ActiveRecord
end
end
- #--
- # DATABASE STATEMENTS ======================================
- #++
-
- # Returns a record hash with the column names as keys and column values
- # as values.
- def select_one(arel, name = nil, binds = [])
- arel, binds = binds_from_relation(arel, binds)
- execute(to_sql(arel, binds), name).each(as: :hash) do |row|
- @connection.next_result while @connection.more_results?
- return row
- end
- end
-
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- result.to_a
- end
-
- # Executes the SQL statement in the context of this connection.
- def execute(sql, name = nil)
- if @connection
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- end
-
- super
- end
-
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- ActiveRecord::Result.new(result.fields, result.to_a) if result
- end
-
- def exec_delete(sql, name, binds)
- execute to_sql(sql, binds), name
- @connection.affected_rows
- end
- alias :exec_update :exec_delete
-
- def last_inserted_id(result)
- @connection.last_id
- end
-
private
def connect
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index eeccb09bdf..838cb63281 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -1,3 +1,5 @@
+require 'ipaddr'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
index 7427a25ad5..4da240edb2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
@@ -14,6 +14,8 @@ module ActiveRecord
def cast(value)
case value
when ::String
+ return if value.blank?
+
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index 1047ba8cac..a1e10fd364 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module PostgreSQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = super.except!(:null)
+ spec = super
if schema_type(column) == :uuid
spec[:default] ||= 'nil'
end
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 39e8492688..6318b1c65a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -172,7 +174,11 @@ module ActiveRecord
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
+ (SELECT COUNT(*) FROM pg_opclass o
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -190,43 +196,61 @@ module ActiveRecord
indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
+ comment = row[5]
+ opclass = row[6]
- columns = Hash[query(<<-SQL, "SCHEMA")]
- SELECT a.attnum, a.attname
- FROM pg_attribute a
- WHERE a.attrelid = #{oid}
- AND a.attnum IN (#{indkey.join(",")})
- SQL
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
- column_names = columns.values_at(*indkey).compact
+ if indkey.include?(0) || opclass > 0
+ columns = expressions
+ else
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
+ SELECT a.attnum, a.attname
+ FROM pg_attribute a
+ WHERE a.attrelid = #{oid}
+ AND a.attnum IN (#{indkey.join(",")})
+ SQL
- unless column_names.empty?
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
-
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ orders = Hash[
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
+ ]
end
+
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
end.compact
end
# Returns the list of all column definitions for a table.
def columns(table_name) # :nodoc:
table_name = table_name.to_s
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
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)
- new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation)
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
end
end
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
+ def new_column(*args) # :nodoc:
+ PostgreSQLColumn.new(*args)
+ end
+
+ # 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
+ 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))'}
+ SQL
+ end
end
# Returns the current database name.
@@ -445,6 +469,7 @@ module ActiveRecord
def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -466,6 +491,7 @@ module ActiveRecord
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
# Changes the default value of a table column.
@@ -494,6 +520,18 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
+ end
+
+ # Adds comment for given table or drops it if +comment+ is a +nil+
+ def change_table_comment(table_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
+ end
+
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
@@ -502,8 +540,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
+ end
end
def remove_index(table_name, options = {}) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6497b1cc31..bab80a8890 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,8 +16,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'ipaddr'
-
module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects
@@ -119,12 +117,15 @@ module ActiveRecord
include PostgreSQL::SchemaStatements
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- include Savepoints
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?
@@ -139,6 +140,10 @@ module ActiveRecord
true
end
+ def supports_expression_index?
+ true
+ end
+
def supports_transaction_isolation?
true
end
@@ -159,6 +164,14 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_comments?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -195,14 +208,6 @@ module ActiveRecord
def initialize(connection, logger, connection_parameters, config)
super(connection, logger, config)
- @visitor = Arel::Visitors::PostgreSQL.new self
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
-
@connection_parameters = connection_parameters
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -212,7 +217,7 @@ module ActiveRecord
connect
add_pg_encoders
@statements = StatementPool.new @connection,
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
+ self.class.type_cast_config_to_integer(config[:statement_limit])
if postgresql_version < 90100
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
@@ -398,6 +403,7 @@ module ActiveRecord
protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
+ VALUE_LIMIT_VIOLATION = "22001"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
@@ -409,6 +415,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message)
+ when VALUE_LIMIT_VIOLATION
+ ValueTooLong.new(message)
else
super
end
@@ -712,7 +720,7 @@ module ActiveRecord
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
- # SELECT column.name, column.type, default.value
+ # SELECT column.name, column.type, default.value, column.comment
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
@@ -732,7 +740,8 @@ module ActiveRecord
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
(SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
+ col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -746,8 +755,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- PostgreSQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ PostgreSQL::TableDefinition.new(*args)
end
def can_perform_case_insensitive_comparison_for?(column)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index d3a91f73c8..d5a181d3e2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -14,6 +14,10 @@ module ActiveRecord
@quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
+ def quoted_time(value)
+ quoted_date(value)
+ end
+
private
def _quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index fe1dcbd710..70c0d28830 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -3,6 +3,13 @@ module ActiveRecord
module SQLite3
class SchemaCreation < AbstractAdapter::SchemaCreation
private
+
+ def column_options(o)
+ options = super
+ options[:null] = false if o.primary_key
+ options
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 786b0ab2ed..eb2268157b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -52,7 +52,6 @@ module ActiveRecord
ADAPTER_NAME = 'SQLite'.freeze
include SQLite3::Quoting
- include Savepoints
NATIVE_DATABASE_TYPES = {
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
@@ -80,20 +79,15 @@ module ActiveRecord
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)
@active = nil
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
- @visitor = Arel::Visitors::SQLite.new self
-
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
end
def supports_ddl_transactions?
@@ -135,6 +129,10 @@ module ActiveRecord
true
end
+ def supports_multi_insert?
+ sqlite_version >= '3.7.11'
+ end
+
def active?
@active != false
end
@@ -156,6 +154,10 @@ module ActiveRecord
true
end
+ def valid_type?(type)
+ true
+ end
+
# Returns 62. SQLite supports index names up to 64
# characters. The rest is used by rails internally to perform
# temporary rename operations
@@ -230,10 +232,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
- end
-
def begin_db_transaction #:nodoc:
log('begin transaction',nil) { @connection.transaction }
end
@@ -543,7 +541,7 @@ module ActiveRecord
result = exec_query(sql, 'SCHEMA').first
if result
- # Splitting with left parantheses and picking up last will return all
+ # Splitting with left parentheses and picking up last will return all
# columns separated with comma(,).
columns_string = result["sql"].split('(').last
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 57463dd749..9b0ed3e08b 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -1,11 +1,13 @@
module ActiveRecord
module ConnectionAdapters
- class StatementPool
+ class StatementPool # :nodoc:
include Enumerable
- def initialize(max = 1000)
+ DEFAULT_STATEMENT_LIMIT = 1000
+
+ def initialize(statement_limit = nil)
@cache = Hash.new { |h,pid| h[pid] = {} }
- @max = max
+ @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
end
def each(&block)
@@ -25,7 +27,7 @@ module ActiveRecord
end
def []=(sql, stmt)
- while @max <= cache.size
+ while @statement_limit <= cache.size
dealloc(cache.shift.last)
end
cache[sql] = stmt
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index a8b3d03ba5..f932deb18d 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -45,16 +45,20 @@ module ActiveRecord
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
def establish_connection(spec = nil)
+ raise "Anonymous class is not allowed." unless name
+
spec ||= DEFAULT_ENV.call.to_sym
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
- spec = resolver.spec(spec)
+ # TODO: uses name on establish_connection, for backwards compatibility
+ spec = resolver.spec(spec, self == Base ? "primary" : name)
unless respond_to?(spec.adapter_method)
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
end
- remove_connection
- connection_handler.establish_connection self, spec
+ remove_connection(spec.name)
+ self.connection_specification_name = spec.name
+ connection_handler.establish_connection spec
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
@@ -87,6 +91,16 @@ module ActiveRecord
retrieve_connection
end
+ attr_writer :connection_specification_name
+
+ # Return the specification name from the current class or its parent.
+ def connection_specification_name
+ if !defined?(@connection_specification_name) || @connection_specification_name.nil?
+ return self == Base ? "primary" : superclass.connection_specification_name
+ end
+ @connection_specification_name
+ end
+
def connection_id
ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
end
@@ -106,20 +120,28 @@ module ActiveRecord
end
def connection_pool
- connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
+ connection_handler.retrieve_connection_pool(connection_specification_name) or raise ConnectionNotEstablished
end
def retrieve_connection
- connection_handler.retrieve_connection(self)
+ connection_handler.retrieve_connection(connection_specification_name)
end
# Returns +true+ if Active Record is connected.
def connected?
- connection_handler.connected?(self)
+ connection_handler.connected?(connection_specification_name)
end
- def remove_connection(klass = self)
- connection_handler.remove_connection(klass)
+ def remove_connection(name = nil)
+ name ||= @connection_specification_name if defined?(@connection_specification_name)
+ # if removing a connection that have a pool, we reset the
+ # connection_specification_name so it will use the parent
+ # pool.
+ if connection_handler.retrieve_connection_pool(name)
+ self.connection_specification_name = nil
+ end
+
+ connection_handler.remove_connection(name)
end
def clear_cache! # :nodoc:
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 5d74631e32..f936e865e4 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -257,7 +257,7 @@ module ActiveRecord
# Returns the Arel engine.
def arel_engine # :nodoc:
@arel_engine ||=
- if Base == self || connection_handler.retrieve_connection_pool(self)
+ if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name)
self
else
superclass.arel_engine
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 2ec9bf3d67..b8b8684cff 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -125,6 +125,10 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
+ # Raised when a record cannot be inserted or updated because a value too long for a column type.
+ class ValueTooLong < StatementInvalid
+ end
+
# Raised when number of bind variables in statement given to +:condition+ key
# (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
# does not match number of expected values supplied.
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 73be4cb271..f33456a744 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 = 0
+ MINOR = 1
TINY = 0
- PRE = "beta3"
+ 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 2336d23a1c..67b8efac66 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -150,7 +150,7 @@ module ActiveRecord
# The version column used for optimistic locking. Defaults to +lock_version+.
def locking_column
- reset_locking_column unless defined?(@locking_column)
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
@locking_column
end
@@ -184,9 +184,16 @@ module ActiveRecord
end
end
+
+ # In de/serialize we change `nil` to 0, so that we can allow passing
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
+ # during update record.
class LockingType < DelegateClass(Type::Value) # :nodoc:
def deserialize(value)
- # `nil` *should* be changed to 0
+ super.to_i
+ end
+
+ def serialize(value)
super.to_i
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 99a79024ad..81fe053fe1 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -166,13 +166,13 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
- msg << "You are running in `#{ current }` environment."
+ msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << "\tbin/rails db:environment:set"
if defined?(Rails.env)
- super("#{msg} RAILS_ENV=#{::Rails.env}")
+ super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
else
- super(msg)
+ super("#{msg}\n\n")
end
end
end
@@ -277,7 +277,7 @@ module ActiveRecord
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
# * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a
- # default value for +column_name+ definded by +default+ on +table_name+.
+ # default value for +column_name+ defined by +default+ on +table_name+.
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
# indicates whether the value can be +NULL+. See
@@ -524,17 +524,11 @@ module ActiveRecord
end
def self.[](version)
- version = version.to_s
- name = "V#{version.tr('.', '_')}"
- unless Compatibility.const_defined?(name)
- versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
- raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
- end
- Compatibility.const_get(name)
+ Compatibility.find(version)
end
def self.current_version
- Rails.version.to_f
+ ActiveRecord::VERSION::STRING.to_f
end
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index a20d7e0820..74833f938f 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -1,7 +1,17 @@
module ActiveRecord
class Migration
module Compatibility # :nodoc: all
- V5_0 = Current
+ def self.find(version)
+ version = version.to_s
+ name = "V#{version.tr('.', '_')}"
+ unless const_defined?(name)
+ versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
+ raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
+ end
+ const_get(name)
+ end
+
+ V5_1 = Current
module FourTwoShared
module TableDefinition
@@ -92,6 +102,9 @@ module ActiveRecord
end
end
+ class V5_0 < V5_1
+ end
+
class V4_2 < V5_0
# 4.2 is defined as a module because it needs to be shared with
# Legacy. When the time comes, V5_0 should be defined straight
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 52eab952e1..f691a8319d 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -238,7 +238,7 @@ module ActiveRecord
end
# Returns the next value that will be used as the primary key on
- # an insert statment.
+ # an insert statement.
def next_sequence_value
connection.next_sequence_value(sequence_name)
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index bbbc824b25..ca12a603da 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -44,8 +44,9 @@ module ActiveRecord
executor.register_hook(self)
executor.to_complete do
- # FIXME: This should be skipped when env['rack.test']
- ActiveRecord::Base.clear_active_connections!
+ unless ActiveRecord::Base.connection.transaction_open?
+ ActiveRecord::Base.clear_active_connections!
+ end
end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 4e32d73001..53ddd95bb0 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Querying
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :empty?, :none?, :one?, to: :all
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all
delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index 13393dc605..333b3a63cf 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -42,7 +42,7 @@ module ActiveRecord
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
#
# People.in_batches.delete_all
- # People.in_batches.destroy_all('age < 10')
+ # People.where('age < 10').in_batches.destroy_all
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 54c9af4898..120f34109e 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,7 +37,11 @@ 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)
- calculate(:count, column_name)
+ if block_given?
+ to_a.count { |*block_args| yield(*block_args) }
+ else
+ calculate(:count, column_name)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index f2578f5f96..2484cb3264 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,4 +1,3 @@
-require 'set'
require 'active_support/concern'
module ActiveRecord
@@ -36,7 +35,7 @@ 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, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
:shuffle, :split, to: :records
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 550416238f..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -84,7 +84,6 @@ module ActiveRecord
return ["1=0"] if attributes.empty?
attributes.flat_map do |key, value|
- key = key.to_s
if value.is_a?(Hash)
associated_predicate_builder(key).expand_from_hash(value)
else
@@ -137,11 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select do |s|
- s.respond_to?(:include?) && s.include?(".".freeze)
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4533f3263f..6477629560 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -99,22 +99,28 @@ module ActiveRecord
end
def bound_attributes
- result = from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
if limit_value && !string_containing_comma?(limit_value)
- result << Attribute.with_cast_value(
+ limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type::Value.new,
)
end
if offset_value
- result << Attribute.with_cast_value(
+ offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type::Value.new,
)
end
- result
+ connection.combine_bind_parameters(
+ from_clause: from_clause.binds,
+ join_clause: arel.bind_values,
+ where_clause: where_clause.binds,
+ having_clause: having_clause.binds,
+ limit: limit_bind,
+ offset: offset_bind,
+ )
end
FROZEN_EMPTY_HASH = {}.freeze
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 2c2d6cfa47..89396b518c 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -158,8 +158,9 @@ module ActiveRecord
end
end
+ ARRAY_WITH_EMPTY_STRING = ['']
def non_empty_predicates
- predicates - ['']
+ predicates - ARRAY_WITH_EMPTY_STRING
end
def wrap_sql_literal(node)
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index c0ccb00b6f..dbf172a577 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -15,6 +15,7 @@ module ActiveRecord
when Hash
attributes = predicate_builder.resolve_column_aliases(opts)
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
+ attributes.stringify_keys!
attributes, binds = predicate_builder.create_binds(attributes)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index affcd9aed1..301718b874 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -138,6 +138,10 @@ HEADER
table_options = @connection.table_options(table)
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+ if comment = @connection.table_comment(table).presence
+ tbl.print ", comment: #{comment.inspect}"
+ end
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -175,11 +179,11 @@ HEADER
tbl.puts
end
+ indexes_in_create(table, tbl)
+
tbl.puts " end"
tbl.puts
- indexes(table, tbl)
-
tbl.rewind
stream.print tbl.read
rescue => e
@@ -191,26 +195,12 @@ HEADER
stream
end
+ # Keep it for indexing materialized views
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
- statement_parts = [
- "add_index #{remove_prefix_and_suffix(index.table).inspect}",
- index.columns.inspect,
- "name: #{index.name.inspect}",
- ]
- statement_parts << 'unique: true' if index.unique
-
- index_lengths = (index.lengths || []).compact
- statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
-
- index_orders = index.orders || {}
- statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
- statement_parts << "where: #{index.where.inspect}" if index.where
- statement_parts << "using: #{index.using.inspect}" if index.using
- statement_parts << "type: #{index.type.inspect}" if index.type
-
- " #{statement_parts.join(', ')}"
+ table_name = remove_prefix_and_suffix(index.table).inspect
+ " add_index #{([table_name]+index_parts(index)).join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
@@ -218,6 +208,34 @@ HEADER
end
end
+ def indexes_in_create(table, stream)
+ if (indexes = @connection.indexes(table)).any?
+ index_statements = indexes.map do |index|
+ " t.index #{index_parts(index).join(', ')}"
+ end
+ stream.puts index_statements.sort.join("\n")
+ end
+ end
+
+ def index_parts(index)
+ index_parts = [
+ index.columns.inspect,
+ "name: #{index.name.inspect}",
+ ]
+ index_parts << 'unique: true' if index.unique
+
+ index_lengths = (index.lengths || []).compact
+ index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
+
+ index_orders = index.orders || {}
+ index_parts << "order: #{index.orders.inspect}" if index_orders.any?
+ index_parts << "where: #{index.where.inspect}" if index.where
+ index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "type: #{index.type.inspect}" if index.type
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
+ index_parts
+ end
+
def foreign_keys(table, stream)
if (foreign_keys = @connection.foreign_keys(table)).any?
add_foreign_key_statements = foreign_keys.map do |foreign_key|
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index 8ec4b48d31..d9acb1a1dc 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -30,10 +30,11 @@ module ActiveRecord
module ClassMethods
def suppress(&block)
+ previous_state = SuppressorRegistry.suppressed[name]
SuppressorRegistry.suppressed[name] = true
yield
ensure
- SuppressorRegistry.suppressed[name] = false
+ SuppressorRegistry.suppressed[name] = previous_state
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 8881986f1b..0df46d54df 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -107,8 +107,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
+ $stdout.puts "Created database '#{configuration['database']}'"
rescue DatabaseAlreadyExists
- $stderr.puts "#{configuration['database']} already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists"
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -116,10 +117,10 @@ module ActiveRecord
end
def create_all
- old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base)
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
each_local_configuration { |configuration| create configuration }
if old_pool
- ActiveRecord::Base.connection_handler.establish_connection(ActiveRecord::Base, old_pool.spec)
+ ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec)
end
end
@@ -133,11 +134,12 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ $stdout.puts "Dropped database '#{configuration['database']}'"
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't drop #{configuration['database']}"
+ $stderr.puts "Couldn't drop database '#{configuration['database']}'"
raise
end
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 097d1bd363..513c938088 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,11 +17,7 @@ module ActiveRecord
end
def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
+ ::ActiveSupport::JSON.encode(value)
end
def accessor
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4ff0740cfb..a3a5241780 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -32,7 +32,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- raw_new_value = serialize(value)
+ raw_new_value = encoded(value)
raw_old_value.nil? != raw_new_value.nil? ||
subtype.changed_in_place?(raw_old_value, raw_new_value)
end
@@ -52,6 +52,12 @@ module ActiveRecord
def default_value?(value)
value == coder.load(nil)
end
+
+ def encoded(value)
+ unless default_value?(value)
+ coder.dump(value)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 70988d84ff..7da49e43c7 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -2,6 +2,18 @@ module ActiveRecord
module Type
class Time < ActiveModel::Type::Time
include Internal::Timezone
+
+ class Value < DelegateClass(::Time) # :nodoc:
+ end
+
+ def serialize(value)
+ case value = super
+ when ::Time
+ Value.new(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 4a80cda0b8..1f59276137 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -67,9 +67,6 @@ module ActiveRecord
cast_type = klass.type_for_attribute(attribute_name)
value = cast_type.serialize(value)
value = klass.connection.type_cast(value)
- if value.is_a?(String) && column.limit
- value = value.to_s[0, column.limit]
- end
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation