Skip to content

Commit 534fc4f

Browse files
committed
Merge pull request rails#37627
Closes rails#37627
2 parents e807099 + 3a8668b commit 534fc4f

File tree

6 files changed

+59
-0
lines changed

6 files changed

+59
-0
lines changed

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ def build_insert_sql(insert) # :nodoc:
511511
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
512512
elsif insert.update_duplicates?
513513
sql << " ON DUPLICATE KEY UPDATE "
514+
sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
514515
sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
515516
end
516517

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ def build_insert_sql(insert) # :nodoc:
454454
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
455455
elsif insert.update_duplicates?
456456
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
457+
sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
457458
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
458459
end
459460

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ def build_insert_sql(insert) # :nodoc:
323323
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
324324
elsif insert.update_duplicates?
325325
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
326+
sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
326327
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
327328
end
328329

activerecord/lib/active_record/insert_all.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,21 @@ def updatable_columns
154154
quote_columns(insert_all.updatable_columns)
155155
end
156156

157+
def touch_model_timestamps_unless(&block)
158+
model.send(:timestamp_attributes_for_update_in_model).map do |column_name|
159+
if touch_timestamp_attribute?(column_name)
160+
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
161+
end
162+
end.compact.join
163+
end
164+
157165
private
158166
attr_reader :connection, :insert_all
159167

168+
def touch_timestamp_attribute?(column_name)
169+
update_duplicates? && !insert_all.updatable_columns.include?(column_name)
170+
end
171+
160172
def columns_list
161173
format_columns(insert_all.keys)
162174
end

activerecord/test/cases/insert_all_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,46 @@ def test_upsert_all_does_not_perform_an_upsert_if_a_partial_index_doesnt_apply
274274
assert_equal ["Out of the Silent Planet", "Perelandra"], Book.where(isbn: "1974522598").order(:name).pluck(:name)
275275
end
276276

277+
def test_upsert_all_does_not_touch_updated_at_when_values_do_not_change
278+
skip unless supports_insert_on_duplicate_update?
279+
280+
updated_at = Time.now.utc - 5.years
281+
Book.insert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1), updated_at: updated_at }]
282+
Book.upsert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1) }]
283+
284+
assert_in_delta updated_at, Book.find(101).updated_at, 1
285+
end
286+
287+
def test_upsert_all_touches_updated_at_and_updated_on_when_values_change
288+
skip unless supports_insert_on_duplicate_update?
289+
290+
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 }]
291+
Book.upsert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 8) }]
292+
293+
assert_equal Time.now.year, Book.find(101).updated_at.year
294+
assert_equal Time.now.year, Book.find(101).updated_on.year
295+
end
296+
297+
def test_upsert_all_uses_given_updated_at_over_implicit_updated_at
298+
skip unless supports_insert_on_duplicate_update?
299+
300+
updated_at = Time.now.utc - 1.year
301+
Book.insert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1), updated_at: 5.years.ago }]
302+
Book.upsert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 8), updated_at: updated_at }]
303+
304+
assert_in_delta updated_at, Book.find(101).updated_at, 1
305+
end
306+
307+
def test_upsert_all_uses_given_updated_on_over_implicit_updated_on
308+
skip unless supports_insert_on_duplicate_update?
309+
310+
updated_on = Time.now.utc.to_date - 30
311+
Book.insert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1), updated_on: 5.years.ago }]
312+
Book.upsert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 8), updated_on: updated_on }]
313+
314+
assert_equal updated_on, Book.find(101).updated_on
315+
end
316+
277317
def test_insert_all_raises_on_unknown_attribute
278318
assert_raise ActiveRecord::UnknownAttributeError do
279319
Book.insert_all! [{ unknown_attribute: "Test" }]

activerecord/test/schema/schema.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@
120120
t.boolean :boolean_status
121121
t.index [:author_id, :name], unique: true
122122
t.index :isbn, where: "published_on IS NOT NULL", unique: true
123+
124+
t.datetime :created_at
125+
t.datetime :updated_at
126+
t.date :updated_on
123127
end
124128

125129
create_table :booleans, force: true do |t|

0 commit comments

Comments
 (0)