Skip to content

Commit 28c38ea

Browse files
authored
Fix insertion of records to non-default schema table using raw SQL (#1053)
1 parent 98e57a6 commit 28c38ea

File tree

7 files changed

+38
-16
lines changed

7 files changed

+38
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Fixed
44

55
- [#1052](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1052) Ignore casing of VALUES clause when inserting records using SQL
6+
- [#1053](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1053) Fix insertion of records to non-default schema table using raw SQL
67

78
## v7.0.2.0
89

lib/active_record/connection_adapters/sqlserver/database_statements.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def query_requires_identity_insert?(sql)
402402
raw_table_name = get_raw_table_name(sql)
403403
id_column = identity_columns(raw_table_name).first
404404

405-
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? SQLServer::Utils.extract_identifiers(raw_table_name).object : false
405+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? SQLServer::Utils.extract_identifiers(raw_table_name).quoted : false
406406
end
407407

408408
def insert_sql?(sql)

lib/active_record/connection_adapters/sqlserver/utils.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Utils
99
QUOTED_STRING_PREFIX = "N"
1010

1111
# Value object to return identifiers from SQL Server names http://bit.ly/1CZ3EiL
12-
# Inspiried from Rails PostgreSQL::Name adapter object in their own Utils.
12+
# Inspired from Rails PostgreSQL::Name adapter object in their own Utils.
1313
#
1414
class Name
1515
SEPARATOR = "."

test/cases/adapter_test_sqlserver.rb

+32-8
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,31 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
193193
@identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
194194
@identity_insert_sql_unordered = "INSERT INTO [funny_jokes] ([name],[id]) VALUES('Knock knock',420)"
195195
@identity_insert_sql_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
196-
@identity_insert_sql_unquoted_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
196+
@identity_insert_sql_unquoted_sp = "EXEC sp_executesql N'INSERT INTO funny_jokes (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
197197
@identity_insert_sql_unordered_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Knock knock', @1 = 420"
198+
199+
@identity_insert_sql_non_dbo = "INSERT INTO [test].[aliens] ([id],[name]) VALUES(420,'Mork')"
200+
@identity_insert_sql_non_dbo_unquoted = "INSERT INTO test.aliens ([id],[name]) VALUES(420,'Mork')"
201+
@identity_insert_sql_non_dbo_unordered = "INSERT INTO [test].[aliens] ([name],[id]) VALUES('Mork',420)"
202+
@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'"
203+
@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'"
204+
@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"
198205
end
199206

200-
it "return unquoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
201-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
202-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
203-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
204-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
205-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
206-
assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
207+
it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
208+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
209+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
210+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
211+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
212+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
213+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
214+
215+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo)
216+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted)
217+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered)
218+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_sp)
219+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted_sp)
220+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered_sp)
207221
end
208222

209223
it "return false to #query_requires_identity_insert? for normal SQL" do
@@ -533,6 +547,16 @@ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
533547
end
534548
end
535549

550+
describe 'table is in non-dbo schema' do
551+
it "records can be created successfully" do
552+
Alien.create!(name: 'Trisolarans')
553+
end
554+
555+
it 'records can be inserted using SQL' do
556+
Alien.connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')")
557+
end
558+
end
559+
536560
describe "exec_insert" do
537561
it 'values clause should be case-insensitive' do
538562
assert_difference("Post.count", 4) do

test/cases/schema_test_sqlserver.rb

-4
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,5 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
4747
assert_equal 255, columns.find { |c| c.name == "n_name" }.limit
4848
assert_equal 1000, columns.find { |c| c.name == "n_description" }.limit
4949
end
50-
51-
it "creates new record" do
52-
Alien.create!(name: 'Trisolarans')
53-
end
5450
end
5551
end

test/cases/utils_test_sqlserver.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class UtilsTestSQLServer < ActiveRecord::TestCase
7777
_(extract_identifiers(" ").object).must_be_nil
7878
end
7979

80-
it "has a #quoted that returns a fully quoted name with all identifiers as orginially passed in" do
80+
it "has a #quoted that returns a fully quoted name with all identifiers as originally passed in" do
8181
_(extract_identifiers("object").quoted).must_equal "[object]"
8282
_(extract_identifiers("server.database..object").quoted).must_equal "[server].[database]..[object]"
8383
_(extract_identifiers("[server]...[object]").quoted).must_equal "[server]...[object]"
@@ -92,7 +92,7 @@ class UtilsTestSQLServer < ActiveRecord::TestCase
9292
_(extract_identifiers("[obj.name].[foo]").quoted).must_equal "[obj.name].[foo]"
9393
end
9494

95-
it "should indicate if a name is fully qualitified" do
95+
it "should indicate if a name is fully qualified" do
9696
_(extract_identifiers("object").fully_qualified?).must_equal false
9797
_(extract_identifiers("schema.object").fully_qualified?).must_equal false
9898
_(extract_identifiers("database.schema.object").fully_qualified?).must_equal false

test/schema/sqlserver_specific_schema.rb

+1
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@
316316
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'aliens' and TABLE_SCHEMA = 'test') DROP TABLE test.aliens"
317317
execute <<-TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL
318318
CREATE TABLE test.aliens(
319+
id int IDENTITY NOT NULL primary key,
319320
name varchar(255)
320321
)
321322
TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL

0 commit comments

Comments
 (0)