From 45bf0efdbba86f81de847497ed26ac7669fd432e Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Sun, 23 Mar 2025 11:56:02 +0100 Subject: [PATCH] Fix upserting with mixed data types The previous `CASE` statement used `COALESCE` with `'NULL'` as a fallback value if either `source` or `target` were `NULL`. However, `'NULL'` as written in this statement, is a `varchar` and therefore does not work when comparing with other datatypes. This commit changes the switch statement to check if either both values are equal, or both values are actually `NULL`. --- CHANGELOG.md | 1 + .../sqlserver/database_statements.rb | 2 +- test/cases/insert_all_test_sqlserver.rb | 20 +++++++++++++++++++ test/models/sqlserver/recurring_task.rb | 3 +++ test/schema/sqlserver_specific_schema.rb | 8 ++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/cases/insert_all_test_sqlserver.rb create mode 100644 test/models/sqlserver/recurring_task.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index fabc1048a..bcddc4efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,5 +13,6 @@ #### Fixed - [#1313](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1313) Correctly retrieve the SQL Server database version. +- [#1320](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1320) Fix SQL statement to calculate `updated_at` when upserting Please check [8-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/8-0-stable/CHANGELOG.md) for previous changes. diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index addc593d3..30296d1a9 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -592,7 +592,7 @@ def joining_on_columns_with_uniqueness_constraints(columns_with_uniqueness_const def build_sql_for_recording_timestamps_when_updating(insert:) insert.model.timestamp_attributes_for_update_in_model.filter_map do |column_name| if insert.send(:touch_timestamp_attribute?, column_name) - "target.#{quote_column_name(column_name)}=CASE WHEN (#{insert.updatable_columns.map { |column| "(COALESCE(target.#{quote_column_name(column)}, 'NULL') = COALESCE(source.#{quote_column_name(column)}, 'NULL'))" }.join(" AND ")}) THEN target.#{quote_column_name(column_name)} ELSE #{high_precision_current_timestamp} END," + "target.#{quote_column_name(column_name)}=CASE WHEN (#{insert.updatable_columns.map { |column| "(source.#{quote_column_name(column)} = target.#{quote_column_name(column)} OR (source.#{quote_column_name(column)} IS NULL AND target.#{quote_column_name(column)} IS NULL))" }.join(" AND ")}) THEN target.#{quote_column_name(column_name)} ELSE #{high_precision_current_timestamp} END," end end.join end diff --git a/test/cases/insert_all_test_sqlserver.rb b/test/cases/insert_all_test_sqlserver.rb new file mode 100644 index 000000000..ee17d5500 --- /dev/null +++ b/test/cases/insert_all_test_sqlserver.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "cases/helper_sqlserver" +require "models/sqlserver/recurring_task" + +class InsertAllTestSQLServer < ActiveRecord::TestCase + test "upsert_all recording of timestamps works with mixed datatypes" do + task = RecurringTask.create!( + key: "abcdef", + priority: 5 + ) + + RecurringTask.upsert_all([{ + id: task.id, + priority: nil + }]) + + assert_not_equal task.updated_at, RecurringTask.find(task.id).updated_at + end +end diff --git a/test/models/sqlserver/recurring_task.rb b/test/models/sqlserver/recurring_task.rb new file mode 100644 index 000000000..aecf7c462 --- /dev/null +++ b/test/models/sqlserver/recurring_task.rb @@ -0,0 +1,3 @@ +class RecurringTask < ActiveRecord::Base + self.table_name = "recurring_tasks" +end diff --git a/test/schema/sqlserver_specific_schema.rb b/test/schema/sqlserver_specific_schema.rb index 367ae2bf8..68770d59a 100644 --- a/test/schema/sqlserver_specific_schema.rb +++ b/test/schema/sqlserver_specific_schema.rb @@ -372,4 +372,12 @@ name varchar(255) ) TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL + + create_table "recurring_tasks", force: true do |t| + t.string :key + t.integer :priority, default: 0 + + t.datetime2 :created_at + t.datetime2 :updated_at + end end