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/attribute_methods/dirty.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb53
-rw-r--r--activerecord/lib/active_record/querying.rb19
5 files changed, 72 insertions, 16 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index ebc2252c50..45e4b8adfa 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -158,9 +158,13 @@ module ActiveRecord
end
private
- def write_attribute_without_type_cast(attr_name, _)
- result = super
- clear_attribute_change(attr_name)
+ def write_attribute_without_type_cast(attr_name, value)
+ name = attr_name.to_s
+ if self.class.attribute_alias?(name)
+ name = self.class.attribute_alias(name)
+ end
+ result = super(name, value)
+ clear_attribute_change(name)
result
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 2e7a78215a..f60d8469cc 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -174,12 +174,12 @@ module ActiveRecord
if e.path == path_to_adapter
# We can assume that a non-builtin adapter was specified, so it's
# either misspelled or missing from Gemfile.
- raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
+ raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
# Bubbled up from the adapter require. Prefix the exception message
# with some guidance about how to address it and reraise.
else
- raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
+ raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index bfd300723d..f2f4701500 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -68,7 +68,7 @@ module ActiveRecord
# * <tt>"schema_name".table_name</tt>
# * <tt>"schema.name"."table name"</tt>
def extract_schema_qualified_name(string)
- schema, table = string.scan(/[^".\s]+|"[^"]*"/)
+ schema, table = string.scan(/[^".]+|"[^"]*"/)
if table.nil?
table = schema
schema = nil
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 33c4066b89..90fb10a1f1 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -96,8 +96,19 @@ module ActiveRecord
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
# +false+ (which it is by default until Rails 6.0).
def cache_version
- if cache_versioning && timestamp = try(:updated_at)
- timestamp.utc.to_s(:usec)
+ return unless cache_versioning
+
+ if has_attribute?("updated_at")
+ timestamp = updated_at_before_type_cast
+ if can_use_fast_cache_version?(timestamp)
+ raw_timestamp_to_cache_version(timestamp)
+ elsif timestamp = updated_at
+ timestamp.utc.to_s(cache_timestamp_format)
+ end
+ else
+ if self.class.has_attribute?("updated_at")
+ raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
+ end
end
end
@@ -151,5 +162,43 @@ module ActiveRecord
end
end
end
+
+ private
+ # Detects if the value before type cast
+ # can be used to generate a cache_version.
+ #
+ # The fast cache version only works with a
+ # string value directly from the database.
+ #
+ # We also must check if the timestamp format has been changed
+ # or if the timezone is not set to UTC then
+ # we cannot apply our transformations correctly.
+ def can_use_fast_cache_version?(timestamp)
+ timestamp.is_a?(String) &&
+ cache_timestamp_format == :usec &&
+ default_timezone == :utc &&
+ !updated_at_came_from_user?
+ end
+
+ # Converts a raw database string to `:usec`
+ # format.
+ #
+ # Example:
+ #
+ # timestamp = "2018-10-15 20:02:15.266505"
+ # raw_timestamp_to_cache_version(timestamp)
+ # # => "20181015200215266505"
+ #
+ # Postgres truncates trailing zeros,
+ # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
+ # to account for this we pad the output with zeros
+ def raw_timestamp_to_cache_version(timestamp)
+ key = timestamp.delete("- :.")
+ if key.length < 20
+ key.ljust(20, "0")
+ else
+ key
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index c84f3d0fbb..8c1b2e2be1 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -16,17 +16,17 @@ module ActiveRecord
delegate :pluck, :pick, :ids, to: :all
# Executes a custom SQL query against your database and returns all the results. The results will
- # be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # be returned as an array, with the requested columns encapsulated as attributes of the model you call
+ # this method from. For example, if you call <tt>Product.find_by_sql</tt>, then the results will be returned in
# a +Product+ object with the attributes you specified in the SQL query.
#
- # If you call a complicated SQL query which spans multiple tables the columns specified by the
+ # If you call a complicated SQL query which spans multiple tables, the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
- # no database agnostic conversions performed. This should be a last resort because using, for example,
- # MySQL specific terms will lock you to using that particular database engine or require you to
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is; there will be
+ # no database agnostic conversions performed. This should be a last resort because using
+ # database-specific terms will lock you into using that particular database engine, or require you to
# change your call if you switch engines.
#
# # A simple SQL query spanning multiple tables
@@ -40,7 +40,8 @@ module ActiveRecord
def find_by_sql(sql, binds = [], preparable: nil, &block)
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
column_types = result_set.column_types.dup
- columns_hash.each_key { |k| column_types.delete k }
+ cached_columns_hash = connection.schema_cache.columns_hash(table_name)
+ cached_columns_hash.each_key { |k| column_types.delete k }
message_bus = ActiveSupport::Notifications.instrumenter
payload = {
@@ -60,7 +61,9 @@ module ActiveRecord
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ # using the ActiveRecord::Calculations class methods. Look into those before using this method,
+ # as it could lock you into a specific database engine or require a code change to switch
+ # database engines.
#
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
# # => 12