From 1356b0aa95b604c13a3b9a9a0651d126a20b64a6 Mon Sep 17 00:00:00 2001 From: David Genord II Date: Thu, 10 Aug 2023 04:24:40 -0400 Subject: [PATCH 01/17] Improve performance of view default function lookup (#1073) --- .../sqlserver/schema_statements.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 34b112392..ccd3c826f 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -371,6 +371,15 @@ def column_definitions(table_name) view_exists = view_exists?(table_name) view_tblnm = view_table_name(table_name) if view_exists + if view_exists + results = sp_executesql %{ + SELECT c.COLUMN_NAME AS [name], c.COLUMN_DEFAULT AS [default] + FROM #{database}.INFORMATION_SCHEMA.COLUMNS c + WHERE c.TABLE_NAME = #{quote(view_tblnm)} + }.squish, "SCHEMA", [] + default_functions = results.each.with_object({}) {|row, out| out[row["name"]] = row["default"] }.compact + end + sql = column_definitions_sql(database, identifier) binds = [] @@ -402,13 +411,8 @@ def column_definitions(table_name) ci[:default_function] = begin default = ci[:default_value] if default.nil? && view_exists - default = select_value %{ - SELECT c.COLUMN_DEFAULT - FROM #{database}.INFORMATION_SCHEMA.COLUMNS c - WHERE - c.TABLE_NAME = '#{view_tblnm}' - AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}' - }.squish, "SCHEMA" + view_column = views_real_column_name(table_name, ci[:name]) + default = default_functions[view_column] if view_column.present? end case default when nil From 47af4b51bd9b91460afa7cdfef1362509ee2f035 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 10 Aug 2023 09:26:12 +0100 Subject: [PATCH 02/17] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 647901c7b..501714c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Changed + +- [#1073](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1073) Improve performance of view default function lookup + ## v7.0.3.0 #### Fixed From cbf0cc9057014d7e9060d2f1bd1f048563fcde72 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 29 Aug 2023 11:18:32 +0100 Subject: [PATCH 03/17] Coerce test --- test/cases/coerced_tests.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/cases/coerced_tests.rb b/test/cases/coerced_tests.rb index e7996f968..23b2efb16 100644 --- a/test/cases/coerced_tests.rb +++ b/test/cases/coerced_tests.rb @@ -2145,6 +2145,17 @@ def test_in_order_of_with_enums_keys_coerced Book.where(author_id: nil, name: nil).delete_all Book.connection.add_index(:books, [:author_id, :name], unique: true) end + + # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite. + coerce_tests! :test_in_order_of_with_nil + def test_in_order_of_with_nil_coerced + Book.connection.remove_index(:books, column: [:author_id, :name]) + + original_test_in_order_of_with_nil + ensure + Book.where(author_id: nil, name: nil).delete_all + Book.connection.add_index(:books, [:author_id, :name], unique: true) + end end require "models/dashboard" From 6f52a5380d9c769b831885fb249f9a25881df81c Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 19 Sep 2023 14:27:13 +0100 Subject: [PATCH 04/17] Fix creation of stored procedures that contain insert statements (#1088) --- CHANGELOG.md | 4 ++++ .../sqlserver/database_statements.rb | 2 +- test/cases/migration_test_sqlserver.rb | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 501714c3b..f57bdab0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - [#1073](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1073) Improve performance of view default function lookup +#### Fixed + +- [#1088](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1088) Fix creation of stored procedures that contain insert statements + ## v7.0.3.0 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index 5e154b6b2..522d9fac2 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -406,7 +406,7 @@ def query_requires_identity_insert?(sql) end def insert_sql?(sql) - !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil? + !(sql =~ /\A\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil? end def identity_columns(table_name) diff --git a/test/cases/migration_test_sqlserver.rb b/test/cases/migration_test_sqlserver.rb index 132d69115..74b14eb18 100644 --- a/test/cases/migration_test_sqlserver.rb +++ b/test/cases/migration_test_sqlserver.rb @@ -115,4 +115,22 @@ class MigrationTestSQLServer < ActiveRecord::TestCase refute_includes schemas, { "name" => "some schema" } end end + + describe 'creating stored procedure' do + it 'stored procedure contains inserts are created successfully' do + sql = <<-SQL + CREATE OR ALTER PROCEDURE do_some_task + AS + IF NOT EXISTS(SELECT * FROM sys.objects WHERE type = 'U' AND name = 'SomeTableName') + BEGIN + CREATE TABLE SomeTableName (SomeNum int PRIMARY KEY CLUSTERED); + INSERT INTO SomeTableName(SomeNum) VALUES(1); + END + SQL + + assert_nothing_raised { connection.execute(sql) } + ensure + connection.execute("DROP PROCEDURE IF EXISTS dbo.do_some_task;") + end + end end From 081cb848919477e746b31c116c7dc4f077457859 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 19 Sep 2023 14:43:43 +0100 Subject: [PATCH 05/17] When changing columns set date-time columns to datetime(6) by default (#1089) --- .../sqlserver/schema_statements.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index ccd3c826f..87713cd5a 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -143,6 +143,15 @@ def remove_column(table_name, column_name, type = nil, **options) def change_column(table_name, column_name, type, options = {}) sql_commands = [] indexes = [] + + if type == :datetime + # If no precision then default it to 6. + options[:precision] = 6 unless options.key?(:precision) + + # If there is precision then column must be of type 'datetime2'. + type = :datetime2 unless options[:precision].nil? + end + column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s } without_constraints = options.key?(:default) || options.key?(:limit) default = if !options.key?(:default) && column_object @@ -150,24 +159,29 @@ def change_column(table_name, column_name, type, options = {}) else options[:default] end + if without_constraints || (column_object && column_object.type != type.to_sym) remove_default_constraint(table_name, column_name) indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) } remove_indexes(table_name, column_name) end + sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil? alter_command = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}" alter_command += " COLLATE #{options[:collation]}" if options[:collation].present? alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false sql_commands << alter_command + if without_constraints default = quote_default_expression(default, column_object || column_for(table_name, column_name)) sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{default} FOR #{quote_column_name(column_name)}" end + # Add any removed indexes back indexes.each do |index| sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})" end + sql_commands.each { |c| do_execute(c) } clear_cache! end @@ -229,6 +243,7 @@ def extract_foreign_key_action(action, fk_name) def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s) limit = nil unless type_limitable + case type.to_s when "integer" case limit From a2db8967d18d6b825e0f9fea444590753f7e119c Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 19 Sep 2023 14:44:48 +0100 Subject: [PATCH 06/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f57bdab0b..81bb6a800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixed - [#1088](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1088) Fix creation of stored procedures that contain insert statements +- [#1089](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1089) When changing columns set date-time columns to datetime(6) by default ## v7.0.3.0 From 5b720b657d55226a79158c324bdd1e72f8e649f6 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Tue, 19 Sep 2023 14:58:23 +0100 Subject: [PATCH 07/17] Release v7.0.4.0 --- CHANGELOG.md | 2 +- README.md | 2 +- VERSION | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81bb6a800..1325fcb07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.0.4.0 #### Changed diff --git a/README.md b/README.md index cf412b10e..ac5314be1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | |-----------------| ------------- | ------------------------------------------------------------------------------------------- | -| `7.0.3.0` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +| `7.0.4.0` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | | `6.1.2.1` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | | `5.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | diff --git a/VERSION b/VERSION index 8117d37ee..2858f1dec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.0.3.0 +7.0.4.0 From 0871ed87aa19b95a9b2d23e082088b965aaf893a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 12 Oct 2023 10:42:15 +0100 Subject: [PATCH 08/17] Fix issue with default view value not being found because of case sensitivity (#1113) --- CHANGELOG.md | 6 ++++ .../sqlserver/schema_statements.rb | 4 +-- test/cases/view_test_sqlserver.rb | 36 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/cases/view_test_sqlserver.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 1325fcb07..4826f4f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1113](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1113) Fix issue with default view value not being found because of case sensitivity + ## v7.0.4.0 #### Changed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 87713cd5a..276a2f2ed 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -388,7 +388,7 @@ def column_definitions(table_name) if view_exists results = sp_executesql %{ - SELECT c.COLUMN_NAME AS [name], c.COLUMN_DEFAULT AS [default] + SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default] FROM #{database}.INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = #{quote(view_tblnm)} }.squish, "SCHEMA", [] @@ -426,7 +426,7 @@ def column_definitions(table_name) ci[:default_function] = begin default = ci[:default_value] if default.nil? && view_exists - view_column = views_real_column_name(table_name, ci[:name]) + view_column = views_real_column_name(table_name, ci[:name]).downcase default = default_functions[view_column] if view_column.present? end case default diff --git a/test/cases/view_test_sqlserver.rb b/test/cases/view_test_sqlserver.rb new file mode 100644 index 000000000..82985f37e --- /dev/null +++ b/test/cases/view_test_sqlserver.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "cases/helper_sqlserver" + +class ViewTestSQLServer < ActiveRecord::TestCase + let(:connection) { ActiveRecord::Base.connection } + + describe 'view with default values' do + before do + connection.drop_table :view_casing_table rescue nil + connection.create_table :view_casing_table, force: true do |t| + t.boolean :Default_Falsey, null: false, default: false + t.boolean :Default_Truthy, null: false, default: true + end + + connection.execute("DROP VIEW IF EXISTS view_casing_table_view;") + connection.execute("CREATE VIEW view_casing_table_view AS SELECT id AS id, default_falsey AS falsey, default_truthy AS truthy FROM view_casing_table") + end + + it "default values are correct when column casing used in tables and views are different" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "view_casing_table_view" + end + + obj = klass.new + assert_equal false, obj.falsey + assert_equal true, obj.truthy + assert_equal 0, klass.count + + obj.save! + assert_equal false, obj.falsey + assert_equal true, obj.truthy + assert_equal 1, klass.count + end + end +end From 48eaad096e472ef920633cd5345f21747384b5c7 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 6 Nov 2023 11:45:08 +0000 Subject: [PATCH 09/17] Fix view issue with default column value not found (#1126) --- CHANGELOG.md | 1 + .../sqlserver/schema_statements.rb | 2 +- test/cases/view_test_sqlserver.rb | 12 +++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4826f4f8a..bf3eb2e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Fixed - [#1113](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1113) Fix issue with default view value not being found because of case sensitivity +- [#1126](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1113) Fix view issue with default column value not found ## v7.0.4.0 diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 276a2f2ed..da296f0c7 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -620,7 +620,7 @@ def views_real_column_name(table_name, column_name) view_definition = view_information(table_name)[:VIEW_DEFINITION] return column_name unless view_definition - match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im) + match_data = view_definition.match(/CREATE\s+VIEW.*AS\s+SELECT.*\W([\w-]*)\s+AS\s+#{column_name}/im) match_data ? match_data[1] : column_name end diff --git a/test/cases/view_test_sqlserver.rb b/test/cases/view_test_sqlserver.rb index 82985f37e..5fd5c973c 100644 --- a/test/cases/view_test_sqlserver.rb +++ b/test/cases/view_test_sqlserver.rb @@ -11,10 +11,18 @@ class ViewTestSQLServer < ActiveRecord::TestCase connection.create_table :view_casing_table, force: true do |t| t.boolean :Default_Falsey, null: false, default: false t.boolean :Default_Truthy, null: false, default: true + t.string :default_string, null: false, default: "abc" end connection.execute("DROP VIEW IF EXISTS view_casing_table_view;") - connection.execute("CREATE VIEW view_casing_table_view AS SELECT id AS id, default_falsey AS falsey, default_truthy AS truthy FROM view_casing_table") + connection.execute <<-SQL + CREATE VIEW view_casing_table_view AS + SELECT id AS id, + default_falsey AS falsey, + default_truthy AS truthy, + default_string AS s + FROM view_casing_table + SQL end it "default values are correct when column casing used in tables and views are different" do @@ -25,11 +33,13 @@ class ViewTestSQLServer < ActiveRecord::TestCase obj = klass.new assert_equal false, obj.falsey assert_equal true, obj.truthy + assert_equal "abc", obj.s assert_equal 0, klass.count obj.save! assert_equal false, obj.falsey assert_equal true, obj.truthy + assert_equal "abc", obj.s assert_equal 1, klass.count end end From 232fc7f4b6858f26aa3f4f5cb6926e3bed025a0d Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 6 Nov 2023 11:59:24 +0000 Subject: [PATCH 10/17] Release 7.0.5.0 --- CHANGELOG.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf3eb2e07..49ebdde93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.0.5.0 #### Fixed diff --git a/VERSION b/VERSION index 2858f1dec..c7ee3e978 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.0.4.0 +7.0.5.0 From bbcfa82331e5ccd5a9ab380f7f349d6827de11ea Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 8 Nov 2023 12:19:22 +0000 Subject: [PATCH 11/17] Fix matching view's real column name (#1133) --- CHANGELOG.md | 6 ++++++ .../sqlserver/schema_statements.rb | 9 ++++++--- test/cases/view_test_sqlserver.rb | 16 ++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ebdde93..0d37bbaec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Fixed + +- [#1133](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1133) Fix matching view's real column name + ## v7.0.5.0 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index da296f0c7..542caba93 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -601,17 +601,19 @@ def view_information(table_name) identifier = SQLServer::Utils.extract_identifiers(table_name) information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]" view_info = select_one "SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA" + if view_info view_info = view_info.with_indifferent_access if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000 view_info[:VIEW_DEFINITION] = begin - select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join + select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join rescue warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;" nil - end + end end end + view_info end end @@ -620,7 +622,8 @@ def views_real_column_name(table_name, column_name) view_definition = view_information(table_name)[:VIEW_DEFINITION] return column_name unless view_definition - match_data = view_definition.match(/CREATE\s+VIEW.*AS\s+SELECT.*\W([\w-]*)\s+AS\s+#{column_name}/im) + # Remove "CREATE VIEW ... AS SELECT ..." and then match the column name. + match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/, '').match(/([\w-]*)\s+AS\s+#{column_name}\W/im) match_data ? match_data[1] : column_name end diff --git a/test/cases/view_test_sqlserver.rb b/test/cases/view_test_sqlserver.rb index 5fd5c973c..b371bd0b1 100644 --- a/test/cases/view_test_sqlserver.rb +++ b/test/cases/view_test_sqlserver.rb @@ -9,18 +9,20 @@ class ViewTestSQLServer < ActiveRecord::TestCase before do connection.drop_table :view_casing_table rescue nil connection.create_table :view_casing_table, force: true do |t| - t.boolean :Default_Falsey, null: false, default: false - t.boolean :Default_Truthy, null: false, default: true - t.string :default_string, null: false, default: "abc" + t.boolean :Default_Falsey, null: false, default: false + t.boolean :Default_Truthy, null: false, default: true + t.string :default_string_null, null: true, default: nil + t.string :default_string, null: false, default: "abc" end connection.execute("DROP VIEW IF EXISTS view_casing_table_view;") connection.execute <<-SQL CREATE VIEW view_casing_table_view AS SELECT id AS id, - default_falsey AS falsey, - default_truthy AS truthy, - default_string AS s + default_falsey AS falsey, + default_truthy AS truthy, + default_string_null AS s_null, + default_string AS s FROM view_casing_table SQL end @@ -34,12 +36,14 @@ class ViewTestSQLServer < ActiveRecord::TestCase assert_equal false, obj.falsey assert_equal true, obj.truthy assert_equal "abc", obj.s + assert_nil obj.s_null assert_equal 0, klass.count obj.save! assert_equal false, obj.falsey assert_equal true, obj.truthy assert_equal "abc", obj.s + assert_nil obj.s_null assert_equal 1, klass.count end end From de6072823143afa5fe5f16da329dbd3ef6318fa3 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Wed, 8 Nov 2023 12:21:29 +0000 Subject: [PATCH 12/17] Release v7.0.5.1 --- CHANGELOG.md | 2 +- README.md | 2 +- VERSION | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d37bbaec..d20a9f643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.0.5.1 #### Fixed diff --git a/README.md b/README.md index ac5314be1..c45bd4618 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | |-----------------| ------------- | ------------------------------------------------------------------------------------------- | -| `7.0.4.0` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +| `7.0.5.1` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | | `6.1.2.1` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | | `5.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | diff --git a/VERSION b/VERSION index c7ee3e978..7f722d5dc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.0.5.0 +7.0.5.1 From 34fe26a600906aafc2e053c4c0e756cf720587d1 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 20 Nov 2023 14:24:01 +0000 Subject: [PATCH 13/17] Added support for check constraints (#1142) --- CHANGELOG.md | 6 ++ .../sqlserver/schema_statements.rb | 23 ++++++++ .../connection_adapters/sqlserver_adapter.rb | 4 ++ test/cases/coerced_tests.rb | 57 ++++++++++++++++++- 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d20a9f643..84035a1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Added + +- [#1141](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1141) Added support for check constraints. + ## v7.0.5.1 #### Fixed diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 542caba93..cba49dcd5 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -240,6 +240,29 @@ def extract_foreign_key_action(action, fk_name) end end + def check_constraints(table_name) + sql = <<~SQL + select chk.name AS 'name', + chk.definition AS 'expression' + from sys.check_constraints chk + inner join sys.tables st on chk.parent_object_id = st.object_id + where + st.name = '#{table_name}' + SQL + + chk_info = exec_query(sql, "SCHEMA") + + chk_info.map do |row| + options = { + name: row["name"] + } + expression = row["expression"] + expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")") + + CheckConstraintDefinition.new(table_name, expression, options) + end + end + def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s) limit = nil unless type_limitable diff --git a/lib/active_record/connection_adapters/sqlserver_adapter.rb b/lib/active_record/connection_adapters/sqlserver_adapter.rb index a8cb14337..b35ecffbe 100644 --- a/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -228,6 +228,10 @@ def supports_datetime_with_precision? true end + def supports_check_constraints? + true + end + def supports_json? @version_year >= 2016 end diff --git a/test/cases/coerced_tests.rb b/test/cases/coerced_tests.rb index 23b2efb16..b60aa57e7 100644 --- a/test/cases/coerced_tests.rb +++ b/test/cases/coerced_tests.rb @@ -1449,6 +1449,13 @@ def test_schema_dump_includes_decimal_options_coerced output = dump_all_table_schema([/^[^n]/]) assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output end + + # SQL Server formats the check constraint expression differently. + coerce_tests! :test_schema_dumps_check_constraints + def test_schema_dumps_check_constraints_coerced + constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip + assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition + end end class SchemaDumperDefaultsTest < ActiveRecord::TestCase @@ -2150,7 +2157,7 @@ def test_in_order_of_with_enums_keys_coerced coerce_tests! :test_in_order_of_with_nil def test_in_order_of_with_nil_coerced Book.connection.remove_index(:books, column: [:author_id, :name]) - + original_test_in_order_of_with_nil ensure Book.where(author_id: nil, name: nil).delete_all @@ -2294,3 +2301,51 @@ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::Encryption assert_not author.valid? end end + +module ActiveRecord + class Migration + class CheckConstraintTest < ActiveRecord::TestCase + # SQL Server formats the check constraint expression differently. + coerce_tests! :test_check_constraints + def test_check_constraints_coerced + check_constraints = @connection.check_constraints("products") + assert_equal 1, check_constraints.size + + constraint = check_constraints.first + assert_equal "products", constraint.table_name + assert_equal "products_price_check", constraint.name + assert_equal "[price]>[discounted_price]", constraint.expression + end + + # SQL Server formats the check constraint expression differently. + coerce_tests! :test_add_check_constraint + def test_add_check_constraint_coerced + @connection.add_check_constraint :trades, "quantity > 0" + + check_constraints = @connection.check_constraints("trades") + assert_equal 1, check_constraints.size + + constraint = check_constraints.first + assert_equal "trades", constraint.table_name + assert_equal "chk_rails_2189e9f96c", constraint.name + assert_equal "[quantity]>(0)", constraint.expression + end + + # SQL Server formats the check constraint expression differently. + coerce_tests! :test_remove_check_constraint + def test_remove_check_constraint_coerced + @connection.add_check_constraint :trades, "price > 0", name: "price_check" + @connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check" + + assert_equal 2, @connection.check_constraints("trades").size + @connection.remove_check_constraint :trades, name: "quantity_check" + assert_equal 1, @connection.check_constraints("trades").size + + constraint = @connection.check_constraints("trades").first + assert_equal "trades", constraint.table_name + assert_equal "price_check", constraint.name + assert_equal "[price]>(0)", constraint.expression + end + end + end +end From faa34a72eb5057047e86da96cf8117e61174aa89 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 20 Nov 2023 14:25:02 +0000 Subject: [PATCH 14/17] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 27b05d95f..6365e37d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ nbproject/ -debug.log +debug.log* .DS_Store pkg/ doc/ @@ -16,3 +16,5 @@ coverage/* .flooignore .floo .byebug_history +tmp/* +test/storage/test.sqlite3* From 6a4ac6039cd7cf9b955fa14acb53490bd46532df Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Mon, 20 Nov 2023 14:31:22 +0000 Subject: [PATCH 15/17] Release v7.0.6 --- CHANGELOG.md | 2 +- README.md | 6 +++--- VERSION | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84035a1e7..40ec49b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.0.6 #### Added diff --git a/README.md b/README.md index c45bd4618..c5ba5d72d 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ 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. | Adapter Version | Rails Version | Support | -|-----------------| ------------- | ------------------------------------------------------------------------------------------- | -| `7.0.5.1` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) | +|-----------------| ------------- |---------------------------------------------------------------------------------------------| +| `7.0.6` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.2.1` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | -| `5.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | +| `5.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | | `5.1.6` | `5.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-1-stable) | | `4.2.18` | `4.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) | | `4.1.8` | `4.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) | diff --git a/VERSION b/VERSION index 7f722d5dc..024b4b9b5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.0.5.1 +7.0.6 From 4cf10846ff9d34009e5779ce21c9a447779306f0 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 4 Jul 2024 15:01:54 +0100 Subject: [PATCH 16/17] Remove ActiveRecord::Relation#calculate patch (#1200) --- .github/workflows/ci.yml | 1 - CHANGELOG.md | 6 ++++++ .../sqlserver/core_ext/calculations.rb | 19 ------------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 687823942..3c48eb342 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: fail-fast: false matrix: ruby: - - 2.7.7 - 3.1.3 - 3.2.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ec49b6e..189d24320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +#### Changed + +- [#1200](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1200) Remove ActiveRecord::Relation#calculate patch + ## v7.0.6 #### Added 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 da0e37f80..0346a090a 100644 --- a/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +++ b/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb @@ -8,25 +8,6 @@ module ConnectionAdapters module SQLServer module CoreExt module Calculations - # Same as original except we don't perform PostgreSQL hack that removes ordering. - def calculate(operation, column_name) - return super unless klass.connection.adapter_name == "SQLServer" - - if has_include?(column_name) - relation = apply_join_dependency - - if operation.to_s.downcase == "count" - unless distinct_value || distinct_select?(column_name || select_for_count) - relation.distinct! - relation.select_values = [klass.primary_key || table[Arel.star]] - end - end - - relation.calculate(operation, column_name) - else - perform_calculation(operation, column_name) - end - end private From 1e9409d1a21805a35147b6567438c4f9ccd51882 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 4 Jul 2024 15:08:14 +0100 Subject: [PATCH 17/17] Release v7.0.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 189d24320..e42a02da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## v7.0.7 #### Changed diff --git a/README.md b/README.md index c5ba5d72d..bdec83b8e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Interested in older versions? We follow a rational versioning policy that tracks | Adapter Version | Rails Version | Support | |-----------------| ------------- |---------------------------------------------------------------------------------------------| -| `7.0.6` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | +| `7.0.7` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) | | `6.1.2.1` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) | | `6.0.3` | `6.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) | | `5.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) | diff --git a/VERSION b/VERSION index 024b4b9b5..2f2974f9b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.0.6 +7.0.7