Skip to content

Commit 09db3c1

Browse files
committed
Support cross database inserts (#1357)
1 parent 0adea5b commit 09db3c1

File tree

5 files changed

+47
-18
lines changed

5 files changed

+47
-18
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ jobs:
1212
test:
1313
name: Run test suite
1414
runs-on: ubuntu-latest
15+
timeout-minutes: 10
1516

1617
env:
1718
COMPOSE_FILE: compose.ci.yaml

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
- [#1327](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1327) Fix multiple `nil` identity columns for merge insert.
1919
- [#1338](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1338) Fix `insert_all`/`upsert_all` for table names containing numbers.
2020
- [#1345](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1345) Maintain index options during `change_column` operations.
21+
- [#1357](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1357) Support cross database inserts.
2122

2223
Please check [8-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/8-0-stable/CHANGELOG.md) for previous changes.

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ def column_type(ci:)
593593
end
594594

595595
def column_definitions_sql(database, identifier)
596+
database = "TEMPDB" if identifier.temporary_table?
596597
schema_name = "schema_name()"
597598

598599
if prepared_statements
@@ -603,12 +604,8 @@ def column_definitions_sql(database, identifier)
603604
schema_name = quote(identifier.schema) if identifier.schema.present?
604605
end
605606

606-
object_id_arg = identifier.schema.present? ? "CONCAT(#{schema_name},'.',#{object_name})" : object_name
607-
608-
if identifier.temporary_table?
609-
database = "TEMPDB"
610-
object_id_arg = "CONCAT('#{database}','..',#{object_name})"
611-
end
607+
object_id_arg = identifier.schema.present? ? "CONCAT('.',#{schema_name},'.',#{object_name})" : "CONCAT('..',#{object_name})"
608+
object_id_arg = "CONCAT('#{database}',#{object_id_arg})"
612609

613610
%{
614611
SELECT

test/cases/adapter_test_sqlserver.rb

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77
require "models/subscriber"
88
require "models/minimalistic"
99
require "models/college"
10+
require "models/dog"
11+
require "models/other_dog"
1012
require "models/discount"
1113

1214
class AdapterTestSQLServer < ActiveRecord::TestCase
1315
fixtures :tasks
1416

17+
let(:arunit_connection) { Topic.lease_connection }
18+
let(:arunit2_connection) { College.lease_connection }
19+
let(:arunit_database) { arunit_connection.pool.db_config.database }
20+
let(:arunit2_database) { arunit2_connection.pool.db_config.database }
21+
1522
let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
1623
let(:basic_merge_sql) { "MERGE INTO [ships] WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT [id], [name], ROW_NUMBER() OVER ( PARTITION BY [id] ORDER BY [id] DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 ([id], [name]) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.[id] = source.[id]) WHEN MATCHED THEN UPDATE SET target.[name] = source.[name]" }
1724
let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
@@ -51,8 +58,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
5158
assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
5259

5360
# Test when database and owner included in table name.
54-
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
55-
Topic.table_name = "#{db_config.database}.dbo.topics"
61+
Topic.table_name = "#{arunit_database}.dbo.topics"
5662
assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
5763
ensure
5864
Topic.table_name = "topics"
@@ -110,13 +116,13 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
110116
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
111117
configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
112118
assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
113-
"expected database #{configuration[:database]} to not exist"
119+
"expected database #{configuration[:database]} to not exist"
114120
end
115121

116122
it "test database exists returns true when the database exists" do
117123
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
118124
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
119-
"expected database #{db_config.database} to exist"
125+
"expected database #{db_config.database} to exist"
120126
end
121127

122128
it "test primary key violation" do
@@ -231,6 +237,9 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
231237
@identity_insert_sql_non_dbo_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
232238
@identity_insert_sql_non_dbo_unquoted_sp = "EXEC sp_executesql N'INSERT INTO test.aliens (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
233239
@identity_insert_sql_non_dbo_unordered_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Mork', @1 = 420"
240+
241+
@non_identity_insert_sql_cross_database = "INSERT INTO #{arunit2_database}.dbo.dogs SELECT * FROM #{arunit_database}.dbo.dogs"
242+
@identity_insert_sql_cross_database = "INSERT INTO #{arunit2_database}.dbo.dogs(id) SELECT id FROM #{arunit_database}.dbo.dogs"
234243
end
235244

236245
it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
@@ -251,20 +260,32 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
251260
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_sp)
252261
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted_sp)
253262
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered_sp)
263+
264+
assert_equal "[#{arunit2_database}].[dbo].[dogs]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_cross_database)
254265
end
255266

256267
it "return false to #query_requires_identity_insert? for normal SQL" do
257-
[basic_insert_sql, basic_merge_sql, basic_update_sql, basic_select_sql].each do |sql|
268+
[basic_insert_sql, basic_merge_sql, basic_update_sql, basic_select_sql, @non_identity_insert_sql_cross_database].each do |sql|
258269
assert !connection.send(:query_requires_identity_insert?, sql), "SQL was #{sql}"
259270
end
260271
end
261272

262-
it "find identity column using #identity_columns" do
273+
it "find identity column" do
263274
task_id_column = Task.columns_hash["id"]
264275
assert_equal task_id_column.name, connection.send(:identity_columns, Task.table_name).first.name
265276
assert_equal task_id_column.sql_type, connection.send(:identity_columns, Task.table_name).first.sql_type
266277
end
267278

279+
it "find identity column cross database" do
280+
id_column = Dog.columns_hash["id"]
281+
assert_equal id_column.name, arunit2_connection.send(:identity_columns, Dog.table_name).first.name
282+
assert_equal id_column.sql_type, arunit2_connection.send(:identity_columns, Dog.table_name).first.sql_type
283+
284+
id_column = OtherDog.columns_hash["id"]
285+
assert_equal id_column.name, arunit_connection.send(:identity_columns, OtherDog.table_name).first.name
286+
assert_equal id_column.sql_type, arunit_connection.send(:identity_columns, OtherDog.table_name).first.sql_type
287+
end
288+
268289
it "return an empty array when calling #identity_columns for a table_name with no identity" do
269290
_(connection.send(:identity_columns, Subscriber.table_name)).must_equal []
270291
end
@@ -455,8 +476,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
455476
assert_equal columns.size, SSTestCustomersView.columns.size
456477
columns.each do |colname|
457478
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
458-
SSTestCustomersView.columns_hash[colname],
459-
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
479+
SSTestCustomersView.columns_hash[colname],
480+
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
460481
end
461482
end
462483

@@ -482,8 +503,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
482503
assert_equal columns.size, SSTestStringDefaultsView.columns.size
483504
columns.each do |colname|
484505
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
485-
SSTestStringDefaultsView.columns_hash[colname],
486-
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
506+
SSTestStringDefaultsView.columns_hash[colname],
507+
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
487508
end
488509
end
489510

@@ -495,7 +516,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
495516

496517
it "find default values" do
497518
assert_equal "null", SSTestStringDefaultsView.new.pretend_null,
498-
SSTestStringDefaultsView.columns_hash["pretend_null"].inspect
519+
SSTestStringDefaultsView.columns_hash["pretend_null"].inspect
499520
end
500521

501522
it "respond true to data_source_exists?" do
@@ -510,7 +531,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
510531

511532
it "using alternate view definition still be able to find real default" do
512533
assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
513-
SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
534+
SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
514535
end
515536
end
516537

test/cases/temp_test_sqlserver.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper_sqlserver"
4+
5+
class TempTestSQLServer < ActiveRecord::TestCase
6+
# it "assert true" do
7+
# assert true
8+
# end
9+
end

0 commit comments

Comments
 (0)