Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

#### Changed

- [#1405](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1405) Fix `insert_all`/`upsert_all` for single character, temporary, non-ASCII and three part table names, and stop an aliased target being included in the `MERGE` table name.

## v8.1.3

#### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module ActiveRecord
module ConnectionAdapters
module SQLServer
module SchemaStatements
MERGE_TARGET_IDENTIFIER = /(?:\[[^\]]+\]|[a-z0-9_-]+)/i # :nodoc:
MERGE_TARGET_TABLE_NAME = /\A\s*MERGE\s+INTO\s+(#{MERGE_TARGET_IDENTIFIER}(?:\s*\.#{MERGE_TARGET_IDENTIFIER}){0,2})\s+(?:AS|WITH|USING)/i # :nodoc:

def create_table(table_name, **options)
res = super
clear_cache!
Expand Down Expand Up @@ -726,7 +729,7 @@ def get_raw_table_name(sql)
elsif s.match?(/^\s*UPDATE\s+.*/i)
s.match(/UPDATE\s+([^(\s]+)\s*/i)[1]
elsif s.match?(/^\s*MERGE INTO.*/i)
s.match(/^\s*MERGE\s+INTO\s+(\[?[a-z0-9_ -]+\]?\.?\[?[a-z0-9_ -]+\]?)\s+(AS|WITH|USING)/i)[1]
s.match(MERGE_TARGET_TABLE_NAME)[1]
else
s.match(/FROM[\s|(]+((\[[^(\]]+\])|[^(\s]+)\s*/i)[1]
end.strip
Expand Down
2 changes: 2 additions & 0 deletions test/cases/adapter_test_sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
@identity_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] WHEN NOT MATCHED BY TARGET THEN INSERT ([id], [name]) VALUES (source.[id], source.[name]) OUTPUT INSERTED.[id]"
@identity_merge_sql_unquoted = "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 WHEN NOT MATCHED BY TARGET THEN INSERT (id, name) VALUES (source.id, source.name) OUTPUT INSERTED.id"
@identity_merge_sql_unordered = "MERGE INTO [ships] WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT [name], [id], ROW_NUMBER() OVER ( PARTITION BY [id] ORDER BY [id] DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 ([name], [id]) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.[id] = source.[id]) WHEN MATCHED THEN UPDATE SET target.[name] = source.[name] WHEN NOT MATCHED BY TARGET THEN INSERT ([name], [id]) VALUES (source.[name], source.[id]) OUTPUT INSERTED.[id]"
@identity_merge_sql_aliased_target = @identity_merge_sql.sub(" WITH (UPDLOCK, HOLDLOCK)", "")

@identity_insert_sql_non_dbo = "INSERT INTO [test].[aliens] ([id],[name]) VALUES(420,'Mork')"
@identity_insert_sql_non_dbo_unquoted = "INSERT INTO test.aliens ([id],[name]) VALUES(420,'Mork')"
Expand All @@ -253,6 +254,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql)
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_unquoted)
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_unordered)
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_aliased_target)

assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo)
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted)
Expand Down
32 changes: 32 additions & 0 deletions test/cases/schema_test_sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
it do
assert_equal "[with_numbers_1234]", connection.send(:get_raw_table_name, "MERGE INTO [with_numbers_1234] AS target")
end

it do
assert_equal "[dashboards]", connection.send(:get_raw_table_name, "MERGE INTO [dashboards] AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "lock_without_defaults", connection.send(:get_raw_table_name, "MERGE INTO lock_without_defaults AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "MERGE INTO [WITH - SPACES] AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "MERGE INTO [with].[select notation] AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "[x]", connection.send(:get_raw_table_name, "MERGE INTO [x] WITH (UPDLOCK, HOLDLOCK) AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "MERGE INTO [with].[select notation] WITH (UPDLOCK, HOLDLOCK) AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "[a].[dbo].[dashboards]", connection.send(:get_raw_table_name, "MERGE INTO [a].[dbo].[dashboards] WITH (UPDLOCK, HOLDLOCK) AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end

it do
assert_equal "with .dashboards", connection.send(:get_raw_table_name, "MERGE INTO with .dashboards WITH (UPDLOCK, HOLDLOCK) AS target USING (VALUES (1)) AS source ([id]) ON (target.[id] = source.[id]) WHEN NOT MATCHED THEN INSERT ([id]) VALUES (source.[id]);")
end
end

describe "CREATE VIEW statements" do
Expand Down
Loading