From 5b61f948975b11ebdae3bee898b5be7e405351fe Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 5 Jul 2024 13:59:51 +0100 Subject: [PATCH 1/7] Support non-dbo schemas in dumper --- .../sqlserver/schema_dumper.rb | 11 ++++++++ .../sqlserver/schema_statements.rb | 27 ++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb b/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb index 9da6eef6f..cc14f25cf 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb @@ -39,6 +39,17 @@ def schema_collation(column) def default_primary_key?(column) super && column.is_identity? end + + def schemas(stream) + schema_names = @connection.schema_names - ["guest"] + + if schema_names.any? + schema_names.sort.each do |name| + stream.puts " create_schema #{name.inspect}" + end + stream.puts + end + end end end end diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 1b968420d..120cf2a0d 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -393,19 +393,33 @@ def drop_schema(schema_name) execute "DROP SCHEMA [#{schema_name}]" end + # Returns an array of schema names. + def schema_names + sql = <<~SQL.squish + SELECT name + FROM sys.schemas + WHERE + name NOT LIKE 'db_%' AND + name NOT IN ('INFORMATION_SCHEMA', 'sys') + SQL + + query_values(sql, "SCHEMA") + end + private def data_source_sql(name = nil, type: nil) - scope = quoted_scope name, type: type + scope = quoted_scope(name, type: type) - table_name = lowercase_schema_reflection_sql 'TABLE_NAME' - database = scope[:database].present? ? "#{scope[:database]}." : "" + table_schema = lowercase_schema_reflection_sql('TABLE_SCHEMA') + table_name = lowercase_schema_reflection_sql('TABLE_NAME') + database = scope[:database].present? ? "#{scope[:database]}." : "" table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()" - sql = "SELECT #{table_name}" + sql = "SELECT CONCAT(#{table_schema}, '.', #{table_name})" sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)" sql += " WHERE TABLE_CATALOG = #{table_catalog}" - sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}" + sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}" if scope[:schema] sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name] sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type] sql += " ORDER BY #{table_name}" @@ -414,9 +428,10 @@ def data_source_sql(name = nil, type: nil) def quoted_scope(name = nil, type: nil) identifier = SQLServer::Utils.extract_identifiers(name) + {}.tap do |scope| scope[:database] = identifier.database if identifier.database - scope[:schema] = identifier.schema || "dbo" + scope[:schema] = identifier.schema || "dbo" if name.present? scope[:name] = identifier.object if identifier.object scope[:type] = type if type end From 1de6669ef54f047bbb31d2bc91782f5f9719cb6c Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 10 Jul 2024 15:32:38 +0100 Subject: [PATCH 2/7] Update schema_statements.rb --- .../connection_adapters/sqlserver/schema_statements.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 120cf2a0d..7cbb7a436 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -416,7 +416,11 @@ def data_source_sql(name = nil, type: nil) database = scope[:database].present? ? "#{scope[:database]}." : "" table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()" - sql = "SELECT CONCAT(#{table_schema}, '.', #{table_name})" + sql = "SELECT " + sql += " CASE" + sql += " WHEN #{table_schema} = 'dbo' THEN #{table_name}" + sql += " ELSE CONCAT(#{table_schema}, '.', #{table_name})" + sql += " END" sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)" sql += " WHERE TABLE_CATALOG = #{table_catalog}" sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}" if scope[:schema] From ff447ca3e81b68929f1bbd9b233f6b5efabd29b8 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 15 Jul 2024 16:44:07 +0100 Subject: [PATCH 3/7] Guest schema should not be excluded --- .../connection_adapters/sqlserver/schema_dumper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb b/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb index cc14f25cf..d5bb1b348 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb @@ -41,7 +41,7 @@ def default_primary_key?(column) end def schemas(stream) - schema_names = @connection.schema_names - ["guest"] + schema_names = @connection.schema_names if schema_names.any? schema_names.sort.each do |name| From 216661f490212f125d1d98b19846b751f5f0b329 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 15 Jul 2024 16:44:14 +0100 Subject: [PATCH 4/7] Added test --- test/cases/schema_dumper_test_sqlserver.rb | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index 97e48d557..a680bda4c 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "cases/helper_sqlserver" +require "stringio" class SchemaDumperTestSQLServer < ActiveRecord::TestCase before { all_tables } @@ -141,7 +142,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase it "honor nonstandard primary keys" do generate_schema_for_table("movies") do |output| match = output.match(%r{create_table "movies"(.*)do}) - assert_not_nil(match, "nonstandardpk table not found") + assert_not_nil(match, "non-standard primary key table not found") assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved" end end @@ -159,15 +160,30 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase _(output.scan('t.integer "unique_field"').length).must_equal(1) end + it "schemas are dumped and tables names include non-default schema" do + stream = StringIO.new + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) + generated_schema = stream.string + + assert_not_includes generated_schema, 'create_schema "dbo"' + assert_includes generated_schema, 'create_schema "test"' + assert_includes generated_schema, 'create_schema "test2"' + + # Only non-default schemas should be included in table names. Default schema is 'dbo'. + assert_includes generated_schema, 'create_table "accounts"' + assert_includes generated_schema, 'create_table "test.aliens"' + assert_includes generated_schema, 'create_table "test2.sst_schema_test_mulitple_schema"' + end + private def generate_schema_for_table(*table_names) - require "stringio" + previous_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names stream = StringIO.new - ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) - + @generated_schema = stream.string yield @generated_schema if block_given? @schema_lines = Hash.new @@ -178,6 +194,8 @@ def generate_schema_for_table(*table_names) @schema_lines[Regexp.last_match[1]] = SchemaLine.new(line) end @generated_schema + ensure + ActiveRecord::SchemaDumper.ignore_tables = previous_ignore_tables end def line(column_name) From a4f478429bd77718b02a6640b375f416898d2333 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 15 Jul 2024 16:45:23 +0100 Subject: [PATCH 5/7] Update schema_dumper_test_sqlserver.rb --- test/cases/schema_dumper_test_sqlserver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index a680bda4c..49320e853 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -172,7 +172,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase # Only non-default schemas should be included in table names. Default schema is 'dbo'. assert_includes generated_schema, 'create_table "accounts"' assert_includes generated_schema, 'create_table "test.aliens"' - assert_includes generated_schema, 'create_table "test2.sst_schema_test_mulitple_schema"' + assert_includes generated_schema, 'create_table "test2.sst_schema_test_multiple_schema"' end private From 9c9f356a4cda2c90ed1aea42fd35c59c41fc737c Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 15 Jul 2024 17:13:28 +0100 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 213a453e3..b445b0e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +#### Added + +- [#1201](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1201) Support non-dbo schemas in schema dumper. + #### Changed - [#1153](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1153) Only support Ruby v3.1+ From e2d17b69af3ec349a114beef896b9feee7a605cc Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 16 Jul 2024 14:37:16 +0100 Subject: [PATCH 7/7] Update schema_dumper_test_sqlserver.rb --- test/cases/schema_dumper_test_sqlserver.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index 49320e853..a3c82d87f 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -160,11 +160,12 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase _(output.scan('t.integer "unique_field"').length).must_equal(1) end - it "schemas are dumped and tables names include non-default schema" do + it "schemas are dumped and tables names only include non-default schema" do stream = StringIO.new ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) generated_schema = stream.string + # Only generate non-default schemas. Default schema is 'dbo'. assert_not_includes generated_schema, 'create_schema "dbo"' assert_includes generated_schema, 'create_schema "test"' assert_includes generated_schema, 'create_schema "test2"'