activerecord-sqlserver-adapter 7.0.6 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -3
  3. data/CHANGELOG.md +2 -88
  4. data/Gemfile +3 -0
  5. data/README.md +16 -11
  6. data/Rakefile +2 -6
  7. data/VERSION +1 -1
  8. data/activerecord-sqlserver-adapter.gemspec +1 -1
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +29 -6
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -4
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -2
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +15 -3
  14. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +87 -131
  16. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  17. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -2
  18. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +24 -0
  19. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +71 -58
  20. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  21. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +6 -0
  22. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  23. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +10 -0
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +81 -118
  25. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  26. data/lib/active_record/sqlserver_base.rb +1 -10
  27. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -2
  28. data/lib/arel/visitors/sqlserver.rb +0 -33
  29. data/test/cases/adapter_test_sqlserver.rb +8 -7
  30. data/test/cases/coerced_tests.rb +558 -248
  31. data/test/cases/column_test_sqlserver.rb +6 -6
  32. data/test/cases/connection_test_sqlserver.rb +3 -6
  33. data/test/cases/disconnected_test_sqlserver.rb +5 -8
  34. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  35. data/test/cases/rake_test_sqlserver.rb +1 -1
  36. data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
  37. data/test/cases/view_test_sqlserver.rb +6 -10
  38. data/test/config.yml +1 -2
  39. data/test/support/connection_reflection.rb +2 -8
  40. data/test/support/core_ext/query_cache.rb +7 -1
  41. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  42. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  43. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  44. 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,36 +921,28 @@ 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
924
  end
852
925
 
853
926
  require "models/post"
854
927
  require "models/subscriber"
855
928
  class EachTest < ActiveRecord::TestCase
856
929
  # 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
930
+ # TODO: Remove coerced test when https://github.com/rails/rails/pull/49269 merged.
931
+ coerce_tests! :test_in_batches_no_subqueries_for_whole_tables_batching
932
+ def test_in_batches_no_subqueries_for_whole_tables_batching_coerced
933
+ c = Post.connection
934
+ quoted_posts_id = Regexp.escape(c.quote_table_name("posts.id"))
935
+ assert_sql(/DELETE FROM #{Regexp.escape(c.quote_table_name("posts"))} WHERE #{quoted_posts_id} > .+ AND #{quoted_posts_id} <=/i) do
936
+ Post.in_batches(of: 2).delete_all
865
937
  end
866
938
  end
867
939
 
868
940
  # Quoting in tests does not cope with bracket quoting.
941
+ # TODO: Remove coerced test when https://github.com/rails/rails/pull/49269 merged.
869
942
  coerce_tests! :test_in_batches_should_quote_batch_order
870
943
  def test_in_batches_should_quote_batch_order_coerced
871
- Post.connection
872
- assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
944
+ c = Post.connection
945
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name('posts'))}\.#{Regexp.escape(c.quote_column_name('id'))}/) do
873
946
  Post.in_batches(of: 1) do |relation|
874
947
  assert_kind_of ActiveRecord::Relation, relation
875
948
  assert_kind_of Post, relation.first
@@ -879,13 +952,13 @@ class EachTest < ActiveRecord::TestCase
879
952
  end
880
953
 
881
954
  class EagerAssociationTest < ActiveRecord::TestCase
882
- # Use LEN() vs length() function.
955
+ # Use LEN() instead of LENGTH() function.
883
956
  coerce_tests! :test_count_with_include
884
957
  def test_count_with_include_coerced
885
958
  assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
886
959
  end
887
960
 
888
- # Use TOP (1) in scope vs limit 1.
961
+ # The raw SQL in the scope uses `limit 1`.
889
962
  coerce_tests! %r{including association based on sql condition and no database column}
890
963
  end
891
964
 
@@ -899,14 +972,6 @@ class FinderTest < ActiveRecord::TestCase
899
972
  coerce_tests! %r{doesn't have implicit ordering},
900
973
  :test_find_doesnt_have_implicit_ordering
901
974
 
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
975
  # Assert SQL Server limit implementation
911
976
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
912
977
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
@@ -1018,6 +1083,92 @@ class FinderTest < ActiveRecord::TestCase
1018
1083
  NonPrimaryKey.implicit_order_column = old_implicit_order_column
1019
1084
  end
1020
1085
 
1086
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1087
+ coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
1088
+ def test_member_on_unloaded_relation_with_composite_primary_key_coerced
1089
+ assert_sql(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1090
+ book = cpk_books(:cpk_great_author_first_book)
1091
+ assert Cpk::Book.where(title: "The first book").member?(book)
1092
+ end
1093
+ end
1094
+
1095
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1096
+ coerce_tests! :test_implicit_order_column_prepends_query_constraints
1097
+ def test_implicit_order_column_prepends_query_constraints_coerced
1098
+ c = ClothingItem.connection
1099
+ ClothingItem.implicit_order_column = "description"
1100
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1101
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1102
+ quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1103
+
1104
+ 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
1105
+ assert_kind_of ClothingItem, ClothingItem.first
1106
+ end
1107
+ ensure
1108
+ ClothingItem.implicit_order_column = nil
1109
+ end
1110
+
1111
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1112
+ coerce_tests! %r{#last for a model with composite query constraints}
1113
+ test "#last for a model with composite query constraints coerced" do
1114
+ c = ClothingItem.connection
1115
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1116
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1117
+
1118
+ assert_sql(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1119
+ assert_kind_of ClothingItem, ClothingItem.last
1120
+ end
1121
+ end
1122
+
1123
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1124
+ coerce_tests! %r{#first for a model with composite query constraints}
1125
+ test "#first for a model with composite query constraints coerced" do
1126
+ c = ClothingItem.connection
1127
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1128
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1129
+
1130
+ assert_sql(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1131
+ assert_kind_of ClothingItem, ClothingItem.first
1132
+ end
1133
+ end
1134
+
1135
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1136
+ coerce_tests! :test_implicit_order_column_reorders_query_constraints
1137
+ def test_implicit_order_column_reorders_query_constraints_coerced
1138
+ c = ClothingItem.connection
1139
+ ClothingItem.implicit_order_column = "color"
1140
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1141
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1142
+
1143
+ assert_sql(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1144
+ assert_kind_of ClothingItem, ClothingItem.first
1145
+ end
1146
+ ensure
1147
+ ClothingItem.implicit_order_column = nil
1148
+ end
1149
+
1150
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1151
+ coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
1152
+ def test_include_on_unloaded_relation_with_composite_primary_key_coerced
1153
+ assert_sql(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1154
+ book = cpk_books(:cpk_great_author_first_book)
1155
+ assert Cpk::Book.where(title: "The first book").include?(book)
1156
+ end
1157
+ end
1158
+
1159
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1160
+ coerce_tests! :test_nth_to_last_with_order_uses_limit
1161
+ def test_nth_to_last_with_order_uses_limit_coerced
1162
+ c = Topic.connection
1163
+ 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
1164
+ Topic.second_to_last
1165
+ end
1166
+
1167
+ 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
1168
+ Topic.order(:updated_at).second_to_last
1169
+ end
1170
+ end
1171
+
1021
1172
  # SQL Server is unable to use aliased SELECT in the HAVING clause.
1022
1173
  coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
1023
1174
  end
@@ -1025,7 +1176,7 @@ end
1025
1176
  module ActiveRecord
1026
1177
  class Migration
1027
1178
  class ForeignKeyTest < ActiveRecord::TestCase
1028
- # We do not support :restrict.
1179
+ # SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
1029
1180
  coerce_tests! :test_add_on_delete_restrict_foreign_key
1030
1181
  def test_add_on_delete_restrict_foreign_key_coerced
1031
1182
  assert_raises ArgumentError do
@@ -1079,27 +1230,6 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1079
1230
  end
1080
1231
  end
1081
1232
 
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
1233
  class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1104
1234
  # Uses || operator in SQL. Just trust core gets value out of this test.
1105
1235
  coerce_tests! :test_does_not_override_select
@@ -1263,14 +1393,14 @@ end
1263
1393
 
1264
1394
  require "models/post"
1265
1395
  class RelationTest < ActiveRecord::TestCase
1266
- # Use LEN vs LENGTH function.
1396
+ # Use LEN() instead of LENGTH() function.
1267
1397
  coerce_tests! :test_reverse_order_with_function
1268
1398
  def test_reverse_order_with_function_coerced
1269
1399
  topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
1270
1400
  assert_equal topics(:second).title, topics.first.title
1271
1401
  end
1272
1402
 
1273
- # Use LEN vs LENGTH function.
1403
+ # Use LEN() instead of LENGTH() function.
1274
1404
  coerce_tests! :test_reverse_order_with_function_other_predicates
1275
1405
  def test_reverse_order_with_function_other_predicates_coerced
1276
1406
  topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
@@ -1288,7 +1418,7 @@ class RelationTest < ActiveRecord::TestCase
1288
1418
  sql_log = capture_sql do
1289
1419
  assert Post.order(:title).reorder(nil).take
1290
1420
  end
1291
- assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1421
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1292
1422
  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
1423
  end
1294
1424
 
@@ -1300,7 +1430,7 @@ class RelationTest < ActiveRecord::TestCase
1300
1430
  post = Post.order(:title).reorder(nil).first
1301
1431
  end
1302
1432
  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}"
1433
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1304
1434
  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
1435
  end
1306
1436
 
@@ -1339,6 +1469,14 @@ class RelationTest < ActiveRecord::TestCase
1339
1469
  end
1340
1470
  end
1341
1471
 
1472
+ # Find any limit via our expression.
1473
+ coerce_tests! %r{relations don't load all records in #pretty_print}
1474
+ def test_relations_dont_load_all_records_in_pretty_print_coerced
1475
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY/) do
1476
+ PP.pp Post.all, StringIO.new # avoid outputting.
1477
+ end
1478
+ end
1479
+
1342
1480
  # Order column must be in the GROUP clause.
1343
1481
  coerce_tests! :test_empty_complex_chained_relations
1344
1482
  def test_empty_complex_chained_relations_coerced
@@ -1368,7 +1506,7 @@ class RelationTest < ActiveRecord::TestCase
1368
1506
  assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
1369
1507
  end
1370
1508
 
1371
- # Use LEN() vs length() function.
1509
+ # Use LEN() instead of LENGTH() function.
1372
1510
  coerce_tests! :test_reverse_arel_assoc_order_with_function
1373
1511
  def test_reverse_arel_assoc_order_with_function_coerced
1374
1512
  topics = Topic.order(Arel.sql("LEN(title)") => :asc).reverse_order
@@ -1388,15 +1526,6 @@ module ActiveRecord
1388
1526
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1389
1527
  assert_equal expected, query
1390
1528
  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
1529
  end
1401
1530
  end
1402
1531
 
@@ -1423,9 +1552,36 @@ class SanitizeTest < ActiveRecord::TestCase
1423
1552
  searchable_post.search_as_scope("20% _reduction_!").to_a
1424
1553
  end
1425
1554
  end
1555
+
1556
+ # Use nvarchar string (N'') in assert
1557
+ coerce_tests! :test_named_bind_with_literal_colons
1558
+ def test_named_bind_with_literal_colons_coerced
1559
+ 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")
1560
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
1561
+ end
1426
1562
  end
1427
1563
 
1428
1564
  class SchemaDumperTest < ActiveRecord::TestCase
1565
+ # Use nvarchar string (N'') in assert
1566
+ coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1567
+ def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1568
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
1569
+ versions.shuffle.each do |v|
1570
+ @schema_migration.create_version(v)
1571
+ end
1572
+
1573
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
1574
+ expected = <<~STR
1575
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("schema_migrations")} (version) VALUES
1576
+ (N'20100301010101'),
1577
+ (N'20100201010101'),
1578
+ (N'20100101010101');
1579
+ STR
1580
+ assert_equal expected.strip, schema_info
1581
+ ensure
1582
+ @schema_migration.delete_all_versions
1583
+ end
1584
+
1429
1585
  # We have precision to 38.
1430
1586
  coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
1431
1587
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
@@ -1450,12 +1606,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
1450
1606
  assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
1451
1607
  end
1452
1608
 
1453
- # SQL Server formats the check constraint expression differently.
1454
- coerce_tests! :test_schema_dumps_check_constraints
1455
- def test_schema_dumps_check_constraints_coerced
1456
- constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
1457
- assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
1458
- end
1609
+ # Tests are not about a specific adapter.
1610
+ coerce_tests! :test_do_not_dump_foreign_keys_when_bypassed_by_config
1459
1611
  end
1460
1612
 
1461
1613
  class SchemaDumperDefaultsTest < ActiveRecord::TestCase
@@ -1499,16 +1651,95 @@ end
1499
1651
 
1500
1652
  require "models/topic"
1501
1653
  class TransactionTest < ActiveRecord::TestCase
1502
- # SQL Server does not have query for release_savepoint
1654
+ # SQL Server does not have query for release_savepoint.
1503
1655
  coerce_tests! :test_releasing_named_savepoints
1504
1656
  def test_releasing_named_savepoints_coerced
1505
1657
  Topic.transaction do
1658
+ Topic.connection.materialize_transactions
1659
+
1506
1660
  Topic.connection.create_savepoint("another")
1507
1661
  Topic.connection.release_savepoint("another")
1508
1662
  # We do not have a notion of releasing, so this does nothing vs raise an error.
1509
1663
  Topic.connection.release_savepoint("another")
1510
1664
  end
1511
1665
  end
1666
+
1667
+ # SQL Server does not have query for release_savepoint.
1668
+ coerce_tests! :test_nested_transactions_after_disable_lazy_transactions
1669
+ def test_nested_transactions_after_disable_lazy_transactions_coerced
1670
+ Topic.connection.disable_lazy_transactions!
1671
+
1672
+ capture_sql do
1673
+ # RealTransaction (begin..commit)
1674
+ Topic.transaction(requires_new: true) do
1675
+ # ResetParentTransaction (no queries)
1676
+ Topic.transaction(requires_new: true) do
1677
+ Topic.delete_all
1678
+ # SavepointTransaction (savepoint..release)
1679
+ Topic.transaction(requires_new: true) do
1680
+ # ResetParentTransaction (no queries)
1681
+ Topic.transaction(requires_new: true) do
1682
+ # no-op
1683
+ end
1684
+ end
1685
+ end
1686
+ Topic.delete_all
1687
+ end
1688
+ end
1689
+
1690
+ actual_queries = ActiveRecord::SQLCounter.log_all
1691
+
1692
+ expected_queries = [
1693
+ /BEGIN/i,
1694
+ /DELETE/i,
1695
+ /^SAVE TRANSACTION/i,
1696
+ /DELETE/i,
1697
+ /COMMIT/i,
1698
+ ]
1699
+
1700
+ assert_equal expected_queries.size, actual_queries.size
1701
+ expected_queries.zip(actual_queries) do |expected, actual|
1702
+ assert_match expected, actual
1703
+ end
1704
+ end
1705
+
1706
+ # SQL Server does not have query for release_savepoint.
1707
+ coerce_tests! :test_nested_transactions_skip_excess_savepoints
1708
+ def test_nested_transactions_skip_excess_savepoints_coerced
1709
+ capture_sql do
1710
+ # RealTransaction (begin..commit)
1711
+ Topic.transaction(requires_new: true) do
1712
+ # ResetParentTransaction (no queries)
1713
+ Topic.transaction(requires_new: true) do
1714
+ Topic.delete_all
1715
+ # SavepointTransaction (savepoint..release)
1716
+ Topic.transaction(requires_new: true) do
1717
+ # ResetParentTransaction (no queries)
1718
+ Topic.transaction(requires_new: true) do
1719
+ Topic.delete_all
1720
+ end
1721
+ end
1722
+ end
1723
+ Topic.delete_all
1724
+ end
1725
+ end
1726
+
1727
+ actual_queries = ActiveRecord::SQLCounter.log_all
1728
+
1729
+ expected_queries = [
1730
+ /BEGIN/i,
1731
+ /DELETE/i,
1732
+ /^SAVE TRANSACTION/i,
1733
+ /DELETE/i,
1734
+ /DELETE/i,
1735
+ /COMMIT/i,
1736
+ ]
1737
+
1738
+ assert_equal expected_queries.size, actual_queries.size
1739
+ expected_queries.zip(actual_queries) do |expected, actual|
1740
+ assert_match expected, actual
1741
+ end
1742
+ end
1512
1743
  end
1513
1744
 
1514
1745
  require "models/tag"
@@ -1637,8 +1868,8 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1637
1868
  coerce_tests! :test_default_negative_integer
1638
1869
  def test_default_negative_integer_coerced
1639
1870
  record = DefaultNumber.new
1640
- assert_equal -5, record.negative_integer
1641
- assert_equal -5, record.negative_integer_before_type_cast
1871
+ assert_equal (-5), record.negative_integer
1872
+ assert_equal (-5), record.negative_integer_before_type_cast
1642
1873
  end
1643
1874
 
1644
1875
  # We do better with native types and do not return strings for everything.
@@ -1727,8 +1958,9 @@ module ActiveRecord
1727
1958
  end
1728
1959
 
1729
1960
  # We need to give the full path for this to work.
1961
+ undef_method :schema_dump_path
1730
1962
  def schema_dump_path
1731
- File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
1963
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1732
1964
  end
1733
1965
  end
1734
1966
  end
@@ -1739,7 +1971,7 @@ require "models/comment"
1739
1971
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1740
1972
  fixtures :posts
1741
1973
 
1742
- # Use LEN() vs length() function.
1974
+ # Use LEN() instead of LENGTH() function.
1743
1975
  coerce_tests! %r{order: always allows Arel}
1744
1976
  test "order: always allows Arel" do
1745
1977
  titles = Post.order(Arel.sql("len(title)")).pluck(:title)
@@ -1747,7 +1979,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1747
1979
  assert_not_empty titles
1748
1980
  end
1749
1981
 
1750
- # Use LEN() vs length() function.
1982
+ # Use LEN() instead of LENGTH() function.
1751
1983
  coerce_tests! %r{pluck: always allows Arel}
1752
1984
  test "pluck: always allows Arel" do
1753
1985
  excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
@@ -1756,7 +1988,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1756
1988
  assert_equal excepted_values, values
1757
1989
  end
1758
1990
 
1759
- # Use LEN() vs length() function.
1991
+ # Use LEN() instead of LENGTH() function.
1760
1992
  coerce_tests! %r{order: allows valid Array arguments}
1761
1993
  test "order: allows valid Array arguments" do
1762
1994
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
@@ -1766,6 +1998,27 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1766
1998
  assert_equal ids_expected, ids
1767
1999
  end
1768
2000
 
2001
+ # Use LEN() instead of LENGTH() function.
2002
+ coerce_tests! %r{order: allows nested functions}
2003
+ test "order: allows nested functions" do
2004
+ ids_expected = Post.order(Arel.sql("author_id, len(trim(title))")).pluck(:id)
2005
+
2006
+ # $DEBUG = true
2007
+ ids = Post.order("author_id, len(trim(title))").pluck(:id)
2008
+
2009
+ assert_equal ids_expected, ids
2010
+ end
2011
+
2012
+ # Use LEN() instead of LENGTH() function.
2013
+ coerce_tests! %r{pluck: allows nested functions}
2014
+ test "pluck: allows nested functions" do
2015
+ title_lengths_expected = Post.pluck(Arel.sql("len(trim(title))"))
2016
+
2017
+ title_lengths = Post.pluck("len(trim(title))")
2018
+
2019
+ assert_equal title_lengths_expected, title_lengths
2020
+ end
2021
+
1769
2022
  test "order: allows string column names that are quoted" do
1770
2023
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1771
2024
 
@@ -1829,6 +2082,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1829
2082
 
1830
2083
  assert_equal titles_expected, titles
1831
2084
  end
2085
+
2086
+ # Collation name should not be quoted. Hardcoded values for different adapters.
2087
+ coerce_tests! %r{order: allows valid arguments with COLLATE}
2088
+ test "order: allows valid arguments with COLLATE" do
2089
+ collation_name = "Latin1_General_CS_AS_WS"
2090
+
2091
+ ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2092
+
2093
+ ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2094
+
2095
+ assert_equal ids_expected, ids
2096
+ end
1832
2097
  end
1833
2098
 
1834
2099
  class ReservedWordTest < ActiveRecord::TestCase
@@ -2028,14 +2293,15 @@ class LogSubscriberTest < ActiveRecord::TestCase
2028
2293
  end
2029
2294
  end
2030
2295
 
2031
- class ActiveRecordSchemaTest < ActiveRecord::TestCase
2032
- # Workaround for randomly failing test.
2033
- coerce_tests! :test_has_primary_key
2034
- def test_has_primary_key_coerced
2035
- @schema_migration.reset_column_information
2036
- original_test_has_primary_key
2037
- end
2038
- end
2296
+ # TODO: Method `reset_column_information` does not exist. Comment out the test for the time being.
2297
+ # class ActiveRecordSchemaTest < ActiveRecord::TestCase
2298
+ # # Workaround for randomly failing test.
2299
+ # coerce_tests! :test_has_primary_key
2300
+ # def test_has_primary_key_coerced
2301
+ # @schema_migration.reset_column_information
2302
+ # original_test_has_primary_key
2303
+ # end
2304
+ # end
2039
2305
 
2040
2306
  class ReloadModelsTest < ActiveRecord::TestCase
2041
2307
  # Skip test on Windows. The number of arguments passed to `IO.popen` in
@@ -2046,6 +2312,7 @@ end
2046
2312
  class MarshalSerializationTest < ActiveRecord::TestCase
2047
2313
  private
2048
2314
 
2315
+ undef_method :marshal_fixture_path
2049
2316
  def marshal_fixture_path(file_name)
2050
2317
  File.expand_path(
2051
2318
  "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
@@ -2080,6 +2347,59 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
2080
2347
  end
2081
2348
  end
2082
2349
 
2350
+ class PreloaderTest < ActiveRecord::TestCase
2351
+ # Need to handle query parameters in SQL regex.
2352
+ coerce_tests! :test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
2353
+ def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute_coerced
2354
+ order = cpk_orders(:cpk_groceries_order_2)
2355
+ _shop_id, order_id = order.id
2356
+ order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
2357
+
2358
+ assert_not_empty order_agreements
2359
+ assert_equal order_agreements.sort, order.order_agreements.sort
2360
+
2361
+ loaded_order = nil
2362
+ sql = capture_sql do
2363
+ loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
2364
+ end
2365
+
2366
+ assert_equal 2, sql.size
2367
+ preload_sql = sql.last
2368
+
2369
+ c = Cpk::OrderAgreement.connection
2370
+ order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
2371
+ order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
2372
+ expectation = /SELECT.*WHERE.* #{order_id_constraint}/
2373
+
2374
+ assert_match(expectation, preload_sql)
2375
+ assert_equal order_agreements.sort, loaded_order.order_agreements.sort
2376
+ end
2377
+
2378
+ # Need to handle query parameters in SQL regex.
2379
+ coerce_tests! :test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
2380
+ def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute_coerced
2381
+ order_agreement = cpk_order_agreements(:order_agreement_three)
2382
+ order = cpk_orders(:cpk_groceries_order_2)
2383
+ assert_equal order, order_agreement.order
2384
+
2385
+ loaded_order_agreement = nil
2386
+ sql = capture_sql do
2387
+ loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
2388
+ end
2389
+
2390
+ assert_equal 2, sql.size
2391
+ preload_sql = sql.last
2392
+
2393
+ c = Cpk::Order.connection
2394
+ order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
2395
+ order_constraint = /#{order_id} = @0.*@0 = \d+$/
2396
+ expectation = /SELECT.*WHERE.* #{order_constraint}/
2397
+
2398
+ assert_match(expectation, preload_sql)
2399
+ assert_equal order, loaded_order_agreement.order
2400
+ end
2401
+ end
2402
+
2083
2403
  class BasePreventWritesTest < ActiveRecord::TestCase
2084
2404
  # SQL Server does not have query for release_savepoint
2085
2405
  coerce_tests! %r{an empty transaction does not raise if preventing writes}
@@ -2092,20 +2412,6 @@ class BasePreventWritesTest < ActiveRecord::TestCase
2092
2412
  end
2093
2413
  end
2094
2414
  end
2095
-
2096
- class BasePreventWritesLegacyTest < ActiveRecord::TestCase
2097
- # SQL Server does not have query for release_savepoint
2098
- coerce_tests! %r{an empty transaction does not raise if preventing writes}
2099
- test "an empty transaction does not raise if preventing writes coerced" do
2100
- ActiveRecord::Base.connection_handler.while_preventing_writes do
2101
- assert_queries(1, ignore_none: true) do
2102
- Bird.transaction do
2103
- ActiveRecord::Base.connection.materialize_transactions
2104
- end
2105
- end
2106
- end
2107
- end
2108
- end
2109
2415
  end
2110
2416
 
2111
2417
  class MigratorTest < ActiveRecord::TestCase
@@ -2167,96 +2473,63 @@ end
2167
2473
 
2168
2474
  require "models/dashboard"
2169
2475
  class QueryLogsTest < ActiveRecord::TestCase
2170
- # Same as original coerced test except our SQL ends with binding.
2171
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44053)
2172
- coerce_tests! :test_custom_basic_tags, :test_custom_proc_tags, :test_multiple_custom_tags, :test_custom_proc_context_tags
2173
- def test_custom_basic_tags_coerced
2174
- ActiveRecord::QueryLogs.tags = [ :application, { custom_string: "test content" } ]
2175
-
2176
- assert_sql(%r{/\*application:active_record,custom_string:test content\*/}) do
2476
+ # SQL requires double single-quotes.
2477
+ coerce_tests! :test_sql_commenter_format
2478
+ def test_sql_commenter_format_coerced
2479
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2480
+ assert_sql(%r{/\*application=''active_record''\*/}) do
2177
2481
  Dashboard.first
2178
2482
  end
2179
2483
  end
2180
2484
 
2181
- def test_custom_proc_tags_coerced
2182
- ActiveRecord::QueryLogs.tags = [ :application, { custom_proc: -> { "test content" } } ]
2485
+ # SQL requires double single-quotes.
2486
+ coerce_tests! :test_sqlcommenter_format_value
2487
+ def test_sqlcommenter_format_value_coerced
2488
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2489
+
2490
+ ActiveRecord::QueryLogs.tags = [
2491
+ :application,
2492
+ { tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
2493
+ ]
2183
2494
 
2184
- assert_sql(%r{/\*application:active_record,custom_proc:test content\*/}) do
2495
+ assert_sql(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2185
2496
  Dashboard.first
2186
2497
  end
2187
2498
  end
2188
2499
 
2189
- def test_multiple_custom_tags_coerced
2500
+ # SQL requires double single-quotes.
2501
+ coerce_tests! :test_sqlcommenter_format_value_string_coercible
2502
+ def test_sqlcommenter_format_value_string_coercible_coerced
2503
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2504
+
2190
2505
  ActiveRecord::QueryLogs.tags = [
2191
2506
  :application,
2192
- { custom_proc: -> { "test content" }, another_proc: -> { "more test content" } },
2507
+ { custom_proc: -> { 1234 } },
2193
2508
  ]
2194
2509
 
2195
- assert_sql(%r{/\*application:active_record,custom_proc:test content,another_proc:more test content\*/}) do
2510
+ assert_sql(%r{custom_proc=''1234''\*/}) do
2196
2511
  Dashboard.first
2197
2512
  end
2198
2513
  end
2199
2514
 
2200
- def test_custom_proc_context_tags_coerced
2201
- ActiveSupport::ExecutionContext[:foo] = "bar"
2202
- ActiveRecord::QueryLogs.tags = [ :application, { custom_context_proc: ->(context) { context[:foo] } } ]
2203
-
2204
- assert_sql(%r{/\*application:active_record,custom_context_proc:bar\*/}) do
2205
- Dashboard.first
2515
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2516
+ coerce_tests! :test_invalid_encoding_query
2517
+ def test_invalid_encoding_query_coerced
2518
+ ActiveRecord::QueryLogs.tags = [ :application ]
2519
+ assert_raises ActiveRecord::StatementInvalid do
2520
+ ActiveRecord::Base.connection.execute "select 1 as '\xFF'"
2206
2521
  end
2207
2522
  end
2208
2523
  end
2209
2524
 
2210
- # SQL Server does not support upsert yet
2211
- # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44050)
2212
2525
  class InsertAllTest < ActiveRecord::TestCase
2213
- coerce_tests! :test_upsert_all_only_updates_the_column_provided_via_update_only
2214
- def test_upsert_all_only_updates_the_column_provided_via_update_only_coerced
2215
- assert_raises(ArgumentError, /does not support upsert/) do
2216
- original_test_upsert_all_only_updates_the_column_provided_via_update_only
2217
- end
2218
- end
2219
-
2220
- coerce_tests! :test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2221
- def test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only_coerced
2222
- assert_raises(ArgumentError, /does not support upsert/) do
2223
- original_test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2224
- end
2225
- end
2226
-
2227
- coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2228
- def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true_coerced
2229
- assert_raises(ArgumentError, /does not support upsert/) do
2230
- original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2231
- end
2232
- end
2233
-
2234
- coerce_tests! :test_upsert_all_respects_created_at_precision_when_touched_implicitly
2235
- def test_upsert_all_respects_created_at_precision_when_touched_implicitly_coerced
2236
- assert_raises(ArgumentError, /does not support upsert/) do
2237
- original_test_upsert_all_respects_created_at_precision_when_touched_implicitly
2238
- end
2239
- end
2240
-
2241
- coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden
2242
- def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden_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_true_but_overridden
2245
- end
2246
- end
2247
-
2248
- coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2249
- def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false_coerced
2250
- assert_raises(ArgumentError, /does not support upsert/) do
2251
- original_test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2252
- end
2253
- end
2526
+ # Same as original but using INSERTED.name as UPPER argument
2527
+ coerce_tests! :test_insert_all_returns_requested_sql_fields
2528
+ def test_insert_all_returns_requested_sql_fields_coerced
2529
+ skip unless supports_insert_returning?
2254
2530
 
2255
- coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2256
- def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden_coerced
2257
- assert_raises(ArgumentError, /does not support upsert/) do
2258
- original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2259
- end
2531
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2532
+ assert_equal %w[ REWORK ], result.pluck("name")
2260
2533
  end
2261
2534
  end
2262
2535
 
@@ -2279,17 +2552,6 @@ class HasOneThroughDisableJoinsAssociationsTest < ActiveRecord::TestCase
2279
2552
  end
2280
2553
  end
2281
2554
 
2282
- class InsertAllTest < ActiveRecord::TestCase
2283
- coerce_tests! :test_insert_all_returns_requested_sql_fields
2284
- # Same as original but using INSERTED.name as UPPER argument
2285
- def test_insert_all_returns_requested_sql_fields_coerced
2286
- skip unless supports_insert_returning?
2287
-
2288
- result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2289
- assert_equal %w[ REWORK ], result.pluck("name")
2290
- end
2291
- end
2292
-
2293
2555
  class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::EncryptionTestCase
2294
2556
  # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44052)
2295
2557
  # Same as original but SQL Server string is varchar(4000), not varchar(255) as other adapters. Produce invalid strings with 4001 characters
@@ -2304,48 +2566,96 @@ end
2304
2566
 
2305
2567
  module ActiveRecord
2306
2568
  class Migration
2307
- class CheckConstraintTest < ActiveRecord::TestCase
2308
- # SQL Server formats the check constraint expression differently.
2309
- coerce_tests! :test_check_constraints
2310
- def test_check_constraints_coerced
2311
- check_constraints = @connection.check_constraints("products")
2312
- assert_equal 1, check_constraints.size
2313
-
2314
- constraint = check_constraints.first
2315
- assert_equal "products", constraint.table_name
2316
- assert_equal "products_price_check", constraint.name
2317
- assert_equal "[price]>[discounted_price]", constraint.expression
2569
+ class InvalidOptionsTest < ActiveRecord::TestCase
2570
+ # Include the additional SQL Server migration options.
2571
+ undef_method :invalid_add_column_option_exception_message
2572
+ def invalid_add_column_option_exception_message(key)
2573
+ default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2574
+ default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2575
+
2576
+ "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2318
2577
  end
2578
+ end
2579
+ end
2580
+ end
2319
2581
 
2320
- # SQL Server formats the check constraint expression differently.
2321
- coerce_tests! :test_add_check_constraint
2322
- def test_add_check_constraint_coerced
2323
- @connection.add_check_constraint :trades, "quantity > 0"
2324
-
2325
- check_constraints = @connection.check_constraints("trades")
2326
- assert_equal 1, check_constraints.size
2582
+ # SQL Server does not support upsert. Removed dependency on `insert_all` that uses upsert.
2583
+ class ActiveRecord::Encryption::ConcurrencyTest < ActiveRecord::EncryptionTestCase
2584
+ undef_method :thread_encrypting_and_decrypting
2585
+ def thread_encrypting_and_decrypting(thread_label)
2586
+ posts = 100.times.collect { |index| EncryptedPost.create! title: "Article #{index} (#{thread_label})", body: "Body #{index} (#{thread_label})" }
2327
2587
 
2328
- constraint = check_constraints.first
2329
- assert_equal "trades", constraint.table_name
2330
- assert_equal "chk_rails_2189e9f96c", constraint.name
2331
- assert_equal "[quantity]>(0)", constraint.expression
2588
+ Thread.new do
2589
+ posts.each.with_index do |article, index|
2590
+ assert_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2591
+ article.decrypt
2592
+ assert_not_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2332
2593
  end
2594
+ end
2595
+ end
2596
+ end
2333
2597
 
2334
- # SQL Server formats the check constraint expression differently.
2335
- coerce_tests! :test_remove_check_constraint
2336
- def test_remove_check_constraint_coerced
2337
- @connection.add_check_constraint :trades, "price > 0", name: "price_check"
2338
- @connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
2598
+ # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2599
+ # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2600
+ class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2601
+ private
2602
+ undef_method :serializer
2603
+ def serializer
2604
+ @serializer ||= ::MessagePack::Factory.new.tap do |factory|
2605
+ ActiveRecord::MessagePack::Extensions.install(factory)
2606
+ ActiveSupport::MessagePack::Extensions.install(factory)
2607
+ ActiveSupport::MessagePack::Extensions.install_unregistered_type_fallback(factory)
2608
+ end
2609
+ end
2610
+ end
2339
2611
 
2340
- assert_equal 2, @connection.check_constraints("trades").size
2341
- @connection.remove_check_constraint :trades, name: "quantity_check"
2342
- assert_equal 1, @connection.check_constraints("trades").size
2612
+ class StoreTest < ActiveRecord::TestCase
2613
+ # Set the attribute as JSON type for the `StoreTest#saved changes tracking for accessors with json column` test.
2614
+ Admin::User.attribute :json_options, ActiveRecord::Type::SQLServer::Json.new
2615
+ end
2343
2616
 
2344
- constraint = @connection.check_constraints("trades").first
2345
- assert_equal "trades", constraint.table_name
2346
- assert_equal "price_check", constraint.name
2347
- assert_equal "[price]>(0)", constraint.expression
2348
- end
2617
+ class TestDatabasesTest < ActiveRecord::TestCase
2618
+ # Tests are not about a specific adapter.
2619
+ coerce_all_tests!
2620
+ end
2621
+
2622
+ module ActiveRecord
2623
+ module ConnectionAdapters
2624
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2625
+ # Tests are not about a specific adapter.
2626
+ coerce_all_tests!
2627
+ end
2628
+ end
2629
+ end
2630
+
2631
+ module ActiveRecord
2632
+ module ConnectionAdapters
2633
+ class ConnectionSwappingNestedTest < ActiveRecord::TestCase
2634
+ # Tests are not about a specific adapter.
2635
+ coerce_all_tests!
2636
+ end
2637
+ end
2638
+ end
2639
+
2640
+ module ActiveRecord
2641
+ module ConnectionAdapters
2642
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
2643
+ # Tests are not about a specific adapter.
2644
+ coerce_tests! :test_switching_connections_via_handler
2349
2645
  end
2350
2646
  end
2351
2647
  end
2648
+
2649
+ module ActiveRecord
2650
+ module ConnectionAdapters
2651
+ class ConnectionHandlersMultiPoolConfigTest < ActiveRecord::TestCase
2652
+ # Tests are not about a specific adapter.
2653
+ coerce_all_tests!
2654
+ end
2655
+ end
2656
+ end
2657
+
2658
+ # TODO: Need to uncoerce the 'SerializedAttributeTest' tests before releasing adapter for Rails 7.1
2659
+ class SerializedAttributeTest < ActiveRecord::TestCase
2660
+ coerce_all_tests!
2661
+ end