diff --git a/CHANGELOG.md b/CHANGELOG.md index 2226fab10..1c7cd4b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ #### Added -- [#1201](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1201) Support non-dbo schemas in schema dumper. +- [#1201](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1201) Support non-dbo schemas in schema dumper +- [#1206](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1206) Support table names containing spaces ## v7.1.4 diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index afcc0874b..636bcfc7c 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -673,12 +673,18 @@ def get_table_name(sql) # Parses the raw table name that is used in the SQL. Table name could include database/schema/etc. def get_raw_table_name(sql) - case sql - when /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i - Regexp.last_match[3] || Regexp.last_match[4] - when /FROM\s+([^\(\s]+)\s*/i - Regexp.last_match[1] - end + s = sql.gsub(/^\s*EXEC sp_executesql N'/i, "") + + if s.match?(/^\s*INSERT INTO.*/i) + s.split(/INSERT INTO/i)[1] + .split(/OUTPUT INSERTED/i)[0] + .split(/(DEFAULT)?\s+VALUES/i)[0] + .match(/\s*([^(]*)/i)[0] + elsif s.match?(/^\s*UPDATE\s+.*/i) + s.match(/UPDATE\s+([^\(\s]+)\s*/i)[1] + else + s.match(/FROM\s+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1] + end.strip end def default_constraint_name(table_name, column_name) diff --git a/test/cases/adapter_test_sqlserver.rb b/test/cases/adapter_test_sqlserver.rb index 34394732b..166920e4f 100644 --- a/test/cases/adapter_test_sqlserver.rb +++ b/test/cases/adapter_test_sqlserver.rb @@ -550,11 +550,23 @@ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes describe 'table is in non-dbo schema' do it "records can be created successfully" do - Alien.create!(name: 'Trisolarans') + assert_difference("Alien.count", 1) do + Alien.create!(name: 'Trisolarans') + end end it 'records can be inserted using SQL' do - Alien.connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')") + assert_difference("Alien.count", 2) do + Alien.connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')") + end + end + end + + describe 'table names contains spaces' do + it 'records can be created successfully' do + assert_difference("TableWithSpaces.count", 1) do + TableWithSpaces.create!(name: 'Bob') + end end end diff --git a/test/cases/schema_test_sqlserver.rb b/test/cases/schema_test_sqlserver.rb index fc119fe27..1751af622 100644 --- a/test/cases/schema_test_sqlserver.rb +++ b/test/cases/schema_test_sqlserver.rb @@ -39,7 +39,7 @@ class SchemaTestSQLServer < ActiveRecord::TestCase assert_equal 1, columns.select { |c| c.is_identity? }.size end - it "return correct varchar and nvarchar column limit length when table is in non dbo schema" do + it "return correct varchar and nvarchar column limit length when table is in non-dbo schema" do columns = connection.columns("test.sst_schema_columns") assert_equal 255, columns.find { |c| c.name == "name" }.limit @@ -48,4 +48,50 @@ class SchemaTestSQLServer < ActiveRecord::TestCase assert_equal 1000, columns.find { |c| c.name == "n_description" }.limit end end + + describe "parsing table name from raw SQL" do + describe 'SELECT statements' do + it do + assert_equal "[sst_schema_columns]", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM [sst_schema_columns]") + end + + it do + assert_equal "sst_schema_columns", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM sst_schema_columns") + end + + it do + assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES]") + end + + it do + assert_equal "[WITH - SPACES$DOLLAR]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES$DOLLAR]") + end + end + + describe 'INSERT statements' do + it do + assert_equal "[dashboards]", connection.send(:get_raw_table_name, "INSERT INTO [dashboards] DEFAULT VALUES; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident") + end + + it do + assert_equal "lock_without_defaults", connection.send(:get_raw_table_name, "INSERT INTO lock_without_defaults(title) VALUES('title1')") + end + + it do + assert_equal "json_data_type", connection.send(:get_raw_table_name, "insert into json_data_type (payload) VALUES ('null')") + end + + it do + assert_equal "[auto_increments]", connection.send(:get_raw_table_name, "INSERT INTO [auto_increments] OUTPUT INSERTED.[id] DEFAULT VALUES") + end + + it do + assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [WITH - SPACES] ([external_id]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 bigint', @0 = 10") + end + + it do + assert_equal "[test].[aliens]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 varchar(255)', @0 = 'Trisolarans'") + end + end + end end diff --git a/test/models/sqlserver/table_with_spaces.rb b/test/models/sqlserver/table_with_spaces.rb new file mode 100644 index 000000000..d5f07ec4a --- /dev/null +++ b/test/models/sqlserver/table_with_spaces.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class TableWithSpaces < ActiveRecord::Base + self.table_name = "A Table With Spaces" +end diff --git a/test/schema/sqlserver_specific_schema.rb b/test/schema/sqlserver_specific_schema.rb index 1bef7d0e1..b11cd6fa9 100644 --- a/test/schema/sqlserver_specific_schema.rb +++ b/test/schema/sqlserver_specific_schema.rb @@ -151,6 +151,10 @@ SELECT GETUTCDATE() utcdate SQL + create_table 'A Table With Spaces', force: true do |t| + t.string :name + end + # Constraints create_table(:sst_has_fks, force: true) do |t|