activerecord-sqlserver-adapter 7.0.5.1 → 7.1.0.rc1

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