From 997c848a7e84109d609281187857cf6a9d78cf44 Mon Sep 17 00:00:00 2001 From: vividmuimui <1803598+vividmuimui@users.noreply.github.com> Date: Thu, 25 Apr 2024 02:53:59 +0900 Subject: [PATCH 01/32] Fix composite primary key with different data type with triggers (#1164) --- README.md | 4 ++++ .../sqlserver/database_statements.rb | 20 ++++++++++++++----- test/cases/trigger_test_sqlserver.rb | 10 ++++++++++ test/models/sqlserver/trigger.rb | 4 ++++ test/schema/sqlserver_specific_schema.rb | 17 ++++++++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7ffee7c7..f15ea8223 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ adapter.exclude_output_inserted_table_names['my_table_name'] = true # Explicitly set the data type for the temporary key table. adapter.exclude_output_inserted_table_names['my_uuid_table_name'] = 'uniqueidentifier' + + +# Explicitly set data types when data type is different for composite primary keys. +adapter.exclude_output_inserted_table_names['my_composite_pk_table_name'] = { pk_col_one: "uniqueidentifier", pk_col_two: "int" } ``` diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index 31f17033f..308b7050e 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -278,13 +278,17 @@ def sql_for_insert(sql, pk, binds, returning) exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql) if exclude_output_inserted - quoted_pk = Array(pk).map { |subkey| SQLServer::Utils.extract_identifiers(subkey).quoted } + pk_and_types = Array(pk).map do |subkey| + { + quoted: SQLServer::Utils.extract_identifiers(subkey).quoted, + id_sql_type: exclude_output_inserted_id_sql_type(subkey, exclude_output_inserted) + } + end - id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted <<~SQL.squish - DECLARE @ssaIdInsertTable table (#{quoted_pk.map { |subkey| "#{subkey} #{id_sql_type}"}.join(", ") }); - #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/i), " OUTPUT #{ quoted_pk.map { |subkey| "INSERTED.#{subkey}" }.join(", ") } INTO @ssaIdInsertTable"} - SELECT #{quoted_pk.map {|subkey| "CAST(#{subkey} AS #{id_sql_type}) #{subkey}"}.join(", ")} FROM @ssaIdInsertTable + DECLARE @ssaIdInsertTable table (#{pk_and_types.map { |pk_and_type| "#{pk_and_type[:quoted]} #{pk_and_type[:id_sql_type]}"}.join(", ") }); + #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/i), " OUTPUT #{ pk_and_types.map { |pk_and_type| "INSERTED.#{pk_and_type[:quoted]}" }.join(", ") } INTO @ssaIdInsertTable"} + SELECT #{pk_and_types.map {|pk_and_type| "CAST(#{pk_and_type[:quoted]} AS #{pk_and_type[:id_sql_type]}) #{pk_and_type[:quoted]}"}.join(", ")} FROM @ssaIdInsertTable SQL else returning_columns = returning || Array(pk) @@ -382,6 +386,12 @@ def exclude_output_inserted_table_name?(table_name, sql) self.class.exclude_output_inserted_table_names[table_name] end + def exclude_output_inserted_id_sql_type(pk, exclude_output_inserted) + return "bigint" if exclude_output_inserted.is_a?(TrueClass) + return exclude_output_inserted[pk.to_sym] if exclude_output_inserted.is_a?(Hash) + exclude_output_inserted + end + def query_requires_identity_insert?(sql) return false unless insert_sql?(sql) diff --git a/test/cases/trigger_test_sqlserver.rb b/test/cases/trigger_test_sqlserver.rb index e530b4840..e964e0d96 100644 --- a/test/cases/trigger_test_sqlserver.rb +++ b/test/cases/trigger_test_sqlserver.rb @@ -38,4 +38,14 @@ class SQLServerTriggerTest < ActiveRecord::TestCase _(obj.pk_col_two).must_equal 42 _(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source end + + it "can insert into a table with composite pk with different data type with output inserted - with a hash setting for table name" do + exclude_output_inserted_table_names["sst_table_with_composite_pk_trigger_with_different_data_type"] = { pk_col_one: "uniqueidentifier", pk_col_two: "int" } + assert SSTestTriggerHistory.all.empty? + obj = SSTestTriggerCompositePkWithDefferentDataType.create! pk_col_two: 123, event_name: "test trigger" + _(obj.event_name).must_equal "test trigger" + _(obj.pk_col_one).must_be :present? + _(obj.pk_col_two).must_equal 123 + _(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source + end end diff --git a/test/models/sqlserver/trigger.rb b/test/models/sqlserver/trigger.rb index bbdd66827..c668f1f2a 100644 --- a/test/models/sqlserver/trigger.rb +++ b/test/models/sqlserver/trigger.rb @@ -11,3 +11,7 @@ class SSTestTriggerUuid < ActiveRecord::Base class SSTestTriggerCompositePk < ActiveRecord::Base self.table_name = "sst_table_with_composite_pk_trigger" end + +class SSTestTriggerCompositePkWithDefferentDataType < ActiveRecord::Base + self.table_name = "sst_table_with_composite_pk_trigger_with_different_data_type" +end diff --git a/test/schema/sqlserver_specific_schema.rb b/test/schema/sqlserver_specific_schema.rb index 8982a2170..1bef7d0e1 100644 --- a/test/schema/sqlserver_specific_schema.rb +++ b/test/schema/sqlserver_specific_schema.rb @@ -249,6 +249,23 @@ SELECT pk_col_one AS id_source, event_name FROM INSERTED SQL + execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_composite_pk_trigger_with_different_data_type') DROP TABLE sst_table_with_composite_pk_trigger_with_different_data_type" + execute <<-SQL + CREATE TABLE sst_table_with_composite_pk_trigger_with_different_data_type( + pk_col_one uniqueidentifier DEFAULT NEWID(), + pk_col_two int NOT NULL, + event_name nvarchar(255), + CONSTRAINT PK_sst_table_with_composite_pk_trigger_with_different_data_type PRIMARY KEY (pk_col_one, pk_col_two) + ) + SQL + execute <<-SQL + CREATE TRIGGER sst_table_with_composite_pk_trigger_with_different_data_type_t ON sst_table_with_composite_pk_trigger_with_different_data_type + FOR INSERT + AS + INSERT INTO sst_table_with_trigger_history (id_source, event_name) + SELECT pk_col_one AS id_source, event_name FROM INSERTED + SQL + # Another schema. create_table :sst_schema_columns, force: true do |t| From c67ea9442537883e601c6141a8b1c79136d02c8a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 24 Apr 2024 19:01:48 +0100 Subject: [PATCH 02/32] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a37d2971..1c58a1cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1164](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1164) Fix composite primary key with different data type with triggers + ## v7.1.3 #### Fixed From 19ae26da437e490150b7ed6b005d396eb8a81e15 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 4 Jul 2024 15:01:34 +0100 Subject: [PATCH 03/32] Remove ActiveRecord::Relation#calculate patch (#1199) --- CHANGELOG.md | 4 ++ .../sqlserver/core_ext/calculations.rb | 44 +------------------ 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c58a1cf7..3d56b088a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - [#1164](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1164) Fix composite primary key with different data type with triggers +#### Changed + +- [#1199](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1199) Remove ActiveRecord::Relation#calculate patch + ## v7.1.3 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb b/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb index 34355ef53..b7b529262 100644 --- a/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +++ b/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb @@ -8,51 +8,9 @@ module ConnectionAdapters module SQLServer module CoreExt module Calculations - def calculate(operation, column_name) - if klass.connection.sqlserver? - _calculate(operation, column_name) - else - super - end - end - + private - # Same as original `calculate` method except we don't perform PostgreSQL hack that removes ordering. - def _calculate(operation, column_name) - operation = operation.to_s.downcase - - if @none - case operation - when "count", "sum" - result = group_values.any? ? Hash.new : 0 - return @async ? Promise::Complete.new(result) : result - when "average", "minimum", "maximum" - result = group_values.any? ? Hash.new : nil - return @async ? Promise::Complete.new(result) : result - end - end - - if has_include?(column_name) - relation = apply_join_dependency - - if operation == "count" - unless distinct_value || distinct_select?(column_name || select_for_count) - relation.distinct! - relation.select_values = [ klass.primary_key || table[Arel.star] ] - end - # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT - # Start of monkey-patch - # relation.order_values = [] if group_values.empty? - # End of monkey-patch - end - - relation.calculate(operation, column_name) - else - perform_calculation(operation, column_name) - end - end - def build_count_subquery(relation, column_name, distinct) return super unless klass.connection.adapter_name == "SQLServer" From 658fce57f58588a9647bdd035c11b5b74cfb74cd Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 4 Jul 2024 15:17:24 +0100 Subject: [PATCH 04/32] Release v7.1.4 --- CHANGELOG.md | 2 +- README.md | 4 ++-- VERSION | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d56b088a..247421f6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.4 #### Fixed diff --git a/README.md b/README.md index f15ea8223..d3abb815c 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | Branch | |-----------------|---------------|---------|--------------------------------------------------------------------------------------------------| -| `7.1.3` | `7.1.x` | Active | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | -| `7.0.5.1` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | +| `7.1.4` | `7.1.x` | Active | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +| `7.0.7` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.3.0` | `6.1.x` | Active | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | | `5.2.1` | `5.2.x` | Ended | [5-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | diff --git a/VERSION b/VERSION index 1996c5044..b7f8ee41e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.3 +7.1.4 From d361c493bb7325a4217eafcc1b774954106452c2 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 16 Jul 2024 14:48:46 +0100 Subject: [PATCH 05/32] Support non-dbo schemas in schema dumper (#1201) --- CHANGELOG.md | 6 ++++ .../sqlserver/schema_dumper.rb | 11 +++++++ .../sqlserver/schema_statements.rb | 31 +++++++++++++++---- test/cases/schema_dumper_test_sqlserver.rb | 27 ++++++++++++++-- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 247421f6f..d70bc3ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Added + +- [#](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/) Support non-dbo schemas in schema dumper. + ## v7.1.4 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb b/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb index 9da6eef6f..d5bb1b348 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 + + 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 a0b43d1b3..afcc0874b 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -393,19 +393,37 @@ 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 " + 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])}" + 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 +432,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 diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index 13ba5b5ce..1b03f8363 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,14 +160,32 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase _(output.scan('t.integer "unique_field"').length).must_equal(1) end + 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"' + + # 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_multiple_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, stream) + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) + @generated_schema = stream.string yield @generated_schema if block_given? @schema_lines = Hash.new @@ -177,6 +196,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 292615450a8eff1ffc03e787a0b83ecee3c7002a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 16 Jul 2024 14:54:43 +0100 Subject: [PATCH 06/32] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d70bc3ada..2226fab10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ #### Added -- [#](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/) 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. ## v7.1.4 From 537afdb4df0b91dc4c08dd7c3aa749a32811ab1a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 16 Jul 2024 15:06:52 +0100 Subject: [PATCH 07/32] Fix merge issue --- test/cases/schema_dumper_test_sqlserver.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index 1b03f8363..86a14cddc 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -162,7 +162,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase it "schemas are dumped and tables names only include non-default schema" do stream = StringIO.new - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) generated_schema = stream.string # Only generate non-default schemas. Default schema is 'dbo'. @@ -184,7 +184,7 @@ def generate_schema_for_table(*table_names) stream = StringIO.new ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream) + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) @generated_schema = stream.string yield @generated_schema if block_given? From 0f020589db910e5131fdff364e73c86e296b719f Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 16 Jul 2024 15:25:17 +0100 Subject: [PATCH 08/32] 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 86a14cddc..042478232 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -173,7 +173,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_multiple_schema"' + assert_includes generated_schema, 'create_table "test2.sst_schema_test_mulitple_schema"' end private From 682f102168575eb49f8f5de2d8549c14b95414e6 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 25 Jul 2024 09:42:43 +0100 Subject: [PATCH 09/32] Support table names containing spaces (#1206) --- CHANGELOG.md | 3 +- .../sqlserver/schema_statements.rb | 18 ++++--- test/cases/adapter_test_sqlserver.rb | 16 ++++++- test/cases/schema_test_sqlserver.rb | 48 ++++++++++++++++++- test/models/sqlserver/table_with_spaces.rb | 5 ++ test/schema/sqlserver_specific_schema.rb | 4 ++ 6 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 test/models/sqlserver/table_with_spaces.rb 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| From 3a06bccd594bcf07fd379a48a465719a03dae6f2 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 25 Jul 2024 09:54:04 +0100 Subject: [PATCH 10/32] Release v7.1.5 --- CHANGELOG.md | 2 +- README.md | 3 ++- VERSION | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7cd4b84..b39ecd621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.5 #### Added diff --git a/README.md b/README.md index d3abb815c..4b8c02646 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | Branch | |-----------------|---------------|---------|--------------------------------------------------------------------------------------------------| -| `7.1.4` | `7.1.x` | Active | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +| Unreleased | `7.2.x` | In Development | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +| `7.1.5` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | | `7.0.7` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.3.0` | `6.1.x` | Active | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | diff --git a/VERSION b/VERSION index b7f8ee41e..69adf3456 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.4 +7.1.5 From 777b6c571168f12c5816ba280dfefb92ef948d12 Mon Sep 17 00:00:00 2001 From: Jesse vB <64111866+jvon1904@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:30:55 -0400 Subject: [PATCH 11/32] Exclude "guest" schmea in schema dumper (#1208) Co-authored-by: Jesse vonBergen --- .../connection_adapters/sqlserver/schema_statements.rb | 2 +- test/cases/schema_dumper_test_sqlserver.rb | 4 ++++ 2 files 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 636bcfc7c..5606e6b75 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -400,7 +400,7 @@ def schema_names FROM sys.schemas WHERE name NOT LIKE 'db_%' AND - name NOT IN ('INFORMATION_SCHEMA', 'sys') + name NOT IN ('INFORMATION_SCHEMA', 'sys', 'guest') SQL query_values(sql, "SCHEMA") diff --git a/test/cases/schema_dumper_test_sqlserver.rb b/test/cases/schema_dumper_test_sqlserver.rb index 042478232..57c576b26 100644 --- a/test/cases/schema_dumper_test_sqlserver.rb +++ b/test/cases/schema_dumper_test_sqlserver.rb @@ -167,6 +167,10 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase # Only generate non-default schemas. Default schema is 'dbo'. assert_not_includes generated_schema, 'create_schema "dbo"' + assert_not_includes generated_schema, 'create_schema "db_owner"' + assert_not_includes generated_schema, 'create_schema "INFORMATION_SCHEMA"' + assert_not_includes generated_schema, 'create_schema "sys"' + assert_not_includes generated_schema, 'create_schema "guest"' assert_includes generated_schema, 'create_schema "test"' assert_includes generated_schema, 'create_schema "test2"' From 4fc39b2fc44d03769e142eb2222a45c0b432c8f6 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 26 Jul 2024 21:33:37 +0100 Subject: [PATCH 12/32] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39ecd621..e5b3c2843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1208](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1208) Exclude "guest" schema in schema dumper + ## v7.1.5 #### Added From b775037f4a136f02d60961ac519e8b15486cb7d7 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 30 Jul 2024 19:08:33 +0100 Subject: [PATCH 13/32] Release v7.1.6 --- CHANGELOG.md | 2 +- README.md | 2 +- VERSION | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b3c2843..c5c5544f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.6 #### Fixed diff --git a/README.md b/README.md index 4b8c02646..842372258 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | Branch | |-----------------|---------------|---------|--------------------------------------------------------------------------------------------------| | Unreleased | `7.2.x` | In Development | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | -| `7.1.5` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | +| `7.1.6` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | | `7.0.7` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.3.0` | `6.1.x` | Active | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | diff --git a/VERSION b/VERSION index 69adf3456..14627a7c8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.5 +7.1.6 From 61b207a4c8ea7e98ac08ed924d9a7fb39a47fb1d Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 1 Aug 2024 16:08:39 +0100 Subject: [PATCH 14/32] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c5544f3..ce6e97eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1210](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1210) Handle blank SQL when parsing table name + ## v7.1.6 #### Fixed From 3699f2f2b86d8d86cd487373895743ed88057eeb Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 1 Aug 2024 16:09:41 +0100 Subject: [PATCH 15/32] Update CHANGELOG.md --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6e97eb2..c5c5544f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,3 @@ -## Unreleased - -#### Fixed - -- [#1210](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1210) Handle blank SQL when parsing table name - ## v7.1.6 #### Fixed From 6fddb9af60852e3abb144f099ac6ffb294ec060d Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 1 Aug 2024 16:23:24 +0100 Subject: [PATCH 16/32] Handle blank SQL when parsing table name (#1210) --- CHANGELOG.md | 6 ++++++ .../connection_adapters/sqlserver/schema_statements.rb | 2 ++ test/cases/schema_test_sqlserver.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c5544f3..ce6e97eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1210](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1210) Handle blank SQL when parsing table name + ## v7.1.6 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 5606e6b75..7af3cae36 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -673,6 +673,8 @@ 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) + return if sql.blank? + s = sql.gsub(/^\s*EXEC sp_executesql N'/i, "") if s.match?(/^\s*INSERT INTO.*/i) diff --git a/test/cases/schema_test_sqlserver.rb b/test/cases/schema_test_sqlserver.rb index 1751af622..255c58711 100644 --- a/test/cases/schema_test_sqlserver.rb +++ b/test/cases/schema_test_sqlserver.rb @@ -66,6 +66,10 @@ class SchemaTestSQLServer < ActiveRecord::TestCase it do assert_equal "[WITH - SPACES$DOLLAR]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES$DOLLAR]") end + + it do + assert_nil connection.send(:get_raw_table_name, nil) + end end describe 'INSERT statements' do From 78b71f15ae1500bbd1f922352890d31cd7250e40 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 1 Aug 2024 16:28:38 +0100 Subject: [PATCH 17/32] Update docker compose command --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5dacad34..f365f0e0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - name: Build docker images - run: docker-compose build --build-arg TARGET_VERSION=${{ matrix.ruby }} + run: docker compose build --build-arg TARGET_VERSION=${{ matrix.ruby }} - name: Run tests - run: docker-compose run ci + run: docker compose run ci From a2f229055d1f39823c8e39332c21a2132708f122 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 1 Aug 2024 17:05:33 +0100 Subject: [PATCH 18/32] Release v7.1.7 --- CHANGELOG.md | 2 +- README.md | 2 +- VERSION | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6e97eb2..079a70ba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.7 #### Fixed diff --git a/README.md b/README.md index 842372258..3c10da13a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | Branch | |-----------------|---------------|---------|--------------------------------------------------------------------------------------------------| | Unreleased | `7.2.x` | In Development | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | -| `7.1.6` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | +| `7.1.7` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | | `7.0.7` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.3.0` | `6.1.x` | Active | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | diff --git a/VERSION b/VERSION index 14627a7c8..2380dcfd4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.6 +7.1.7 From c25db90b931b72a58047f8838377df297e6ffb27 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 1 Oct 2024 16:24:30 +0100 Subject: [PATCH 19/32] Enable identity insert on view's base table (#1232) --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 6 ++++++ .../connection_adapters/sqlserver/database_statements.rb | 3 +++ test/cases/view_test_sqlserver.rb | 8 ++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f365f0e0a..b790e93d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: test: name: Run test suite - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 # TODO: Change back to 'ubuntu-latest' when https://github.com/microsoft/mssql-docker/issues/899 resolved. env: COMPOSE_FILE: docker-compose.ci.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 079a70ba0..121950973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +#### Unreleased + +#### Fixed + +- [#1232](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1232) Enable identity insert on view's base table + ## v7.1.7 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index 308b7050e..d06cdd899 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -45,6 +45,9 @@ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: fa log(sql, name, binds, async: async) do with_raw_connection do |conn| if id_insert_table_name = query_requires_identity_insert?(sql) + # If the table name is a view, we need to get the base table name for enabling identity insert. + id_insert_table_name = view_table_name(id_insert_table_name) if view_exists?(id_insert_table_name) + with_identity_insert_enabled(id_insert_table_name, conn) do result = internal_exec_sql_query(sql, conn) end diff --git a/test/cases/view_test_sqlserver.rb b/test/cases/view_test_sqlserver.rb index b371bd0b1..04f14b957 100644 --- a/test/cases/view_test_sqlserver.rb +++ b/test/cases/view_test_sqlserver.rb @@ -47,4 +47,12 @@ class ViewTestSQLServer < ActiveRecord::TestCase assert_equal 1, klass.count end end + + describe 'identity insert' do + it "identity insert works with views" do + assert_difference("SSTestCustomersView.count", 1) do + SSTestCustomersView.create!(id: 5, name: "Bob") + end + end + end end From 95118ba80a753627745033255682d41a69e3400c Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 2 Oct 2024 09:47:00 +0100 Subject: [PATCH 20/32] Coerce test (#1233) --- test/cases/coerced_tests.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/cases/coerced_tests.rb b/test/cases/coerced_tests.rb index 315a2ac45..3dd00ba38 100644 --- a/test/cases/coerced_tests.rb +++ b/test/cases/coerced_tests.rb @@ -2484,6 +2484,25 @@ def test_sqlcommenter_format_value_string_coercible_coerced end end + # SQL requires double single-quotes. + coerce_tests! :test_sqlcommenter_format_allows_string_keys + def test_sqlcommenter_format_allows_string_keys_coerced + ActiveRecord::QueryLogs.update_formatter(:sqlcommenter) + + ActiveRecord::QueryLogs.tags = [ + :application, + { + "string" => "value", + tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", + custom_proc: -> { "Joe's Shack" } + }, + ] + + assert_sql(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do + Dashboard.first + end + end + # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres. coerce_tests! :test_invalid_encoding_query def test_invalid_encoding_query_coerced From c6c01fa922580f60c19440fb27af24d9c048af16 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 2 Oct 2024 10:41:17 +0100 Subject: [PATCH 21/32] Fix update-all for composite key (#1234) --- lib/arel/visitors/sqlserver.rb | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/arel/visitors/sqlserver.rb b/lib/arel/visitors/sqlserver.rb index fe56bbef1..ef6a23888 100644 --- a/lib/arel/visitors/sqlserver.rb +++ b/lib/arel/visitors/sqlserver.rb @@ -30,10 +30,42 @@ def visit_Arel_Nodes_Concat(o, collector) end def visit_Arel_Nodes_UpdateStatement(o, collector) - if o.orders.any? && o.limit.nil? - o.limit = Nodes::Limit.new(9_223_372_036_854_775_807) + if has_join_and_composite_primary_key?(o) + update_statement_using_join(o, collector) + else + o.limit = Nodes::Limit.new(9_223_372_036_854_775_807) if o.orders.any? && o.limit.nil? + + super end - super + end + + def visit_Arel_Nodes_DeleteStatement(o, collector) + if has_join_and_composite_primary_key?(o) + delete_statement_using_join(o, collector) + else + super + end + end + + def has_join_and_composite_primary_key?(o) + has_join_sources?(o) && o.relation.left.instance_variable_get(:@klass).composite_primary_key? + end + + def delete_statement_using_join(o, collector) + collector << "DELETE " + visit o.relation.left, collector + collector << " FROM " + visit o.relation, collector + collect_nodes_for o.wheres, collector, " WHERE ", " AND " + end + + def update_statement_using_join(o, collector) + collector << "UPDATE " + visit o.relation.left, collector + collect_nodes_for o.values, collector, " SET " + collector << " FROM " + visit o.relation, collector + collect_nodes_for o.wheres, collector, " WHERE ", " AND " end def visit_Arel_Nodes_Lock(o, collector) From 3f873058f28c4d0afd204e44570c92630f3a8289 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 2 Oct 2024 10:47:51 +0100 Subject: [PATCH 22/32] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 121950973..a777b18d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -#### Unreleased +## Unreleased #### Fixed From 0ade39b9c94dea815c6456702b0483e55e9e9dfa Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 2 Oct 2024 10:54:43 +0100 Subject: [PATCH 23/32] Coerce test (#1235) --- test/cases/coerced_tests.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/cases/coerced_tests.rb b/test/cases/coerced_tests.rb index 3dd00ba38..78b65c52a 100644 --- a/test/cases/coerced_tests.rb +++ b/test/cases/coerced_tests.rb @@ -702,7 +702,6 @@ def migrate(x) ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate assert connection.table_exists?(long_table_name[0...-1]) assert_not connection.table_exists?(:more_testings) - assert connection.table_exists?(long_table_name[0...-1]) ensure connection.drop_table(:more_testings) rescue nil connection.drop_table(long_table_name[0...-1]) rescue nil @@ -727,6 +726,28 @@ def migrate(x) assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message) end + # SQL Server truncates long table names when renaming. + coerce_tests! :test_rename_table_errors_on_too_long_index_name_7_0 + def test_rename_table_errors_on_too_long_index_name_7_0_coerced + long_table_name = "a" * (connection.table_name_length + 1) + + migration = Class.new(ActiveRecord::Migration[7.0]) { + def migrate(x) + add_index :testings, :foo + long_table_name = "a" * (connection.table_name_length + 1) + rename_table :testings, long_table_name + end + }.new + + ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate + + assert_not connection.table_exists?(:testings) + assert connection.table_exists?(long_table_name[0...-1]) + assert connection.index_exists?(long_table_name[0...-1], :foo) + ensure + connection.drop_table(long_table_name[0...-1], if_exists: true) + end + # SQL Server has a different maximum index name length. coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0 def test_create_table_add_index_errors_on_too_long_name_7_0_coerced From 0ecb8ad024922df023ea561053f3644308e1563a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 2 Oct 2024 11:02:30 +0100 Subject: [PATCH 24/32] Release v7.1.8 (#1236) --- CHANGELOG.md | 2 +- README.md | 19 ++++++++++--------- VERSION | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a777b18d6..3ac2ad0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.8 #### Fixed diff --git a/README.md b/README.md index 3c10da13a..5b8c144db 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,18 @@ The SQL Server adapter for ActiveRecord using SQL Server 2012 or higher. Interested in older versions? We follow a rational versioning policy that tracks Rails. That means that our 7.x version of the adapter is only for the latest 7.x version of Rails. If you need the adapter for SQL Server 2008 or 2005, you are still in the right spot. Just install the latest 3.2.x to 4.1.x version of the adapter that matches your Rails version. We also have stable branches for each major/minor release of ActiveRecord. +See [Rubygems](https://rubygems.org/gems/activerecord-sqlserver-adapter/versions) for the latest version of the adapter for each Rails release. + | Adapter Version | Rails Version | Support | Branch | |-----------------|---------------|---------|--------------------------------------------------------------------------------------------------| -| Unreleased | `7.2.x` | In Development | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | -| `7.1.7` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | -| `7.0.7` | `7.0.x` | Active | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | -| `6.1.3.0` | `6.1.x` | Active | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | -| `6.0.3` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | -| `5.2.1` | `5.2.x` | Ended | [5-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | -| `5.1.6` | `5.1.x` | Ended | [5-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-1-stable) | -| `4.2.18` | `4.2.x` | Ended | [4-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) | -| `4.1.8` | `4.1.x` | Ended | [4-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) | +| `7.1.x` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) | +| `7.0.x` | `7.0.x` | Ended | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | +| `6.1.x` | `6.1.x` | Ended | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | +| `6.0.x` | `6.0.x` | Ended | [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | +| `5.2.x` | `5.2.x` | Ended | [5-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | +| `5.1.x` | `5.1.x` | Ended | [5-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-1-stable) | +| `4.2.x` | `4.2.x` | Ended | [4-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) | +| `4.1.x` | `4.1.x` | Ended | [4-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) | For older versions, please check their stable branches. diff --git a/VERSION b/VERSION index 2380dcfd4..2fe040f42 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.7 +7.1.8 From 7425b0920d3c0e66c78a82015c59365786c4c6c3 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 15 Oct 2024 11:20:09 +0100 Subject: [PATCH 25/32] Allow INSERT statements with SELECT notation (#1245) --- CHANGELOG.md | 6 ++++++ .../connection_adapters/sqlserver/schema_statements.rb | 1 + test/cases/schema_test_sqlserver.rb | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac2ad0c1..8b96283d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1245](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1245) Allow INSERT statements with SELECT notation + ## v7.1.8 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 7af3cae36..8c4621e4c 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -681,6 +681,7 @@ def get_raw_table_name(sql) s.split(/INSERT INTO/i)[1] .split(/OUTPUT INSERTED/i)[0] .split(/(DEFAULT)?\s+VALUES/i)[0] + .split(/\bSELECT\b(?![^\[]*\])/i)[0] .match(/\s*([^(]*)/i)[0] elsif s.match?(/^\s*UPDATE\s+.*/i) s.match(/UPDATE\s+([^\(\s]+)\s*/i)[1] diff --git a/test/cases/schema_test_sqlserver.rb b/test/cases/schema_test_sqlserver.rb index 255c58711..f9dbde8e6 100644 --- a/test/cases/schema_test_sqlserver.rb +++ b/test/cases/schema_test_sqlserver.rb @@ -96,6 +96,10 @@ class SchemaTestSQLServer < ActiveRecord::TestCase 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 + + it do + assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "INSERT INTO [with].[select notation] SELECT * FROM [table_name]") + end end end end From 17255fdde35d9d7ba6b957d132a6933146e65ef2 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Sun, 10 Nov 2024 15:38:45 +0000 Subject: [PATCH 26/32] Release v7.1.9 --- CHANGELOG.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b96283d5..be5209a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.9 #### Fixed diff --git a/VERSION b/VERSION index 2fe040f42..5978ab132 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.8 +7.1.9 From e050157f243044e3a215d7799c97bd03ad38c4f5 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Sat, 23 Nov 2024 20:45:20 +0000 Subject: [PATCH 27/32] Fix distinct alias when multiple databases used --- CHANGELOG.md | 6 ++++++ .../connection_adapters/sqlserver/schema_statements.rb | 10 +++++++--- test/cases/adapter_test_sqlserver.rb | 9 +++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5209a5e..5122b2256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1262](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1262) Fix distinct alias when multiple databases used. + ## v7.1.9 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 8c4621e4c..fb2f4f045 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -347,12 +347,16 @@ def add_timestamps(table_name, **options) def columns_for_distinct(columns, orders) order_columns = orders.reject(&:blank?).map { |s| - s = s.to_sql unless s.is_a?(String) + s = visitor.compile(s) unless s.is_a?(String) s.gsub(/\s+(?:ASC|DESC)\b/i, "") .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "") - }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } + } + .reject(&:blank?) + .reject { |s| columns.include?(s) } - (order_columns << super).join(", ") + order_columns_aliased = order_columns.map.with_index { |column, i| "#{column} AS alias_#{i}" } + + (order_columns_aliased << super).join(", ") end def update_table_definition(table_name, base) diff --git a/test/cases/adapter_test_sqlserver.rb b/test/cases/adapter_test_sqlserver.rb index 166920e4f..cb433b74e 100644 --- a/test/cases/adapter_test_sqlserver.rb +++ b/test/cases/adapter_test_sqlserver.rb @@ -581,4 +581,13 @@ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes end end end + + describe "distinct select query" do + it "generated SQL does not contain unnecessary alias projection" do + sqls = capture_sql do + Post.includes(:comments).joins(:comments).first + end + assert_no_match(/AS alias_0/, sqls.first) + end + end end From d94d2f97c450e0cfda736673fbd221fb9c4c8c75 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Sun, 8 Dec 2024 12:17:10 +0000 Subject: [PATCH 28/32] Release v7.1.10 (#1265) --- CHANGELOG.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5122b2256..9dbb7e083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.10 #### Fixed diff --git a/VERSION b/VERSION index 5978ab132..346a7e3aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.9 +7.1.10 From 333b354c5bd344f9262954ec81937520ac64401a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 12 Dec 2024 11:27:53 +0000 Subject: [PATCH 29/32] Fix parsing of raw table name from SQL with extra parentheses (#1271) --- CHANGELOG.md | 6 ++++++ .../connection_adapters/sqlserver/schema_statements.rb | 2 +- test/cases/schema_test_sqlserver.rb | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbb7e083..ac5785b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1271](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1271) Fix parsing of raw table name from SQL with extra parentheses + ## v7.1.10 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index fb2f4f045..d1c4382f3 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -690,7 +690,7 @@ def get_raw_table_name(sql) elsif s.match?(/^\s*UPDATE\s+.*/i) s.match(/UPDATE\s+([^\(\s]+)\s*/i)[1] else - s.match(/FROM\s+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1] + s.match(/FROM[\s|\(]+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1] end.strip end diff --git a/test/cases/schema_test_sqlserver.rb b/test/cases/schema_test_sqlserver.rb index f9dbde8e6..124c2d6dc 100644 --- a/test/cases/schema_test_sqlserver.rb +++ b/test/cases/schema_test_sqlserver.rb @@ -101,5 +101,11 @@ class SchemaTestSQLServer < ActiveRecord::TestCase assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "INSERT INTO [with].[select notation] SELECT * FROM [table_name]") end end + + describe 'CREATE VIEW statements' do + it do + assert_equal "test_table_as", connection.send(:get_raw_table_name, "CREATE VIEW test_views ( test_table_a_id, test_table_b_id ) AS SELECT test_table_as.id as test_table_a_id, test_table_bs.id as test_table_b_id FROM (test_table_as with(nolock) LEFT JOIN test_table_bs with(nolock) ON (test_table_as.id = test_table_bs.test_table_a_id))") + end + end end end From 11c79ff1a883d1566ba7176f57f718cf5aea8144 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 20 Dec 2024 19:19:26 +0000 Subject: [PATCH 30/32] Not compatible with TinyTDS v3+ (#1278) --- CHANGELOG.md | 1 + activerecord-sqlserver-adapter.gemspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5785b57..2fae7fbd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Fixed - [#1271](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1271) Fix parsing of raw table name from SQL with extra parentheses +- [#1278](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1278) Not compatible with TinyTDS v3+ ## v7.1.10 diff --git a/activerecord-sqlserver-adapter.gemspec b/activerecord-sqlserver-adapter.gemspec index f158dca18..e8eba69fc 100644 --- a/activerecord-sqlserver-adapter.gemspec +++ b/activerecord-sqlserver-adapter.gemspec @@ -28,5 +28,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activerecord", "~> 7.1.1" - spec.add_dependency "tiny_tds" + spec.add_dependency "tiny_tds", "~> 2.0" end From 10c6e8f9ea6012ac835dd740daa850d5cab28aa0 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Sat, 21 Dec 2024 10:10:43 +0000 Subject: [PATCH 31/32] Revert "Not compatible with TinyTDS v3+ (#1278)" (#1279) This reverts commit 11c79ff1a883d1566ba7176f57f718cf5aea8144. --- CHANGELOG.md | 1 - activerecord-sqlserver-adapter.gemspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fae7fbd2..ac5785b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ #### Fixed - [#1271](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1271) Fix parsing of raw table name from SQL with extra parentheses -- [#1278](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1278) Not compatible with TinyTDS v3+ ## v7.1.10 diff --git a/activerecord-sqlserver-adapter.gemspec b/activerecord-sqlserver-adapter.gemspec index e8eba69fc..f158dca18 100644 --- a/activerecord-sqlserver-adapter.gemspec +++ b/activerecord-sqlserver-adapter.gemspec @@ -28,5 +28,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activerecord", "~> 7.1.1" - spec.add_dependency "tiny_tds", "~> 2.0" + spec.add_dependency "tiny_tds" end From e984b96bf05b631d412ab16b895bde0edf25b88f Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 8 Jan 2025 13:20:18 +0000 Subject: [PATCH 32/32] Release v7.1.11 (#1288) --- CHANGELOG.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5785b57..122f09ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.1.11 #### Fixed diff --git a/VERSION b/VERSION index 346a7e3aa..e0eaaa0bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.10 +7.1.11