Merge pull request #42993 from sambostock/use-precision-in-updated-at-upsert

Use correct precision when touching `updated_at` column in upsert
This commit is contained in:
Aaron Patterson 2021-09-14 12:21:13 -07:00 committed by GitHub
commit 0b5c7210af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 3 deletions

View File

@ -1,3 +1,15 @@
* Use full precision for `updated_at` in `insert_all`/`upsert_all`
`CURRENT_TIMESTAMP` provides differing precision depending on the database,
and not all databases support explicitly specifying additional precision.
Instead, we delegate to the new `connection.high_precision_current_timestamp`
for the SQL to produce a high precision timestamp on the current database.
Fixes #42992
*Sam Bostock*
* Add ssl support for postgresql database tasks
Add `PGSSLMODE`, `PGSSLCERT`, `PGSSLKEY` and `PGSSLROOTCERT` to pg_env from database config

View File

@ -441,6 +441,19 @@ module ActiveRecord
end
end
# This is a safe default, even if not high precision on all databases
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
# Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
# arbitrary precision date/time columns.
#
# Adapters supporting datetime with precision should override this to
# provide as much precision as is available.
def high_precision_current_timestamp
HIGH_PRECISION_CURRENT_TIMESTAMP
end
private
def execute_batch(statements, name = nil)
statements.each do |statement|

View File

@ -76,6 +76,15 @@ module ActiveRecord
end
alias :exec_update :exec_delete
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
def high_precision_current_timestamp
HIGH_PRECISION_CURRENT_TIMESTAMP
end
private
def raw_execute(sql, name, async: false)
# make sure we carry over any changes to ActiveRecord.default_timezone that have been

View File

@ -123,6 +123,14 @@ module ActiveRecord
execute("ROLLBACK", "TRANSACTION")
end
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
def high_precision_current_timestamp
HIGH_PRECISION_CURRENT_TIMESTAMP
end
private
def execute_batch(statements, name = nil)
execute(combine_multi_statements(statements))

View File

@ -97,6 +97,15 @@ module ActiveRecord
reset_read_uncommitted
end
# https://stackoverflow.com/questions/17574784
# https://www.sqlite.org/lang_datefunc.html
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')").freeze # :nodoc:
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
def high_precision_current_timestamp
HIGH_PRECISION_CURRENT_TIMESTAMP
end
private
def reset_read_uncommitted
read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")

View File

@ -198,7 +198,7 @@ module ActiveRecord
def touch_model_timestamps_unless(&block)
model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
if touch_timestamp_attribute?(column_name)
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
end
end.join
end

View File

@ -364,6 +364,15 @@ class InsertAllTest < ActiveRecord::TestCase
assert_equal Time.now.year, Book.find(101).updated_on.year
end
def test_upsert_all_respects_updated_at_precision_when_touched_implicitly
skip unless supports_insert_on_duplicate_update? && supports_datetime_with_precision?
Book.insert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1), updated_at: 5.years.ago, updated_on: 5.years.ago }]
Book.upsert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 8) }]
assert_not_predicate Book.find(101).updated_at.usec, :zero?, "updated_at should have sub-second precision"
end
def test_upsert_all_uses_given_updated_at_over_implicit_updated_at
skip unless supports_insert_on_duplicate_update?

View File

@ -119,8 +119,13 @@ ActiveRecord::Schema.define do
t.index :isbn, where: "published_on IS NOT NULL", unique: true
t.index "(lower(external_id))", unique: true if supports_expression_index?
t.datetime :created_at
t.datetime :updated_at
if supports_datetime_with_precision?
t.datetime :created_at, precision: 6
t.datetime :updated_at, precision: 6
else
t.datetime :created_at
t.datetime :updated_at
end
t.date :updated_on
end