aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb37
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/fixtures.rb3
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb1
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb40
-rw-r--r--activerecord/lib/active_record/sanitization.rb3
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
34 files changed, 292 insertions, 162 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 84d0493a60..1cb2b2d7c6 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -83,7 +83,7 @@ module ActiveRecord
end
def scope
- target_scope.merge(association_scope)
+ target_scope.merge!(association_scope)
end
# The scope for this association.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 0437a79b84..77282e6463 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -30,13 +30,7 @@ module ActiveRecord
reload
end
- if null_scope?
- # Cache the proxy separately before the owner has an id
- # or else a post-save proxy will still lack the id
- @null_proxy ||= CollectionProxy.create(klass, self)
- else
- @proxy ||= CollectionProxy.create(klass, self)
- end
+ CollectionProxy.create(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -315,9 +309,9 @@ module ActiveRecord
record
end
- def scope(opts = {})
- scope = super()
- scope.none! if opts.fetch(:nullify, true) && null_scope?
+ def scope
+ scope = super
+ scope.none! if null_scope?
scope
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 0d84805b4d..55bf2e0ff0 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,12 +28,9 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate :exists?, :update_all, :arel, to: :scope
-
def initialize(klass, association) #:nodoc:
@association = association
super klass, klass.arel_table, klass.predicate_builder
- merge! association.scope(nullify: false)
end
def target
@@ -956,19 +953,10 @@ module ActiveRecord
@association
end
- # We don't want this object to be put on the scoping stack, because
- # that could create an infinite loop where we call an @association
- # method, which gets the current scope, which is this object, which
- # delegates to @association, and so on.
- def scoping
- @association.scope.scoping { yield }
- end
-
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- @association.scope
+ @scope ||= @association.scope
end
- alias spawn scope
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
# contain the same number of elements and if each element is equal
@@ -1100,6 +1088,7 @@ module ActiveRecord
# person.pets(true) # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
+ @scope = nil
proxy_association.reload
self
end
@@ -1121,11 +1110,21 @@ module ActiveRecord
# person.pets # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reset
+ @scope = nil
proxy_association.reset
proxy_association.reset_scope
self
end
+ delegate_methods = [
+ QueryMethods,
+ SpawnMethods,
+ ].flat_map { |klass|
+ klass.public_instance_methods(false)
+ } - self.public_instance_methods(false) + [:scoping]
+
+ delegate(*delegate_methods, to: :scope)
+
private
def find_nth_with_limit(index, limit)
@@ -1149,6 +1148,18 @@ module ActiveRecord
def exec_queries
load_target
end
+
+ def respond_to_missing?(method, _)
+ scope.respond_to?(method) || super
+ end
+
+ def method_missing(method, *args, &block)
+ if scope.respond_to?(method)
+ scope.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index b0e1391cb9..31c1e687dc 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -275,7 +275,17 @@ module ActiveRecord
def attribute_will_change!(attr_name)
super
- mutations_from_database.force_change(attr_name)
+ if self.class.has_attribute?(attr_name)
+ mutations_from_database.force_change(attr_name)
+ else
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
+ #{attr_name} is not an attribute known to Active Record.
+ This behavior is deprecated and will be removed in the next
+ version of Rails. If you'd like #{attr_name} to be managed
+ by Active Record, add `attribute :#{attr_name} to your class.
+ EOW
+ mutations_from_database.deprecated_force_change(attr_name)
+ end
end
def _update_record(*)
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index df1231ad47..321d039ed4 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/strip"
-
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 3417090830..4de993e169 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -5,6 +5,7 @@ module ActiveRecord
def initialize(attributes)
@attributes = attributes
@forced_changes = Set.new
+ @deprecated_forced_changes = Set.new
end
def changed_values
@@ -31,7 +32,7 @@ module ActiveRecord
end
def any_changes?
- attr_names.any? { |attr| changed?(attr) }
+ attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any?
end
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
@@ -60,11 +61,15 @@ module ActiveRecord
forced_changes << attr_name.to_s
end
+ def deprecated_force_change(attr_name)
+ deprecated_forced_changes << attr_name.to_s
+ end
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
- attr_reader :attributes, :forced_changes
+ attr_reader :attributes, :forced_changes, :deprecated_forced_changes
private
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 ce4721c99d..3f2e86a98d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -353,6 +353,16 @@ module ActiveRecord
@threads_blocking_new_connections = 0
@available = ConnectionLeasingQueue.new self
+
+ @lock_thread = false
+ end
+
+ def lock_thread=(lock_thread)
+ if lock_thread
+ @lock_thread = Thread.current
+ else
+ @lock_thread = nil
+ end
end
# Retrieve the connection associated with the current thread, or call
@@ -361,7 +371,7 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a cache keyed by a thread.
def connection
- @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 7eab7de5d3..e53ba4e666 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -83,7 +83,9 @@ module ActiveRecord
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
- @query_cache.clear
+ @lock.synchronize do
+ @query_cache.clear
+ end
end
def select_all(arel, name = nil, binds = [], preparable: nil)
@@ -99,21 +101,23 @@ module ActiveRecord
private
def cache_sql(sql, name, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument(
- "sql.active_record",
- sql: sql,
- binds: binds,
- name: name,
- connection_id: object_id,
- cached: true,
- )
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
- result.dup
+ @lock.synchronize do
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument(
+ "sql.active_record",
+ sql: sql,
+ binds: binds,
+ name: name,
+ connection_id: object_id,
+ cached: true,
+ )
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ result.dup
+ end
end
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 437e7c6510..e5a24b2aca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -7,8 +7,13 @@ module ActiveRecord
# Quotes the column value to help prevent
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
def quote(value)
- # records are quoted as their primary key
- return value.quoted_id if value.respond_to?(:quoted_id)
+ value = id_value_for_database(value) if value.is_a?(Base)
+
+ if value.respond_to?(:quoted_id)
+ ActiveSupport::Deprecation.warn \
+ "Using #quoted_id is deprecated and will be removed in Rails 5.2."
+ return value.quoted_id
+ end
_quote(value)
end
@@ -17,6 +22,8 @@ module ActiveRecord
# SQLite does not understand dates, so this method will convert a Date
# to a String.
def type_cast(value, column = nil)
+ value = id_value_for_database(value) if value.is_a?(Base)
+
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
return value.id
end
@@ -141,19 +148,29 @@ module ActiveRecord
quoted_date(value).sub(/\A2000-01-01 /, "")
end
+ def quoted_binary(value) # :nodoc:
+ "'#{quote_string(value.to_s)}'"
+ end
+
private
def type_casted_binds(binds)
binds.map { |attr| type_cast(attr.value_for_database) }
end
+ def id_value_for_database(value)
+ if primary_key = value.class.primary_key
+ value.instance_variable_get(:@attributes)[primary_key].value_for_database
+ end
+ end
+
def types_which_need_no_typecasting
[nil, Numeric, String]
end
def _quote(value)
case value
- when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ when String, ActiveSupport::Multibyte::Chars
"'#{quote_string(value.to_s)}'"
when true then quoted_true
when false then quoted_false
@@ -161,6 +178,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::Binary::Data then quoted_binary(value)
when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
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 dff84613a8..c44215cd43 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -273,8 +273,8 @@ module ActiveRecord
yield td if block_given?
- if options[:force] && data_source_exists?(table_name)
- drop_table(table_name, options)
+ if options[:force]
+ drop_table(table_name, **options, if_exists: true)
end
result = execute schema_creation.accept td
@@ -768,16 +768,17 @@ module ActiveRecord
raise ArgumentError, "You must specify the index name"
end
else
- index_name(table_name, column: options)
+ index_name(table_name, index_name_options(options))
end
end
# Verifies the existence of an index with a given name.
- #
- # The default argument is returned if the underlying implementation does not define the indexes method,
- # as there's no way to determine the correct answer in that case.
- def index_name_exists?(table_name, index_name, default)
- return default unless respond_to?(:indexes)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
index_name = index_name.to_s
indexes(table_name).detect { |i| i.name == index_name }
end
@@ -1071,7 +1072,7 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
- elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
+ elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
if (0..6) === precision
column_type_sql << "(#{precision})"
else
@@ -1123,18 +1124,14 @@ module ActiveRecord
end
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
- if column_name.is_a?(String) && /\W/.match?(column_name)
- column_names = column_name
- else
- column_names = Array(column_name)
- end
+ column_names = index_column_names(column_name)
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, index_name_options(column_names))
+ index_name ||= index_name(table_name, column_names)
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1150,7 +1147,7 @@ module ActiveRecord
validate_index_length!(table_name, index_name, options.fetch(:internal, false))
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
@@ -1214,13 +1211,13 @@ module ActiveRecord
if options.is_a?(Hash)
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
- column_names = Array(options[:column]).map(&:to_s)
+ column_names = index_column_names(options[:column])
else
- column_names = Array(options).map(&:to_s)
+ column_names = index_column_names(options)
end
- if column_names.any?
- checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") }
+ if column_names.present?
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
end
raise ArgumentError, "No name or columns specified" if checks.none?
@@ -1267,8 +1264,16 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def index_column_names(column_names)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
+ column_names
+ else
+ Array(column_names)
+ end
+ end
+
def index_name_options(column_names)
- if column_names.is_a?(String)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
column_names = column_names.scan(/\w+/).join("_")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ae24ce7236..b31ce0a181 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,6 +107,7 @@ module ActiveRecord
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
+ @lock = Monitor.new
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -236,11 +237,10 @@ module ActiveRecord
false
end
- # Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables?
- def supports_primary_key?
- false
+ def supports_primary_key? # :nodoc:
+ true
end
+ deprecate :supports_primary_key?
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
@@ -510,6 +510,10 @@ module ActiveRecord
result
end
+ def default_index_type?(index) # :nodoc:
+ index.using.nil?
+ end
+
private
def initialize_type_map(m)
@@ -602,7 +606,11 @@ module ActiveRecord
binds: binds,
type_casted_binds: type_casted_binds,
statement_name: statement_name,
- connection_id: object_id) { yield }
+ connection_id: object_id) do
+ @lock.synchronize do
+ yield
+ end
+ end
rescue => e
raise translate_exception_class(e, sql)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 279c232144..5f86a11c2d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -46,6 +46,7 @@ module ActiveRecord
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
+ timestamp: { name: "timestamp" },
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob", limit: 65535 },
@@ -93,10 +94,6 @@ module ActiveRecord
true
end
- def supports_primary_key?
- true
- end
-
def supports_bulk_alter? #:nodoc:
true
end
@@ -655,6 +652,10 @@ module ActiveRecord
!native_database_types[type].nil?
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
def initialize_type_map(m)
@@ -708,7 +709,7 @@ module ActiveRecord
end
def extract_precision(sql_type)
- if /time/.match?(sql_type)
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
super || 0
else
super
@@ -869,9 +870,9 @@ module ActiveRecord
variables["sql_auto_is_null"] = 0
# Increase timeout so the server doesn't disconnect us.
- wait_timeout = @config[:wait_timeout]
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
- variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout)
+ variables["wait_timeout"] = wait_timeout
defaults = [":default", :default].to_set
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index dcf56997db..3e4ea28f63 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -149,9 +149,18 @@ module ActiveRecord
# Expands each key in @configurations hash into fully resolved hash
def resolve_all
config = configurations.dup
+
+ if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
+ end
+
+ config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) }
+ config.merge! env_config if env_config
+
config.each do |key, value|
config[key] = resolve(value) if value
end
+
config
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index 9d11ad28d4..d4f5906b33 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -36,15 +36,9 @@ module ActiveRecord
end
end
- private
-
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ 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 e8358271ab..083cd6340f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -25,6 +25,14 @@ module ActiveRecord
end
def add_column_options!(sql, options)
+ # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
+ # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
+ # column to contain NULL, explicitly declare it with the NULL attribute.
+ # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
+ if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
+ sql << " NULL" unless options[:null] == false || options_include_default?(options)
+ end
+
if charset = options[:charset]
sql << " CHARACTER SET #{charset}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index 773bbcef4e..6d88c14d50 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -75,6 +75,11 @@ module ActiveRecord
super
end
+
+ private
+ def aliased_types(name, fallback)
+ fallback
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
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 ad4a069d73..3e0afd9761 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -30,7 +30,10 @@ module ActiveRecord
end
def schema_type(column)
- if column.sql_type == "tinyblob"
+ case column.sql_type
+ when /\Atimestamp\b/
+ :timestamp
+ when "tinyblob"
:blob
else
super
@@ -38,7 +41,7 @@ module ActiveRecord
end
def schema_precision(column)
- super unless /time/.match?(column.sql_type) && column.precision == 0
+ super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
end
def schema_collation(column)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 0e526f6201..4098250f3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/postgresql/oid/inet"
require "active_record/connection_adapters/postgresql/oid/json"
require "active_record/connection_adapters/postgresql/oid/jsonb"
require "active_record/connection_adapters/postgresql/oid/money"
+require "active_record/connection_adapters/postgresql/oid/oid"
require "active_record/connection_adapters/postgresql/oid/point"
require "active_record/connection_adapters/postgresql/oid/legacy_point"
require "active_record/connection_adapters/postgresql/oid/range"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
new file mode 100644
index 0000000000..9c2ac08b30
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Oid < Type::Integer # :nodoc:
+ def type
+ :oid
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
index 2d2fede4e8..564e82a4ac 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -5,8 +5,9 @@ module ActiveRecord
class SpecializedString < Type::String # :nodoc:
attr_reader :type
- def initialize(type)
+ def initialize(type, **options)
@type = type
+ super(options)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 3783925954..6663448a99 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,6 +55,10 @@ module ActiveRecord
end
end
+ def quoted_binary(value) # :nodoc:
+ "'#{escape_bytea(value.to_s)}'"
+ end
+
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
@@ -76,8 +80,6 @@ module ActiveRecord
def _quote(value)
case value
- when Type::Binary::Data
- "'#{escape_bytea(value.to_s)}'"
when OID::Xml::Data
"xml '#{quote_string(value.to_s)}'"
when OID::Bit::Data
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 7491fb716e..11ea1e5144 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -88,6 +88,10 @@ module ActiveRecord
args.each { |name| column(name, :inet, options) }
end
+ def interval(*args, **options)
+ args.each { |name| column(name, :interval, options) }
+ end
+
def int4range(*args, **options)
args.each { |name| column(name, :int4range, options) }
end
@@ -120,6 +124,10 @@ module ActiveRecord
args.each { |name| column(name, :numrange, options) }
end
+ def oid(*args, **options)
+ args.each { |name| column(name, :oid, options) }
+ end
+
def point(*args, **options)
args.each { |name| column(name, :point, options) }
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 a4b1723fc7..a61d920a73 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -132,7 +132,12 @@ module ActiveRecord
end
# Verifies existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
table = Utils.extract_schema_qualified_name(table_name.to_s)
index = Utils.extract_schema_qualified_name(index_name.to_s)
@@ -425,17 +430,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- WITH pk_constraint AS (
- SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
- WHERE contype = 'p'
- AND conrelid = #{quote(quote_table_name(table_name))}::regclass
- ), cons AS (
- SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
- )
- SELECT attr.attname FROM pg_attribute attr
- INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
- ORDER BY cons.rownum
+ SELECT column_name
+ FROM information_schema.key_column_usage kcu
+ JOIN information_schema.table_constraints tc
+ ON kcu.table_name = tc.table_name
+ AND kcu.table_schema = tc.table_schema
+ AND kcu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'PRIMARY KEY'
+ AND kcu.table_name = #{quote(name.identifier)}
+ AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
+ ORDER BY kcu.ordinal_position
SQL
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6b1e870a31..c89e29ba44 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -109,6 +109,8 @@ module ActiveRecord
bit: { name: "bit" },
bit_varying: { name: "bit varying" },
money: { name: "money" },
+ interval: { name: "interval" },
+ oid: { name: "oid" },
}
OID = PostgreSQL::OID #:nodoc:
@@ -234,7 +236,9 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.clear
+ @lock.synchronize do
+ @statements.clear
+ end
end
def truncate(table_name, name = nil)
@@ -282,11 +286,6 @@ module ActiveRecord
true
end
- # Does PostgreSQL support finding primary key on non-Active Record tables?
- def supports_primary_key? #:nodoc:
- true
- end
-
def set_standard_conforming_strings
execute("SET standard_conforming_strings = on", "SCHEMA")
end
@@ -405,6 +404,10 @@ module ActiveRecord
@connection.server_version
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
@@ -456,7 +459,7 @@ module ActiveRecord
register_class_with_limit m, "int2", Type::Integer
register_class_with_limit m, "int4", Type::Integer
register_class_with_limit m, "int8", Type::Integer
- m.alias_type "oid", "int2"
+ m.register_type "oid", OID::Oid.new
m.register_type "float4", Type::Float.new
m.alias_type "float8", "float4"
m.register_type "text", Type::Text.new
@@ -491,8 +494,10 @@ module ActiveRecord
m.register_type "polygon", OID::SpecializedString.new(:polygon)
m.register_type "circle", OID::SpecializedString.new(:circle)
- # FIXME: why are we keeping these types as strings?
- m.alias_type "interval", "varchar"
+ m.register_type "interval" do |_, _, sql_type|
+ precision = extract_precision(sql_type)
+ OID::SpecializedString.new(:interval, precision: precision)
+ end
register_class_with_precision m, "time", Type::Time
register_class_with_precision m, "timestamp", OID::DateTime
@@ -634,8 +639,10 @@ module ActiveRecord
if in_transaction?
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
else
- # outside of transactions we can simply flush this query and retry
- @statements.delete sql_key(sql)
+ @lock.synchronize do
+ # outside of transactions we can simply flush this query and retry
+ @statements.delete sql_key(sql)
+ end
retry
end
end
@@ -671,19 +678,21 @@ module ActiveRecord
# Prepare the statement if it hasn't been prepared, return
# the statement key.
def prepare_statement(sql)
- sql_key = sql_key(sql)
- unless @statements.key? sql_key
- nextkey = @statements.next_key
- begin
- @connection.prepare nextkey, sql
- rescue => e
- raise translate_exception_class(e, sql)
+ @lock.synchronize do
+ sql_key = sql_key(sql)
+ unless @statements.key? sql_key
+ nextkey = @statements.next_key
+ begin
+ @connection.prepare nextkey, sql
+ rescue => e
+ raise translate_exception_class(e, sql)
+ end
+ # Clear the queue
+ @connection.get_last_result
+ @statements[sql_key] = nextkey
end
- # Clear the queue
- @connection.get_last_result
- @statements[sql_key] = nextkey
+ @statements[sql_key]
end
- @statements[sql_key]
end
# Connects to a PostgreSQL server and sets up the adapter depending on the
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index f01ed67b0f..7276a65098 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -18,15 +18,11 @@ module ActiveRecord
quoted_date(value)
end
- private
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ end
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ private
def _type_cast(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 3d728d74cf..16ef195bfc 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -122,10 +122,6 @@ module ActiveRecord
true
end
- def supports_primary_key? #:nodoc:
- true
- end
-
def requires_reloading?
true
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 91d8054ef2..e79167d568 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -970,6 +970,7 @@ module ActiveRecord
@fixture_connections = enlist_fixture_connections
@fixture_connections.each do |connection|
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
end
# When connections are established in the future, begin a transaction too
@@ -985,6 +986,7 @@ module ActiveRecord
if connection && !@fixture_connections.include?(connection)
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
@fixture_connections << connection
end
end
@@ -1007,6 +1009,7 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
@fixture_connections.each do |connection|
connection.rollback_transaction if connection.transaction_open?
+ connection.pool.lock_thread = false
end
@fixture_connections.clear
else
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index f33456a744..174f716152 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveRecord
MAJOR = 5
MINOR = 1
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index ffb54f5137..85032ce470 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -145,13 +145,13 @@ module ActiveRecord
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_name_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name)
if options.is_a?(Hash) && options.has_key?(:name)
options_without_column = options.dup
options_without_column.delete :column
index_name_without_column = index_name(table_name, options_without_column)
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
end
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 2a28c6bf6d..54216caaaf 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -432,6 +432,7 @@ module ActiveRecord
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
+ initialize_find_by_cache
end
private
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index ef0d059d1c..417b24c7bb 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -25,10 +25,7 @@ module ActiveRecord
end
def except(*columns)
- WhereClause.new(
- predicates_except(columns),
- binds_except(columns),
- )
+ WhereClause.new(*except_predicates_and_binds(columns))
end
def or(other)
@@ -134,20 +131,35 @@ module ActiveRecord
end
end
- def predicates_except(columns)
- predicates.reject do |node|
- case node
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
- subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
- columns.include?(subrelation.name.to_s)
+ def except_predicates_and_binds(columns)
+ except_binds = []
+ binds_index = 0
+
+ predicates = self.predicates.reject do |node|
+ except = \
+ case node
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
+ binds_contains = node.grep(Arel::Nodes::BindParam).size
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
+ columns.include?(subrelation.name.to_s)
+ end
+
+ if except && binds_contains > 0
+ (binds_index...(binds_index + binds_contains)).each do |i|
+ except_binds[i] = true
+ end
+
+ binds_index += binds_contains
end
+
+ except
end
- end
- def binds_except(columns)
- binds.reject do |attr|
- columns.include?(attr.name)
+ binds = self.binds.reject.with_index do |_, i|
+ except_binds[i]
end
+
+ [predicates, binds]
end
def predicates_with_wrapped_sql_literals
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 427c0019c6..64bda1539c 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module Sanitization
extend ActiveSupport::Concern
@@ -207,9 +206,9 @@ module ActiveRecord
end
end
- # TODO: Deprecate this
def quoted_id # :nodoc:
self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
end
+ deprecate :quoted_id
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 12289511b7..15533f0151 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -188,7 +188,7 @@ HEADER
index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
index_parts << "where: #{index.where.inspect}" if index.where
- index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
index_parts << "type: #{index.type.inspect}" if index.type
index_parts << "comment: #{index.comment.inspect}" if index.comment
index_parts
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index ca5eda2f84..7cfd55f516 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -57,7 +57,7 @@ module ActiveRecord
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See ActiveModel::Validation#validates! for more information.
+ # See ActiveModel::Validations#validates! for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end