activerecord-sqlserver-adapter 7.0.5.1 → 7.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -3
  3. data/.gitignore +3 -1
  4. data/CHANGELOG.md +38 -83
  5. data/Gemfile +3 -0
  6. data/README.md +16 -11
  7. data/RUNNING_UNIT_TESTS.md +24 -10
  8. data/Rakefile +2 -6
  9. data/VERSION +1 -1
  10. data/activerecord-sqlserver-adapter.gemspec +1 -1
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +29 -6
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -2
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +15 -3
  16. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  17. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +86 -133
  18. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  19. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -2
  20. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +24 -0
  21. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +68 -29
  22. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  23. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +6 -0
  24. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  25. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +10 -0
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +81 -114
  27. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  28. data/lib/active_record/sqlserver_base.rb +1 -10
  29. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -2
  30. data/lib/arel/visitors/sqlserver.rb +0 -33
  31. data/test/cases/adapter_test_sqlserver.rb +8 -7
  32. data/test/cases/coerced_tests.rb +526 -235
  33. data/test/cases/column_test_sqlserver.rb +6 -6
  34. data/test/cases/connection_test_sqlserver.rb +3 -6
  35. data/test/cases/disconnected_test_sqlserver.rb +5 -8
  36. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  37. data/test/cases/rake_test_sqlserver.rb +1 -1
  38. data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
  39. data/test/cases/transaction_test_sqlserver.rb +13 -8
  40. data/test/config.yml +1 -2
  41. data/test/support/connection_reflection.rb +2 -8
  42. data/test/support/core_ext/query_cache.rb +7 -1
  43. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  45. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  46. data/test/support/sql_counter_sqlserver.rb +0 -15
  47. metadata +14 -8
@@ -47,6 +47,21 @@ class UniquenessValidationTest < ActiveRecord::TestCase
47
47
  end
48
48
  end
49
49
 
50
+ class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
51
+ # Need to explicitly set the WHERE clause to truthy.
52
+ coerce_tests! :test_partial_index
53
+ def test_partial_index_coerced
54
+ Topic.validates_uniqueness_of(:title)
55
+ @connection.add_index(:topics, :title, unique: true, where: "approved=1", name: :topics_index)
56
+
57
+ t = Topic.create!(title: "abc")
58
+ t.author_name = "John"
59
+ assert_queries(1) do
60
+ t.valid?
61
+ end
62
+ end
63
+ end
64
+
50
65
  require "models/event"
51
66
  module ActiveRecord
52
67
  class AdapterTest < ActiveRecord::TestCase
@@ -92,12 +107,12 @@ module ActiveRecord
92
107
  original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
93
108
  end
94
109
 
110
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
95
111
  coerce_tests! :test_doesnt_error_when_a_select_query_has_encoding_errors
96
112
  def test_doesnt_error_when_a_select_query_has_encoding_errors_coerced
97
113
  ActiveRecord::Base.while_preventing_writes do
98
114
  # TinyTDS fail on encoding errors.
99
- # But at least we can assert it fails in the client and not before when trying to
100
- # match the query.
115
+ # But at least we can assert it fails in the client and not before when trying to match the query.
101
116
  assert_raises ActiveRecord::StatementInvalid do
102
117
  @connection.select_all("SELECT '\xC8'")
103
118
  end
@@ -106,38 +121,6 @@ module ActiveRecord
106
121
  end
107
122
  end
108
123
 
109
- module ActiveRecord
110
- class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
111
- # Fix randomly failing test. The loading of the model's schema was affecting the test.
112
- coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
113
- def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
114
- Subscriber.send(:load_schema!)
115
- original_test_errors_when_an_insert_query_is_called_while_preventing_writes
116
- end
117
-
118
- # Fix randomly failing test. The loading of the model's schema was affecting the test.
119
- coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
120
- def test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes_coerced
121
- Subscriber.send(:load_schema!)
122
- original_test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
123
- end
124
-
125
- # Fix randomly failing test. The loading of the model's schema was affecting the test.
126
- coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
127
- def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
128
- Subscriber.send(:load_schema!)
129
- original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
130
- end
131
-
132
- # Fix randomly failing test. The loading of the model's schema was affecting the test.
133
- coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
134
- def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
135
- Subscriber.send(:load_schema!)
136
- original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
137
- end
138
- end
139
- end
140
-
141
124
  module ActiveRecord
142
125
  class AdapterTestWithoutTransaction < ActiveRecord::TestCase
143
126
  # SQL Server does not allow truncation of tables that are referenced by foreign key
@@ -513,6 +496,9 @@ class CalculationsTest < ActiveRecord::TestCase
513
496
 
514
497
  # Leave it up to users to format selects/functions so HAVING works correctly.
515
498
  coerce_tests! :test_having_with_strong_parameters
499
+
500
+ # SELECT columns must be in the GROUP clause. Since since `ids` only selects the primary key you cannot perform this query in SQL Server.
501
+ coerce_tests! :test_ids_with_includes_and_non_primary_key_order
516
502
  end
517
503
 
518
504
  module ActiveRecord
@@ -662,11 +648,11 @@ class MigrationTest < ActiveRecord::TestCase
662
648
  end
663
649
  }.new
664
650
 
665
- ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, 100).migrate
651
+ ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, @internal_metadata, 100).migrate
666
652
  assert_column Person, :last_name, "migration_a should have created the last_name column on people"
667
653
 
668
654
  assert_nothing_raised do
669
- ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, 101).migrate
655
+ ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, @internal_metadata, 101).migrate
670
656
  end
671
657
  ensure
672
658
  Person.reset_column_information
@@ -676,6 +662,99 @@ class MigrationTest < ActiveRecord::TestCase
676
662
  end
677
663
  end
678
664
 
665
+ module ActiveRecord
666
+ class Migration
667
+ class CompatibilityTest < ActiveRecord::TestCase
668
+ # Error message depends on the database adapter.
669
+ coerce_tests! :test_create_table_on_7_0
670
+ def test_create_table_on_7_0_coerced
671
+ long_table_name = "a" * (connection.table_name_length + 1)
672
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
673
+ @@long_table_name = long_table_name
674
+ def version; 100 end
675
+ def migrate(x)
676
+ create_table @@long_table_name
677
+ end
678
+ }.new
679
+
680
+ error = assert_raises(StandardError) do
681
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
682
+ end
683
+ assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
684
+ ensure
685
+ connection.drop_table(long_table_name) rescue nil
686
+ end
687
+
688
+ # SQL Server truncates long table names when renaming (https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-ver16).
689
+ coerce_tests! :test_rename_table_on_7_0
690
+ def test_rename_table_on_7_0_coerced
691
+ long_table_name = "a" * (connection.table_name_length + 1)
692
+ connection.create_table(:more_testings)
693
+
694
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
695
+ @@long_table_name = long_table_name
696
+ def version; 100 end
697
+ def migrate(x)
698
+ rename_table :more_testings, @@long_table_name
699
+ end
700
+ }.new
701
+
702
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
703
+ assert connection.table_exists?(long_table_name[0...-1])
704
+ assert_not connection.table_exists?(:more_testings)
705
+ assert connection.table_exists?(long_table_name[0...-1])
706
+ ensure
707
+ connection.drop_table(:more_testings) rescue nil
708
+ connection.drop_table(long_table_name[0...-1]) rescue nil
709
+ end
710
+
711
+ # SQL Server has a different maximum index name length.
712
+ coerce_tests! :test_add_index_errors_on_too_long_name_7_0
713
+ def test_add_index_errors_on_too_long_name_7_0_coerced
714
+ long_index_name = 'a' * (connection.index_name_length + 1)
715
+
716
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
717
+ @@long_index_name = long_index_name
718
+ def migrate(x)
719
+ add_column :testings, :very_long_column_name_to_test_with, :string
720
+ add_index :testings, [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
721
+ end
722
+ }.new
723
+
724
+ error = assert_raises(StandardError) do
725
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
726
+ end
727
+ assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
728
+ end
729
+
730
+ # SQL Server has a different maximum index name length.
731
+ coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
732
+ def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
733
+ long_index_name = 'a' * (connection.index_name_length + 1)
734
+
735
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
736
+ @@long_index_name = long_index_name
737
+ def migrate(x)
738
+ create_table :more_testings do |t|
739
+ t.integer :foo
740
+ t.integer :bar
741
+ t.integer :very_long_column_name_to_test_with
742
+ t.index [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
743
+ end
744
+ end
745
+ }.new
746
+
747
+ error = assert_raises(StandardError) do
748
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
749
+ end
750
+ assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
751
+ ensure
752
+ connection.drop_table :more_testings rescue nil
753
+ end
754
+ end
755
+ end
756
+ end
757
+
679
758
  class CoreTest < ActiveRecord::TestCase
680
759
  # I think fixtures are using the wrong time zone and the `:first`
681
760
  # `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
@@ -699,6 +778,7 @@ end
699
778
  module ActiveRecord
700
779
  # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
701
780
  module DatabaseTasksSetupper
781
+ undef_method :setup
702
782
  def setup
703
783
  @sqlserver_tasks =
704
784
  Class.new do
@@ -721,6 +801,7 @@ module ActiveRecord
721
801
  $stderr, @original_stderr = StringIO.new, $stderr
722
802
  end
723
803
 
804
+ undef_method :with_stubbed_new
724
805
  def with_stubbed_new
725
806
  ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
726
807
  yield
@@ -840,52 +921,16 @@ end
840
921
  class DefaultScopingTest < ActiveRecord::TestCase
841
922
  # We are not doing order duplicate removal anymore.
842
923
  coerce_tests! :test_order_in_default_scope_should_not_prevail
843
-
844
- # Use our escaped format in assertion.
845
- coerce_tests! :test_with_abstract_class_scope_should_be_executed_in_correct_context
846
- def test_with_abstract_class_scope_should_be_executed_in_correct_context_coerced
847
- vegetarian_pattern, gender_pattern = [/[lions].[is_vegetarian]/, /[lions].[gender]/]
848
- assert_match vegetarian_pattern, Lion.all.to_sql
849
- assert_match gender_pattern, Lion.female.to_sql
850
- end
851
- end
852
-
853
- require "models/post"
854
- require "models/subscriber"
855
- class EachTest < ActiveRecord::TestCase
856
- # Quoting in tests does not cope with bracket quoting.
857
- coerce_tests! :test_find_in_batches_should_quote_batch_order
858
- def test_find_in_batches_should_quote_batch_order_coerced
859
- Post.connection
860
- assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
861
- Post.find_in_batches(:batch_size => 1) do |batch|
862
- assert_kind_of Array, batch
863
- assert_kind_of Post, batch.first
864
- end
865
- end
866
- end
867
-
868
- # Quoting in tests does not cope with bracket quoting.
869
- coerce_tests! :test_in_batches_should_quote_batch_order
870
- def test_in_batches_should_quote_batch_order_coerced
871
- Post.connection
872
- assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
873
- Post.in_batches(of: 1) do |relation|
874
- assert_kind_of ActiveRecord::Relation, relation
875
- assert_kind_of Post, relation.first
876
- end
877
- end
878
- end
879
924
  end
880
925
 
881
926
  class EagerAssociationTest < ActiveRecord::TestCase
882
- # Use LEN() vs length() function.
927
+ # Use LEN() instead of LENGTH() function.
883
928
  coerce_tests! :test_count_with_include
884
929
  def test_count_with_include_coerced
885
930
  assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
886
931
  end
887
932
 
888
- # Use TOP (1) in scope vs limit 1.
933
+ # The raw SQL in the scope uses `limit 1`.
889
934
  coerce_tests! %r{including association based on sql condition and no database column}
890
935
  end
891
936
 
@@ -899,14 +944,6 @@ class FinderTest < ActiveRecord::TestCase
899
944
  coerce_tests! %r{doesn't have implicit ordering},
900
945
  :test_find_doesnt_have_implicit_ordering
901
946
 
902
- # Square brackets around column name
903
- coerce_tests! :test_exists_does_not_select_columns_without_alias
904
- def test_exists_does_not_select_columns_without_alias_coerced
905
- assert_sql(/SELECT\s+1 AS one FROM \[topics\].*OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) do
906
- Topic.exists?
907
- end
908
- end
909
-
910
947
  # Assert SQL Server limit implementation
911
948
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
912
949
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
@@ -1018,6 +1055,92 @@ class FinderTest < ActiveRecord::TestCase
1018
1055
  NonPrimaryKey.implicit_order_column = old_implicit_order_column
1019
1056
  end
1020
1057
 
1058
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1059
+ coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
1060
+ def test_member_on_unloaded_relation_with_composite_primary_key_coerced
1061
+ assert_sql(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1062
+ book = cpk_books(:cpk_great_author_first_book)
1063
+ assert Cpk::Book.where(title: "The first book").member?(book)
1064
+ end
1065
+ end
1066
+
1067
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1068
+ coerce_tests! :test_implicit_order_column_prepends_query_constraints
1069
+ def test_implicit_order_column_prepends_query_constraints_coerced
1070
+ c = ClothingItem.connection
1071
+ ClothingItem.implicit_order_column = "description"
1072
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1073
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1074
+ quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1075
+
1076
+ assert_sql(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1077
+ assert_kind_of ClothingItem, ClothingItem.first
1078
+ end
1079
+ ensure
1080
+ ClothingItem.implicit_order_column = nil
1081
+ end
1082
+
1083
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1084
+ coerce_tests! %r{#last for a model with composite query constraints}
1085
+ test "#last for a model with composite query constraints coerced" do
1086
+ c = ClothingItem.connection
1087
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1088
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1089
+
1090
+ assert_sql(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1091
+ assert_kind_of ClothingItem, ClothingItem.last
1092
+ end
1093
+ end
1094
+
1095
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1096
+ coerce_tests! %r{#first for a model with composite query constraints}
1097
+ test "#first for a model with composite query constraints coerced" do
1098
+ c = ClothingItem.connection
1099
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1100
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1101
+
1102
+ assert_sql(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1103
+ assert_kind_of ClothingItem, ClothingItem.first
1104
+ end
1105
+ end
1106
+
1107
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1108
+ coerce_tests! :test_implicit_order_column_reorders_query_constraints
1109
+ def test_implicit_order_column_reorders_query_constraints_coerced
1110
+ c = ClothingItem.connection
1111
+ ClothingItem.implicit_order_column = "color"
1112
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1113
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1114
+
1115
+ assert_sql(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1116
+ assert_kind_of ClothingItem, ClothingItem.first
1117
+ end
1118
+ ensure
1119
+ ClothingItem.implicit_order_column = nil
1120
+ end
1121
+
1122
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1123
+ coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
1124
+ def test_include_on_unloaded_relation_with_composite_primary_key_coerced
1125
+ assert_sql(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1126
+ book = cpk_books(:cpk_great_author_first_book)
1127
+ assert Cpk::Book.where(title: "The first book").include?(book)
1128
+ end
1129
+ end
1130
+
1131
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1132
+ coerce_tests! :test_nth_to_last_with_order_uses_limit
1133
+ def test_nth_to_last_with_order_uses_limit_coerced
1134
+ c = Topic.connection
1135
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1136
+ Topic.second_to_last
1137
+ end
1138
+
1139
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1140
+ Topic.order(:updated_at).second_to_last
1141
+ end
1142
+ end
1143
+
1021
1144
  # SQL Server is unable to use aliased SELECT in the HAVING clause.
1022
1145
  coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
1023
1146
  end
@@ -1025,7 +1148,7 @@ end
1025
1148
  module ActiveRecord
1026
1149
  class Migration
1027
1150
  class ForeignKeyTest < ActiveRecord::TestCase
1028
- # We do not support :restrict.
1151
+ # SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
1029
1152
  coerce_tests! :test_add_on_delete_restrict_foreign_key
1030
1153
  def test_add_on_delete_restrict_foreign_key_coerced
1031
1154
  assert_raises ArgumentError do
@@ -1079,27 +1202,6 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1079
1202
  end
1080
1203
  end
1081
1204
 
1082
- require "models/company"
1083
- class InheritanceTest < ActiveRecord::TestCase
1084
- # Rails test required inserting to a identity column.
1085
- coerce_tests! :test_a_bad_type_column
1086
- def test_a_bad_type_column_coerced
1087
- Company.connection.with_identity_insert_enabled("companies") do
1088
- Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
1089
- end
1090
- assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
1091
- end
1092
-
1093
- # Use Square brackets around column name
1094
- coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
1095
- def test_eager_load_belongs_to_primary_key_quoting_coerced
1096
- Account.connection
1097
- assert_sql(/\[companies\]\.\[id\] = @0.* @0 = 1/) do
1098
- Account.all.merge!(:includes => :firm).find(1)
1099
- end
1100
- end
1101
- end
1102
-
1103
1205
  class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1104
1206
  # Uses || operator in SQL. Just trust core gets value out of this test.
1105
1207
  coerce_tests! :test_does_not_override_select
@@ -1263,14 +1365,14 @@ end
1263
1365
 
1264
1366
  require "models/post"
1265
1367
  class RelationTest < ActiveRecord::TestCase
1266
- # Use LEN vs LENGTH function.
1368
+ # Use LEN() instead of LENGTH() function.
1267
1369
  coerce_tests! :test_reverse_order_with_function
1268
1370
  def test_reverse_order_with_function_coerced
1269
1371
  topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
1270
1372
  assert_equal topics(:second).title, topics.first.title
1271
1373
  end
1272
1374
 
1273
- # Use LEN vs LENGTH function.
1375
+ # Use LEN() instead of LENGTH() function.
1274
1376
  coerce_tests! :test_reverse_order_with_function_other_predicates
1275
1377
  def test_reverse_order_with_function_other_predicates_coerced
1276
1378
  topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
@@ -1288,7 +1390,7 @@ class RelationTest < ActiveRecord::TestCase
1288
1390
  sql_log = capture_sql do
1289
1391
  assert Post.order(:title).reorder(nil).take
1290
1392
  end
1291
- assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1393
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1292
1394
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1293
1395
  end
1294
1396
 
@@ -1300,7 +1402,7 @@ class RelationTest < ActiveRecord::TestCase
1300
1402
  post = Post.order(:title).reorder(nil).first
1301
1403
  end
1302
1404
  assert_equal posts(:welcome), post
1303
- assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1405
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1304
1406
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1305
1407
  end
1306
1408
 
@@ -1339,6 +1441,14 @@ class RelationTest < ActiveRecord::TestCase
1339
1441
  end
1340
1442
  end
1341
1443
 
1444
+ # Find any limit via our expression.
1445
+ coerce_tests! %r{relations don't load all records in #pretty_print}
1446
+ def test_relations_dont_load_all_records_in_pretty_print_coerced
1447
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY/) do
1448
+ PP.pp Post.all, StringIO.new # avoid outputting.
1449
+ end
1450
+ end
1451
+
1342
1452
  # Order column must be in the GROUP clause.
1343
1453
  coerce_tests! :test_empty_complex_chained_relations
1344
1454
  def test_empty_complex_chained_relations_coerced
@@ -1368,7 +1478,7 @@ class RelationTest < ActiveRecord::TestCase
1368
1478
  assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
1369
1479
  end
1370
1480
 
1371
- # Use LEN() vs length() function.
1481
+ # Use LEN() instead of LENGTH() function.
1372
1482
  coerce_tests! :test_reverse_arel_assoc_order_with_function
1373
1483
  def test_reverse_arel_assoc_order_with_function_coerced
1374
1484
  topics = Topic.order(Arel.sql("LEN(title)") => :asc).reverse_order
@@ -1388,15 +1498,6 @@ module ActiveRecord
1388
1498
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1389
1499
  assert_equal expected, query
1390
1500
  end
1391
-
1392
- # Workaround for randomly failing test. Ordering of results not guaranteed.
1393
- # TODO: Remove coerced test when https://github.com/rails/rails/pull/44168 merged.
1394
- coerce_tests! :test_select_quotes_when_using_from_clause
1395
- def test_select_quotes_when_using_from_clause_coerced
1396
- quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
1397
- selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
1398
- assert_equal Post.pluck(:id).sort, selected.sort
1399
- end
1400
1501
  end
1401
1502
  end
1402
1503
 
@@ -1423,9 +1524,36 @@ class SanitizeTest < ActiveRecord::TestCase
1423
1524
  searchable_post.search_as_scope("20% _reduction_!").to_a
1424
1525
  end
1425
1526
  end
1527
+
1528
+ # Use nvarchar string (N'') in assert
1529
+ coerce_tests! :test_named_bind_with_literal_colons
1530
+ def test_named_bind_with_literal_colons_coerced
1531
+ assert_equal "TO_TIMESTAMP(N'2017/08/02 10:59:00', 'YYYY/MM/DD HH12:MI:SS')", bind("TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "2017/08/02 10:59:00")
1532
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
1533
+ end
1426
1534
  end
1427
1535
 
1428
1536
  class SchemaDumperTest < ActiveRecord::TestCase
1537
+ # Use nvarchar string (N'') in assert
1538
+ coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1539
+ def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1540
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
1541
+ versions.shuffle.each do |v|
1542
+ @schema_migration.create_version(v)
1543
+ end
1544
+
1545
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
1546
+ expected = <<~STR
1547
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("schema_migrations")} (version) VALUES
1548
+ (N'20100301010101'),
1549
+ (N'20100201010101'),
1550
+ (N'20100101010101');
1551
+ STR
1552
+ assert_equal expected.strip, schema_info
1553
+ ensure
1554
+ @schema_migration.delete_all_versions
1555
+ end
1556
+
1429
1557
  # We have precision to 38.
1430
1558
  coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
1431
1559
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
@@ -1449,6 +1577,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
1449
1577
  output = dump_all_table_schema([/^[^n]/])
1450
1578
  assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
1451
1579
  end
1580
+
1581
+ # Tests are not about a specific adapter.
1582
+ coerce_tests! :test_do_not_dump_foreign_keys_when_bypassed_by_config
1452
1583
  end
1453
1584
 
1454
1585
  class SchemaDumperDefaultsTest < ActiveRecord::TestCase
@@ -1492,16 +1623,95 @@ end
1492
1623
 
1493
1624
  require "models/topic"
1494
1625
  class TransactionTest < ActiveRecord::TestCase
1495
- # SQL Server does not have query for release_savepoint
1626
+ # SQL Server does not have query for release_savepoint.
1496
1627
  coerce_tests! :test_releasing_named_savepoints
1497
1628
  def test_releasing_named_savepoints_coerced
1498
1629
  Topic.transaction do
1630
+ Topic.connection.materialize_transactions
1631
+
1499
1632
  Topic.connection.create_savepoint("another")
1500
1633
  Topic.connection.release_savepoint("another")
1501
1634
  # We do not have a notion of releasing, so this does nothing vs raise an error.
1502
1635
  Topic.connection.release_savepoint("another")
1503
1636
  end
1504
1637
  end
1638
+
1639
+ # SQL Server does not have query for release_savepoint.
1640
+ coerce_tests! :test_nested_transactions_after_disable_lazy_transactions
1641
+ def test_nested_transactions_after_disable_lazy_transactions_coerced
1642
+ Topic.connection.disable_lazy_transactions!
1643
+
1644
+ capture_sql do
1645
+ # RealTransaction (begin..commit)
1646
+ Topic.transaction(requires_new: true) do
1647
+ # ResetParentTransaction (no queries)
1648
+ Topic.transaction(requires_new: true) do
1649
+ Topic.delete_all
1650
+ # SavepointTransaction (savepoint..release)
1651
+ Topic.transaction(requires_new: true) do
1652
+ # ResetParentTransaction (no queries)
1653
+ Topic.transaction(requires_new: true) do
1654
+ # no-op
1655
+ end
1656
+ end
1657
+ end
1658
+ Topic.delete_all
1659
+ end
1660
+ end
1661
+
1662
+ actual_queries = ActiveRecord::SQLCounter.log_all
1663
+
1664
+ expected_queries = [
1665
+ /BEGIN/i,
1666
+ /DELETE/i,
1667
+ /^SAVE TRANSACTION/i,
1668
+ /DELETE/i,
1669
+ /COMMIT/i,
1670
+ ]
1671
+
1672
+ assert_equal expected_queries.size, actual_queries.size
1673
+ expected_queries.zip(actual_queries) do |expected, actual|
1674
+ assert_match expected, actual
1675
+ end
1676
+ end
1677
+
1678
+ # SQL Server does not have query for release_savepoint.
1679
+ coerce_tests! :test_nested_transactions_skip_excess_savepoints
1680
+ def test_nested_transactions_skip_excess_savepoints_coerced
1681
+ capture_sql do
1682
+ # RealTransaction (begin..commit)
1683
+ Topic.transaction(requires_new: true) do
1684
+ # ResetParentTransaction (no queries)
1685
+ Topic.transaction(requires_new: true) do
1686
+ Topic.delete_all
1687
+ # SavepointTransaction (savepoint..release)
1688
+ Topic.transaction(requires_new: true) do
1689
+ # ResetParentTransaction (no queries)
1690
+ Topic.transaction(requires_new: true) do
1691
+ Topic.delete_all
1692
+ end
1693
+ end
1694
+ end
1695
+ Topic.delete_all
1696
+ end
1697
+ end
1698
+
1699
+ actual_queries = ActiveRecord::SQLCounter.log_all
1700
+
1701
+ expected_queries = [
1702
+ /BEGIN/i,
1703
+ /DELETE/i,
1704
+ /^SAVE TRANSACTION/i,
1705
+ /DELETE/i,
1706
+ /DELETE/i,
1707
+ /COMMIT/i,
1708
+ ]
1709
+
1710
+ assert_equal expected_queries.size, actual_queries.size
1711
+ expected_queries.zip(actual_queries) do |expected, actual|
1712
+ assert_match expected, actual
1713
+ end
1714
+ end
1505
1715
  end
1506
1716
 
1507
1717
  require "models/tag"
@@ -1630,8 +1840,8 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1630
1840
  coerce_tests! :test_default_negative_integer
1631
1841
  def test_default_negative_integer_coerced
1632
1842
  record = DefaultNumber.new
1633
- assert_equal -5, record.negative_integer
1634
- assert_equal -5, record.negative_integer_before_type_cast
1843
+ assert_equal (-5), record.negative_integer
1844
+ assert_equal (-5), record.negative_integer_before_type_cast
1635
1845
  end
1636
1846
 
1637
1847
  # We do better with native types and do not return strings for everything.
@@ -1720,8 +1930,9 @@ module ActiveRecord
1720
1930
  end
1721
1931
 
1722
1932
  # We need to give the full path for this to work.
1933
+ undef_method :schema_dump_path
1723
1934
  def schema_dump_path
1724
- File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
1935
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1725
1936
  end
1726
1937
  end
1727
1938
  end
@@ -1732,7 +1943,7 @@ require "models/comment"
1732
1943
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1733
1944
  fixtures :posts
1734
1945
 
1735
- # Use LEN() vs length() function.
1946
+ # Use LEN() instead of LENGTH() function.
1736
1947
  coerce_tests! %r{order: always allows Arel}
1737
1948
  test "order: always allows Arel" do
1738
1949
  titles = Post.order(Arel.sql("len(title)")).pluck(:title)
@@ -1740,7 +1951,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1740
1951
  assert_not_empty titles
1741
1952
  end
1742
1953
 
1743
- # Use LEN() vs length() function.
1954
+ # Use LEN() instead of LENGTH() function.
1744
1955
  coerce_tests! %r{pluck: always allows Arel}
1745
1956
  test "pluck: always allows Arel" do
1746
1957
  excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
@@ -1749,7 +1960,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1749
1960
  assert_equal excepted_values, values
1750
1961
  end
1751
1962
 
1752
- # Use LEN() vs length() function.
1963
+ # Use LEN() instead of LENGTH() function.
1753
1964
  coerce_tests! %r{order: allows valid Array arguments}
1754
1965
  test "order: allows valid Array arguments" do
1755
1966
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
@@ -1759,6 +1970,27 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1759
1970
  assert_equal ids_expected, ids
1760
1971
  end
1761
1972
 
1973
+ # Use LEN() instead of LENGTH() function.
1974
+ coerce_tests! %r{order: allows nested functions}
1975
+ test "order: allows nested functions" do
1976
+ ids_expected = Post.order(Arel.sql("author_id, len(trim(title))")).pluck(:id)
1977
+
1978
+ # $DEBUG = true
1979
+ ids = Post.order("author_id, len(trim(title))").pluck(:id)
1980
+
1981
+ assert_equal ids_expected, ids
1982
+ end
1983
+
1984
+ # Use LEN() instead of LENGTH() function.
1985
+ coerce_tests! %r{pluck: allows nested functions}
1986
+ test "pluck: allows nested functions" do
1987
+ title_lengths_expected = Post.pluck(Arel.sql("len(trim(title))"))
1988
+
1989
+ title_lengths = Post.pluck("len(trim(title))")
1990
+
1991
+ assert_equal title_lengths_expected, title_lengths
1992
+ end
1993
+
1762
1994
  test "order: allows string column names that are quoted" do
1763
1995
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1764
1996
 
@@ -1822,6 +2054,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1822
2054
 
1823
2055
  assert_equal titles_expected, titles
1824
2056
  end
2057
+
2058
+ # Collation name should not be quoted. Hardcoded values for different adapters.
2059
+ coerce_tests! %r{order: allows valid arguments with COLLATE}
2060
+ test "order: allows valid arguments with COLLATE" do
2061
+ collation_name = "Latin1_General_CS_AS_WS"
2062
+
2063
+ ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2064
+
2065
+ ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2066
+
2067
+ assert_equal ids_expected, ids
2068
+ end
1825
2069
  end
1826
2070
 
1827
2071
  class ReservedWordTest < ActiveRecord::TestCase
@@ -2021,15 +2265,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
2021
2265
  end
2022
2266
  end
2023
2267
 
2024
- class ActiveRecordSchemaTest < ActiveRecord::TestCase
2025
- # Workaround for randomly failing test.
2026
- coerce_tests! :test_has_primary_key
2027
- def test_has_primary_key_coerced
2028
- @schema_migration.reset_column_information
2029
- original_test_has_primary_key
2030
- end
2031
- end
2032
-
2033
2268
  class ReloadModelsTest < ActiveRecord::TestCase
2034
2269
  # Skip test on Windows. The number of arguments passed to `IO.popen` in
2035
2270
  # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
@@ -2039,6 +2274,7 @@ end
2039
2274
  class MarshalSerializationTest < ActiveRecord::TestCase
2040
2275
  private
2041
2276
 
2277
+ undef_method :marshal_fixture_path
2042
2278
  def marshal_fixture_path(file_name)
2043
2279
  File.expand_path(
2044
2280
  "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
@@ -2073,6 +2309,59 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
2073
2309
  end
2074
2310
  end
2075
2311
 
2312
+ class PreloaderTest < ActiveRecord::TestCase
2313
+ # Need to handle query parameters in SQL regex.
2314
+ coerce_tests! :test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
2315
+ def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute_coerced
2316
+ order = cpk_orders(:cpk_groceries_order_2)
2317
+ _shop_id, order_id = order.id
2318
+ order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
2319
+
2320
+ assert_not_empty order_agreements
2321
+ assert_equal order_agreements.sort, order.order_agreements.sort
2322
+
2323
+ loaded_order = nil
2324
+ sql = capture_sql do
2325
+ loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
2326
+ end
2327
+
2328
+ assert_equal 2, sql.size
2329
+ preload_sql = sql.last
2330
+
2331
+ c = Cpk::OrderAgreement.connection
2332
+ order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
2333
+ order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
2334
+ expectation = /SELECT.*WHERE.* #{order_id_constraint}/
2335
+
2336
+ assert_match(expectation, preload_sql)
2337
+ assert_equal order_agreements.sort, loaded_order.order_agreements.sort
2338
+ end
2339
+
2340
+ # Need to handle query parameters in SQL regex.
2341
+ coerce_tests! :test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
2342
+ def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute_coerced
2343
+ order_agreement = cpk_order_agreements(:order_agreement_three)
2344
+ order = cpk_orders(:cpk_groceries_order_2)
2345
+ assert_equal order, order_agreement.order
2346
+
2347
+ loaded_order_agreement = nil
2348
+ sql = capture_sql do
2349
+ loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
2350
+ end
2351
+
2352
+ assert_equal 2, sql.size
2353
+ preload_sql = sql.last
2354
+
2355
+ c = Cpk::Order.connection
2356
+ order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
2357
+ order_constraint = /#{order_id} = @0.*@0 = \d+$/
2358
+ expectation = /SELECT.*WHERE.* #{order_constraint}/
2359
+
2360
+ assert_match(expectation, preload_sql)
2361
+ assert_equal order, loaded_order_agreement.order
2362
+ end
2363
+ end
2364
+
2076
2365
  class BasePreventWritesTest < ActiveRecord::TestCase
2077
2366
  # SQL Server does not have query for release_savepoint
2078
2367
  coerce_tests! %r{an empty transaction does not raise if preventing writes}
@@ -2085,20 +2374,6 @@ class BasePreventWritesTest < ActiveRecord::TestCase
2085
2374
  end
2086
2375
  end
2087
2376
  end
2088
-
2089
- class BasePreventWritesLegacyTest < ActiveRecord::TestCase
2090
- # SQL Server does not have query for release_savepoint
2091
- coerce_tests! %r{an empty transaction does not raise if preventing writes}
2092
- test "an empty transaction does not raise if preventing writes coerced" do
2093
- ActiveRecord::Base.connection_handler.while_preventing_writes do
2094
- assert_queries(1, ignore_none: true) do
2095
- Bird.transaction do
2096
- ActiveRecord::Base.connection.materialize_transactions
2097
- end
2098
- end
2099
- end
2100
- end
2101
- end
2102
2377
  end
2103
2378
 
2104
2379
  class MigratorTest < ActiveRecord::TestCase
@@ -2150,7 +2425,7 @@ class FieldOrderedValuesTest < ActiveRecord::TestCase
2150
2425
  coerce_tests! :test_in_order_of_with_nil
2151
2426
  def test_in_order_of_with_nil_coerced
2152
2427
  Book.connection.remove_index(:books, column: [:author_id, :name])
2153
-
2428
+
2154
2429
  original_test_in_order_of_with_nil
2155
2430
  ensure
2156
2431
  Book.where(author_id: nil, name: nil).delete_all
@@ -2160,137 +2435,153 @@ end
2160
2435
 
2161
2436
  require "models/dashboard"
2162
2437
  class QueryLogsTest < ActiveRecord::TestCase
2163
- # Same as original coerced test except our SQL ends with binding.
2164
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44053)
2165
- coerce_tests! :test_custom_basic_tags, :test_custom_proc_tags, :test_multiple_custom_tags, :test_custom_proc_context_tags
2166
- def test_custom_basic_tags_coerced
2167
- ActiveRecord::QueryLogs.tags = [ :application, { custom_string: "test content" } ]
2168
-
2169
- assert_sql(%r{/\*application:active_record,custom_string:test content\*/}) do
2438
+ # SQL requires double single-quotes.
2439
+ coerce_tests! :test_sql_commenter_format
2440
+ def test_sql_commenter_format_coerced
2441
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2442
+ assert_sql(%r{/\*application=''active_record''\*/}) do
2170
2443
  Dashboard.first
2171
2444
  end
2172
2445
  end
2173
2446
 
2174
- def test_custom_proc_tags_coerced
2175
- ActiveRecord::QueryLogs.tags = [ :application, { custom_proc: -> { "test content" } } ]
2447
+ # SQL requires double single-quotes.
2448
+ coerce_tests! :test_sqlcommenter_format_value
2449
+ def test_sqlcommenter_format_value_coerced
2450
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2451
+
2452
+ ActiveRecord::QueryLogs.tags = [
2453
+ :application,
2454
+ { tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
2455
+ ]
2176
2456
 
2177
- assert_sql(%r{/\*application:active_record,custom_proc:test content\*/}) do
2457
+ assert_sql(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2178
2458
  Dashboard.first
2179
2459
  end
2180
2460
  end
2181
2461
 
2182
- def test_multiple_custom_tags_coerced
2462
+ # SQL requires double single-quotes.
2463
+ coerce_tests! :test_sqlcommenter_format_value_string_coercible
2464
+ def test_sqlcommenter_format_value_string_coercible_coerced
2465
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2466
+
2183
2467
  ActiveRecord::QueryLogs.tags = [
2184
2468
  :application,
2185
- { custom_proc: -> { "test content" }, another_proc: -> { "more test content" } },
2469
+ { custom_proc: -> { 1234 } },
2186
2470
  ]
2187
2471
 
2188
- assert_sql(%r{/\*application:active_record,custom_proc:test content,another_proc:more test content\*/}) do
2472
+ assert_sql(%r{custom_proc=''1234''\*/}) do
2189
2473
  Dashboard.first
2190
2474
  end
2191
2475
  end
2192
2476
 
2193
- def test_custom_proc_context_tags_coerced
2194
- ActiveSupport::ExecutionContext[:foo] = "bar"
2195
- ActiveRecord::QueryLogs.tags = [ :application, { custom_context_proc: ->(context) { context[:foo] } } ]
2196
-
2197
- assert_sql(%r{/\*application:active_record,custom_context_proc:bar\*/}) do
2198
- Dashboard.first
2477
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2478
+ coerce_tests! :test_invalid_encoding_query
2479
+ def test_invalid_encoding_query_coerced
2480
+ ActiveRecord::QueryLogs.tags = [ :application ]
2481
+ assert_raises ActiveRecord::StatementInvalid do
2482
+ ActiveRecord::Base.connection.execute "select 1 as '\xFF'"
2199
2483
  end
2200
2484
  end
2201
2485
  end
2202
2486
 
2203
- # SQL Server does not support upsert yet
2204
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44050)
2205
2487
  class InsertAllTest < ActiveRecord::TestCase
2206
- coerce_tests! :test_upsert_all_only_updates_the_column_provided_via_update_only
2207
- def test_upsert_all_only_updates_the_column_provided_via_update_only_coerced
2208
- assert_raises(ArgumentError, /does not support upsert/) do
2209
- original_test_upsert_all_only_updates_the_column_provided_via_update_only
2210
- end
2211
- end
2212
-
2213
- coerce_tests! :test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2214
- def test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only_coerced
2215
- assert_raises(ArgumentError, /does not support upsert/) do
2216
- original_test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2217
- end
2218
- end
2488
+ # Same as original but using INSERTED.name as UPPER argument
2489
+ coerce_tests! :test_insert_all_returns_requested_sql_fields
2490
+ def test_insert_all_returns_requested_sql_fields_coerced
2491
+ skip unless supports_insert_returning?
2219
2492
 
2220
- coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2221
- def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true_coerced
2222
- assert_raises(ArgumentError, /does not support upsert/) do
2223
- original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2224
- end
2493
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2494
+ assert_equal %w[ REWORK ], result.pluck("name")
2225
2495
  end
2496
+ end
2226
2497
 
2227
- coerce_tests! :test_upsert_all_respects_created_at_precision_when_touched_implicitly
2228
- def test_upsert_all_respects_created_at_precision_when_touched_implicitly_coerced
2229
- assert_raises(ArgumentError, /does not support upsert/) do
2230
- original_test_upsert_all_respects_created_at_precision_when_touched_implicitly
2498
+ module ActiveRecord
2499
+ class Migration
2500
+ class InvalidOptionsTest < ActiveRecord::TestCase
2501
+ # Include the additional SQL Server migration options.
2502
+ undef_method :invalid_add_column_option_exception_message
2503
+ def invalid_add_column_option_exception_message(key)
2504
+ default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2505
+ default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2506
+
2507
+ "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2508
+ end
2231
2509
  end
2232
2510
  end
2511
+ end
2233
2512
 
2234
- coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden
2235
- def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden_coerced
2236
- assert_raises(ArgumentError, /does not support upsert/) do
2237
- original_test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden
2238
- end
2239
- end
2513
+ # SQL Server does not support upsert. Removed dependency on `insert_all` that uses upsert.
2514
+ class ActiveRecord::Encryption::ConcurrencyTest < ActiveRecord::EncryptionTestCase
2515
+ undef_method :thread_encrypting_and_decrypting
2516
+ def thread_encrypting_and_decrypting(thread_label)
2517
+ posts = 100.times.collect { |index| EncryptedPost.create! title: "Article #{index} (#{thread_label})", body: "Body #{index} (#{thread_label})" }
2240
2518
 
2241
- coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2242
- def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false_coerced
2243
- assert_raises(ArgumentError, /does not support upsert/) do
2244
- original_test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2519
+ Thread.new do
2520
+ posts.each.with_index do |article, index|
2521
+ assert_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2522
+ article.decrypt
2523
+ assert_not_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2524
+ end
2245
2525
  end
2246
2526
  end
2527
+ end
2247
2528
 
2248
- coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2249
- def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden_coerced
2250
- assert_raises(ArgumentError, /does not support upsert/) do
2251
- original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2529
+ # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2530
+ # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2531
+ class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2532
+ private
2533
+ undef_method :serializer
2534
+ def serializer
2535
+ @serializer ||= ::MessagePack::Factory.new.tap do |factory|
2536
+ ActiveRecord::MessagePack::Extensions.install(factory)
2537
+ ActiveSupport::MessagePack::Extensions.install(factory)
2538
+ ActiveSupport::MessagePack::Extensions.install_unregistered_type_fallback(factory)
2252
2539
  end
2253
2540
  end
2254
2541
  end
2255
2542
 
2256
- class HasOneThroughDisableJoinsAssociationsTest < ActiveRecord::TestCase
2257
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44051)
2258
- coerce_tests! :test_disable_joins_through_with_enum_type
2259
- def test_disable_joins_through_with_enum_type_coerced
2260
- joins = capture_sql { @member.club }
2261
- no_joins = capture_sql { @member.club_without_joins }
2543
+ class StoreTest < ActiveRecord::TestCase
2544
+ # Set the attribute as JSON type for the `StoreTest#saved changes tracking for accessors with json column` test.
2545
+ Admin::User.attribute :json_options, ActiveRecord::Type::SQLServer::Json.new
2546
+ end
2262
2547
 
2263
- assert_equal 1, joins.size
2264
- assert_equal 2, no_joins.size
2548
+ class TestDatabasesTest < ActiveRecord::TestCase
2549
+ # Tests are not about a specific adapter.
2550
+ coerce_all_tests!
2551
+ end
2265
2552
 
2266
- assert_match(/INNER JOIN/, joins.first)
2267
- no_joins.each do |nj|
2268
- assert_no_match(/INNER JOIN/, nj)
2553
+ module ActiveRecord
2554
+ module ConnectionAdapters
2555
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2556
+ # Tests are not about a specific adapter.
2557
+ coerce_all_tests!
2269
2558
  end
2270
-
2271
- assert_match(/\[memberships\]\.\[type\]/, no_joins.first)
2272
2559
  end
2273
2560
  end
2274
2561
 
2275
- class InsertAllTest < ActiveRecord::TestCase
2276
- coerce_tests! :test_insert_all_returns_requested_sql_fields
2277
- # Same as original but using INSERTED.name as UPPER argument
2278
- def test_insert_all_returns_requested_sql_fields_coerced
2279
- skip unless supports_insert_returning?
2562
+ module ActiveRecord
2563
+ module ConnectionAdapters
2564
+ class ConnectionSwappingNestedTest < ActiveRecord::TestCase
2565
+ # Tests are not about a specific adapter.
2566
+ coerce_all_tests!
2567
+ end
2568
+ end
2569
+ end
2280
2570
 
2281
- result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2282
- assert_equal %w[ REWORK ], result.pluck("name")
2571
+ module ActiveRecord
2572
+ module ConnectionAdapters
2573
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
2574
+ # Tests are not about a specific adapter.
2575
+ coerce_tests! :test_switching_connections_via_handler
2576
+ end
2283
2577
  end
2284
2578
  end
2285
2579
 
2286
- class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::EncryptionTestCase
2287
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44052)
2288
- # Same as original but SQL Server string is varchar(4000), not varchar(255) as other adapters. Produce invalid strings with 4001 characters
2289
- coerce_tests! %r{validate column sizes}
2290
- test "validate column sizes coerced" do
2291
- assert EncryptedAuthor.new(name: "jorge").valid?
2292
- assert_not EncryptedAuthor.new(name: "a" * 4001).valid?
2293
- author = EncryptedAuthor.create(name: "a" * 4001)
2294
- assert_not author.valid?
2580
+ module ActiveRecord
2581
+ module ConnectionAdapters
2582
+ class ConnectionHandlersMultiPoolConfigTest < ActiveRecord::TestCase
2583
+ # Tests are not about a specific adapter.
2584
+ coerce_all_tests!
2585
+ end
2295
2586
  end
2296
2587
  end