diff --git a/CHANGELOG.md b/CHANGELOG.md index bcddc4efb..3b0050259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ #### Added -- [#1301](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1301) Add support for INDEX INCLUDE. -- [#1312](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1312) Add support for `insert_all` and `upsert_all` -- [#1317](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1317) Reverse order of values when upserting +- [#1301](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1301) Add support for `INDEX INCLUDE`. +- [#1312](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1312) Add support for `insert_all` and `upsert_all`. +- [#1317](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1317) Reverse order of values when upserting. #### Changed @@ -13,6 +13,7 @@ #### 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 +- [#1320](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1320) Fix SQL statement to calculate `updated_at` when upserting. +- [#1327](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1327) Fix multiple `nil` identity columns for merge insert. 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 30296d1a9..bac9e5441 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -177,7 +177,7 @@ def build_sql_for_merge_insert(insert:, insert_all:, columns_with_uniqueness_con SELECT * FROM ( SELECT #{insert.send(:columns_list)}, #{partition_by_columns_with_uniqueness_constraints(columns_with_uniqueness_constraints:)} - FROM (#{insert.values_list}) + FROM (#{merge_insert_values_list(insert:, insert_all:)}) AS t1 (#{insert.send(:columns_list)}) ) AS ranked_source WHERE #{is_first_record_across_all_uniqueness_constraints(columns_with_uniqueness_constraints:)} @@ -206,6 +206,35 @@ def build_sql_for_merge_insert(insert:, insert_all:, columns_with_uniqueness_con sql end + # For `nil` identity columns we need to ensure that the values do not match so that they are all inserted. + # Method is a combination of `ActiveRecord::InsertAll#values_list` and `ActiveRecord::ConnectionAdapters::SQLServer::DatabaseStatements#default_insert_value`. + def merge_insert_values_list(insert:, insert_all:) + connection = insert.send(:connection) + identity_index = 0 + + types = insert.send(:extract_types_from_columns_on, insert.model.table_name, keys: insert.keys_including_timestamps) + + values_list = insert_all.map_key_with_value do |key, value| + if Arel::Nodes::SqlLiteral === value + value + elsif insert.primary_keys.include?(key) && value.nil? + column = insert.send(:column_from_key, key) + + if column.is_identity? + identity_index += 1 + table_name = quote(quote_table_name(column.table_name)) + Arel.sql("IDENT_CURRENT(#{table_name}) + (IDENT_INCR(#{table_name}) * #{identity_index})") + else + connection.default_insert_value(column) + end + else + ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value)) + end + end + + connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list)) + end + # === SQLServer Specific ======================================== # def execute_procedure(proc_name, *variables)