activerecord-sqlserver-adapter 7.0.7 → 7.1.0.beta1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -2
  3. data/CHANGELOG.md +2 -94
  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 +42 -0
  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 +15 -9
@@ -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