Skip to content

Rails 6: Updates for bulk insert/upsert #795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ def default_insert_value(column)
end
private :default_insert_value

def build_insert_sql(insert) # :nodoc:
sql = +"INSERT #{insert.into}"

if returning = insert.send(:insert_all).returning
sql << " OUTPUT " << returning.map {|column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
end

sql << " #{insert.values_list}"
sql
end

# === SQLServer Specific ======================================== #

def execute_procedure(proc_name, *variables)
Expand Down Expand Up @@ -243,10 +254,12 @@ def sql_for_insert(sql, pk, binds)
table_name = query_requires_identity_insert?(sql)
pk = primary_key(table_name)
end

sql = if pk && use_output_inserted? && !database_prefix_remote_server?
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
table_name ||= get_table_name(sql)
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)

if exclude_output_inserted
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
<<~SQL.squish
Expand Down
16 changes: 16 additions & 0 deletions lib/active_record/connection_adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ def supports_in_memory_oltp?
@version_year >= 2014
end

def supports_insert_returning?
true
end

def supports_insert_on_duplicate_skip?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are already set to false by the abstract adapter but think it better to duplicate the methods here so that it's clear what the MSSQL adapter supports.

false
end

def supports_insert_on_duplicate_update?
false
end

def supports_insert_conflict_target?
false
end

def disable_referential_integrity
tables = tables_with_referential_integrity
tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
Expand Down
36 changes: 36 additions & 0 deletions test/cases/coerced_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1407,3 +1407,39 @@ class EnumTest < ActiveRecord::TestCase
Book.connection.add_index(:books, [:author_id, :name], unique: true)
end
end




require 'models/task'
class QueryCacheExpiryTest < ActiveRecord::TestCase

# SQL Server does not support skipping or upserting duplicates.
coerce_tests! :test_insert_all
def test_insert_all_coerced
assert_raises(ArgumentError, /does not support skipping duplicates/) do
Task.cache { Task.insert({ starting: Time.now }) }
end

assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
Task.cache { Task.insert_all!([{ starting: Time.now }]) }
end

assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
Task.cache { Task.insert!({ starting: Time.now }) }
end

assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
Task.cache { Task.insert_all!([{ starting: Time.now }]) }
end

assert_raises(ArgumentError, /does not support upsert/) do
Task.cache { Task.upsert({ starting: Time.now }) }
end

assert_raises(ArgumentError, /does not support upsert/) do
Task.cache { Task.upsert_all([{ starting: Time.now }]) }
end
end

end