activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +17 -27
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +49 -41
  10. data/Guardfile +9 -8
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +65 -42
  13. data/RUNNING_UNIT_TESTS.md +3 -0
  14. data/Rakefile +14 -16
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +25 -14
  17. data/appveyor.yml +22 -17
  18. data/docker-compose.ci.yml +7 -5
  19. data/guides/RELEASING.md +11 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +5 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -7
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +51 -14
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
  35. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  37. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
  41. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  42. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  43. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  44. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  45. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -3
  46. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
  81. data/lib/active_record/sqlserver_base.rb +11 -9
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +177 -56
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/active_schema_test_sqlserver.rb +55 -0
  88. data/test/cases/adapter_test_sqlserver.rb +258 -173
  89. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  90. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  91. data/test/cases/coerced_tests.rb +1421 -397
  92. data/test/cases/column_test_sqlserver.rb +321 -315
  93. data/test/cases/connection_test_sqlserver.rb +17 -20
  94. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  95. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  96. data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
  97. data/test/cases/fetch_test_sqlserver.rb +33 -21
  98. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  99. data/test/cases/helper_sqlserver.rb +15 -15
  100. data/test/cases/in_clause_test_sqlserver.rb +63 -0
  101. data/test/cases/index_test_sqlserver.rb +15 -15
  102. data/test/cases/json_test_sqlserver.rb +25 -25
  103. data/test/cases/lateral_test_sqlserver.rb +35 -0
  104. data/test/cases/migration_test_sqlserver.rb +74 -27
  105. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  106. data/test/cases/order_test_sqlserver.rb +59 -53
  107. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  108. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  109. data/test/cases/rake_test_sqlserver.rb +70 -45
  110. data/test/cases/schema_dumper_test_sqlserver.rb +124 -109
  111. data/test/cases/schema_test_sqlserver.rb +20 -26
  112. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  113. data/test/cases/showplan_test_sqlserver.rb +28 -35
  114. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  115. data/test/cases/transaction_test_sqlserver.rb +18 -20
  116. data/test/cases/trigger_test_sqlserver.rb +14 -13
  117. data/test/cases/utils_test_sqlserver.rb +70 -70
  118. data/test/cases/uuid_test_sqlserver.rb +13 -14
  119. data/test/debug.rb +8 -6
  120. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  121. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  122. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  123. data/test/models/sqlserver/booking.rb +3 -1
  124. data/test/models/sqlserver/composite_pk.rb +9 -0
  125. data/test/models/sqlserver/customers_view.rb +3 -1
  126. data/test/models/sqlserver/datatype.rb +2 -0
  127. data/test/models/sqlserver/datatype_migration.rb +2 -0
  128. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  129. data/test/models/sqlserver/edge_schema.rb +3 -3
  130. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  131. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  132. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  133. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  134. data/test/models/sqlserver/no_pk_data.rb +3 -1
  135. data/test/models/sqlserver/object_default.rb +3 -1
  136. data/test/models/sqlserver/quoted_table.rb +4 -2
  137. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  138. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  139. data/test/models/sqlserver/sst_memory.rb +3 -1
  140. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  141. data/test/models/sqlserver/string_default.rb +3 -1
  142. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  143. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  144. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  145. data/test/models/sqlserver/trigger.rb +4 -2
  146. data/test/models/sqlserver/trigger_history.rb +3 -1
  147. data/test/models/sqlserver/upper.rb +3 -1
  148. data/test/models/sqlserver/uppered.rb +3 -1
  149. data/test/models/sqlserver/uuid.rb +3 -1
  150. data/test/schema/sqlserver_specific_schema.rb +56 -21
  151. data/test/support/coerceable_test_sqlserver.rb +19 -13
  152. data/test/support/connection_reflection.rb +3 -2
  153. data/test/support/core_ext/query_cache.rb +4 -1
  154. data/test/support/load_schema_sqlserver.rb +5 -5
  155. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  156. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  157. data/test/support/minitest_sqlserver.rb +3 -1
  158. data/test/support/paths_sqlserver.rb +11 -11
  159. data/test/support/rake_helpers.rb +15 -10
  160. data/test/support/sql_counter_sqlserver.rb +16 -15
  161. data/test/support/test_in_memory_oltp.rb +9 -7
  162. metadata +47 -13
  163. data/.travis.yml +0 -25
  164. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
@@ -1,10 +1,10 @@
1
- require 'cases/helper_sqlserver'
1
+ # frozen_string_literal: true
2
2
 
3
+ require "cases/helper_sqlserver"
3
4
 
4
-
5
- require 'models/event'
5
+ require "models/event"
6
6
  class UniquenessValidationTest < ActiveRecord::TestCase
7
- # So sp_executesql swallows this exception. Run without prpared to see it.
7
+ # So sp_executesql swallows this exception. Run without prepared to see it.
8
8
  coerce_tests! :test_validate_uniqueness_with_limit
9
9
  def test_validate_uniqueness_with_limit_coerced
10
10
  connection.unprepared_statement do
@@ -14,7 +14,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
14
14
  end
15
15
  end
16
16
 
17
- # So sp_executesql swallows this exception. Run without prpared to see it.
17
+ # So sp_executesql swallows this exception. Run without prepared to see it.
18
18
  coerce_tests! :test_validate_uniqueness_with_limit_and_utf8
19
19
  def test_validate_uniqueness_with_limit_and_utf8_coerced
20
20
  connection.unprepared_statement do
@@ -23,27 +23,45 @@ class UniquenessValidationTest < ActiveRecord::TestCase
23
23
  end
24
24
  end
25
25
  end
26
- end
27
26
 
27
+ # Same as original coerced test except that it handles default SQL Server case-insensitive collation.
28
+ coerce_tests! :test_validate_uniqueness_by_default_database_collation
29
+ def test_validate_uniqueness_by_default_database_collation_coerced
30
+ Topic.validates_uniqueness_of(:author_email_address)
31
+
32
+ topic1 = Topic.new(author_email_address: "david@loudthinking.com")
33
+ topic2 = Topic.new(author_email_address: "David@loudthinking.com")
34
+
35
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
28
36
 
37
+ assert_not topic1.valid?
38
+ assert_not topic1.save
29
39
 
40
+ # Case insensitive collation (SQL_Latin1_General_CP1_CI_AS) by default.
41
+ # Should not allow "David" if "david" exists.
42
+ assert_not topic2.valid?
43
+ assert_not topic2.save
30
44
 
31
- require 'models/event'
45
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
46
+ assert_equal 1, Topic.where(author_email_address: "David@loudthinking.com").count
47
+ end
48
+ end
49
+
50
+ require "models/event"
32
51
  module ActiveRecord
33
52
  class AdapterTest < ActiveRecord::TestCase
34
- # I really dont think we can support legacy binds.
35
- coerce_tests! :test_select_all_with_legacy_binds
36
- coerce_tests! :test_insert_update_delete_with_legacy_binds
53
+ # Legacy binds are not supported.
54
+ coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
37
55
 
38
56
  # As far as I can tell, SQL Server does not support null bytes in strings.
39
57
  coerce_tests! :test_update_prepared_statement
40
58
 
41
- # So sp_executesql swallows this exception. Run without prpared to see it.
59
+ # So sp_executesql swallows this exception. Run without prepared to see it.
42
60
  coerce_tests! :test_value_limit_violations_are_translated_to_specific_exception
43
61
  def test_value_limit_violations_are_translated_to_specific_exception_coerced
44
62
  connection.unprepared_statement do
45
63
  error = assert_raises(ActiveRecord::ValueTooLong) do
46
- Event.create(title: 'abcdefgh')
64
+ Event.create(title: "abcdefgh")
47
65
  end
48
66
  assert_not_nil error.cause
49
67
  end
@@ -51,38 +69,153 @@ module ActiveRecord
51
69
  end
52
70
  end
53
71
 
72
+ module ActiveRecord
73
+ class AdapterPreventWritesTest < ActiveRecord::TestCase
74
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
75
+ coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
76
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
77
+ Subscriber.send(:load_schema!)
78
+ original_test_errors_when_an_insert_query_is_called_while_preventing_writes
79
+ end
80
+
81
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
82
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
83
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
84
+ Subscriber.send(:load_schema!)
85
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
86
+ end
87
+
88
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
89
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
90
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
91
+ Subscriber.send(:load_schema!)
92
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
93
+ end
94
+
95
+ coerce_tests! :test_doesnt_error_when_a_select_query_has_encoding_errors
96
+ def test_doesnt_error_when_a_select_query_has_encoding_errors_coerced
97
+ ActiveRecord::Base.while_preventing_writes do
98
+ # 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.
101
+ assert_raises ActiveRecord::StatementInvalid do
102
+ @connection.select_all("SELECT '\xC8'")
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
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
+ module ActiveRecord
142
+ class AdapterTestWithoutTransaction < ActiveRecord::TestCase
143
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
144
+ # constraints. So manually remove/add foreign keys in test.
145
+ coerce_tests! :test_truncate_tables
146
+ def test_truncate_tables_coerced
147
+ # Remove foreign key constraint to allow truncation.
148
+ @connection.remove_foreign_key :authors, :author_addresses
149
+
150
+ assert_operator Post.count, :>, 0
151
+ assert_operator Author.count, :>, 0
152
+ assert_operator AuthorAddress.count, :>, 0
153
+
154
+ @connection.truncate_tables("author_addresses", "authors", "posts")
155
+
156
+ assert_equal 0, Post.count
157
+ assert_equal 0, Author.count
158
+ assert_equal 0, AuthorAddress.count
159
+ ensure
160
+ reset_fixtures("posts", "authors", "author_addresses")
161
+
162
+ # Restore foreign key constraint.
163
+ @connection.add_foreign_key :authors, :author_addresses
164
+ end
165
+
166
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
167
+ # constraints. So manually remove/add foreign keys in test.
168
+ coerce_tests! :test_truncate_tables_with_query_cache
169
+ def test_truncate_tables_with_query_cache
170
+ # Remove foreign key constraint to allow truncation.
171
+ @connection.remove_foreign_key :authors, :author_addresses
54
172
 
173
+ @connection.enable_query_cache!
174
+
175
+ assert_operator Post.count, :>, 0
176
+ assert_operator Author.count, :>, 0
177
+ assert_operator AuthorAddress.count, :>, 0
178
+
179
+ @connection.truncate_tables("author_addresses", "authors", "posts")
180
+
181
+ assert_equal 0, Post.count
182
+ assert_equal 0, Author.count
183
+ assert_equal 0, AuthorAddress.count
184
+ ensure
185
+ reset_fixtures("posts", "authors", "author_addresses")
186
+ @connection.disable_query_cache!
55
187
 
188
+ # Restore foreign key constraint.
189
+ @connection.add_foreign_key :authors, :author_addresses
190
+ end
191
+ end
192
+ end
56
193
 
57
- require 'models/topic'
194
+ require "models/topic"
58
195
  class AttributeMethodsTest < ActiveRecord::TestCase
196
+ # Use IFF for boolean statement in SELECT
59
197
  coerce_tests! %r{typecast attribute from select to false}
60
198
  def test_typecast_attribute_from_select_to_false_coerced
61
- Topic.create(:title => 'Budget')
199
+ Topic.create(:title => "Budget")
62
200
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
63
- assert !topic.is_test?
201
+ assert_not_predicate topic, :is_test?
64
202
  end
65
203
 
204
+ # Use IFF for boolean statement in SELECT
66
205
  coerce_tests! %r{typecast attribute from select to true}
67
206
  def test_typecast_attribute_from_select_to_true_coerced
68
- Topic.create(:title => 'Budget')
207
+ Topic.create(:title => "Budget")
69
208
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
70
- assert topic.is_test?
209
+ assert_predicate topic, :is_test?
71
210
  end
72
211
  end
73
212
 
74
-
75
- class NumericDataTest < ActiveRecord::TestCase
76
- # We do not have do the DecimalWithoutScale type.
77
- coerce_tests! :test_numeric_fields
78
- coerce_tests! :test_numeric_fields_with_scale
79
- end
80
-
81
213
  class BasicsTest < ActiveRecord::TestCase
214
+ # Use square brackets as SQL Server escaped character
82
215
  coerce_tests! :test_column_names_are_escaped
83
216
  def test_column_names_are_escaped_coerced
84
217
  conn = ActiveRecord::Base.connection
85
- assert_equal '[t]]]', conn.quote_column_name('t]')
218
+ assert_equal "[t]]]", conn.quote_column_name("t]")
86
219
  end
87
220
 
88
221
  # Just like PostgreSQLAdapter does.
@@ -96,52 +229,60 @@ class BasicsTest < ActiveRecord::TestCase
96
229
  Time.use_zone("Eastern Time (US & Canada)") do
97
230
  topic = Topic.find(1)
98
231
  time = Time.zone.parse("2017-07-17 10:56")
99
- topic.update_attributes!(written_on: time)
232
+ topic.update!(written_on: time)
100
233
  assert_equal(time, topic.written_on)
101
234
  end
102
235
  end
103
236
 
104
237
  def test_update_date_time_attributes_with_default_timezone_local
105
- with_env_tz 'America/New_York' do
238
+ with_env_tz "America/New_York" do
106
239
  with_timezone_config default: :local do
107
240
  Time.use_zone("Eastern Time (US & Canada)") do
108
241
  topic = Topic.find(1)
109
242
  time = Time.zone.parse("2017-07-17 10:56")
110
- topic.update_attributes!(written_on: time)
243
+ topic.update!(written_on: time)
111
244
  assert_equal(time, topic.written_on)
112
245
  end
113
246
  end
114
247
  end
115
248
  end
116
249
 
117
- # Need to escape `quoted_id` once it contains brackets
118
- coerce_tests! %r{column names are quoted when using #from clause and model has ignored columns}
119
- test "column names are quoted when using #from clause and model has ignored columns coerced" do
120
- refute_empty Developer.ignored_columns
121
- query = Developer.from("developers").to_sql
122
- quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}"
123
-
124
- assert_match(/SELECT #{Regexp.escape(quoted_id)}.* FROM developers/, query)
250
+ # SQL Server does not have query for release_savepoint
251
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
252
+ test "an empty transaction does not raise if preventing writes coerced" do
253
+ ActiveRecord::Base.while_preventing_writes do
254
+ assert_queries(1, ignore_none: true) do
255
+ Bird.transaction do
256
+ ActiveRecord::Base.connection.materialize_transactions
257
+ end
258
+ end
259
+ end
125
260
  end
126
261
  end
127
262
 
128
-
129
-
130
-
131
263
  class BelongsToAssociationsTest < ActiveRecord::TestCase
132
264
  # Since @client.firm is a single first/top, and we use FETCH the order clause is used.
133
265
  coerce_tests! :test_belongs_to_does_not_use_order_by
134
266
 
267
+ # Square brackets around column name
135
268
  coerce_tests! :test_belongs_to_with_primary_key_joins_on_correct_column
136
269
  def test_belongs_to_with_primary_key_joins_on_correct_column_coerced
137
270
  sql = Client.joins(:firm_with_primary_key).to_sql
138
271
  assert_no_match(/\[firm_with_primary_keys_companies\]\.\[id\]/, sql)
139
272
  assert_match(/\[firm_with_primary_keys_companies\]\.\[name\]/, sql)
140
273
  end
141
- end
142
-
143
-
144
274
 
275
+ # Asserted SQL to get one row different from original test.
276
+ coerce_tests! :test_belongs_to
277
+ def test_belongs_to_coerced
278
+ client = Client.find(3)
279
+ first_firm = companies(:first_firm)
280
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
281
+ assert_equal first_firm, client.firm
282
+ assert_equal first_firm.name, client.firm.name
283
+ end
284
+ end
285
+ end
145
286
 
146
287
  module ActiveRecord
147
288
  class BindParameterTest < ActiveRecord::TestCase
@@ -163,62 +304,203 @@ module ActiveRecord
163
304
  # SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
164
305
  coerce_tests! :test_statement_cache
165
306
  coerce_tests! :test_statement_cache_with_query_cache
307
+ coerce_tests! :test_statement_cache_with_find
166
308
  coerce_tests! :test_statement_cache_with_find_by
167
309
  coerce_tests! :test_statement_cache_with_in_clause
168
310
  coerce_tests! :test_statement_cache_with_sql_string_literal
311
+
312
+ # Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
313
+ coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
314
+ def test_bind_params_to_sql_with_prepared_statements_coerced
315
+ assert_bind_params_to_sql_coerced(prepared: true)
316
+ end
317
+
318
+ def test_bind_params_to_sql_with_unprepared_statements_coerced
319
+ @connection.unprepared_statement do
320
+ assert_bind_params_to_sql_coerced(prepared: false)
321
+ end
322
+ end
323
+
324
+ private
325
+
326
+ def assert_bind_params_to_sql_coerced(prepared:)
327
+ table = Author.quoted_table_name
328
+ pk = "#{table}.#{Author.quoted_primary_key}"
329
+
330
+ # prepared_statements: true
331
+ #
332
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
333
+ #
334
+ # prepared_statements: false
335
+ #
336
+ # SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
337
+ #
338
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
339
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
340
+
341
+ authors = Author.where(id: [1, 2, 3, nil])
342
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
343
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
344
+
345
+ # prepared_statements: true
346
+ #
347
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
348
+ #
349
+ # prepared_statements: false
350
+ #
351
+ # SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
352
+ #
353
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
354
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
355
+
356
+ authors = Author.where(id: [1, 2, 3, 9223372036854775808])
357
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
358
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
359
+ end
169
360
  end
170
361
  end
171
362
 
172
-
173
363
  module ActiveRecord
174
364
  class InstrumentationTest < ActiveRecord::TestCase
175
- # This fails randomly due to schema cache being lost?
365
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
176
366
  coerce_tests! :test_payload_name_on_load
177
367
  def test_payload_name_on_load_coerced
178
- Book.create(name: "test book")
179
- Book.first
180
- subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
181
- event = ActiveSupport::Notifications::Event.new(*args)
182
- if event.payload[:sql].match "SELECT"
183
- assert_equal "Book Load", event.payload[:name]
184
- end
185
- end
186
- Book.first
187
- ensure
188
- ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
368
+ Book.send(:load_schema!)
369
+ original_test_payload_name_on_load
189
370
  end
190
371
  end
191
372
  end
192
373
 
193
374
  class CalculationsTest < ActiveRecord::TestCase
194
- # This fails randomly due to schema cache being lost?
375
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
195
376
  coerce_tests! :test_offset_is_kept
196
377
  def test_offset_is_kept_coerced
197
- Account.first
198
- queries = assert_sql { Account.offset(1).count }
199
- assert_equal 1, queries.length
200
- assert_match(/OFFSET/, queries.first)
378
+ Account.send(:load_schema!)
379
+ original_test_offset_is_kept
201
380
  end
202
381
 
203
- # Are decimal, not integer.
382
+ # The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
204
383
  coerce_tests! :test_should_return_decimal_average_of_integer_field
205
384
  def test_should_return_decimal_average_of_integer_field_coerced
206
385
  value = Account.average(:id)
207
- assert_equal BigDecimal('3.0').to_s, BigDecimal(value).to_s
386
+ assert_equal 3, value
387
+ end
388
+
389
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
390
+ # Match SQL Server limit implementation.
391
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
392
+ def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
393
+ rails_core = companies(:rails_core)
394
+
395
+ sql = <<~SQL
396
+ SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
397
+ FROM accounts
398
+ WHERE firm_id = ?
399
+ GROUP BY firm_id
400
+ ORDER BY firm_id
401
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
402
+ SQL
403
+
404
+ account = Account.find_by_sql([sql, rails_core]).first
405
+
406
+ # id was not selected, so it should be nil
407
+ # (cannot select id because it wasn't used in the GROUP BY clause)
408
+ assert_nil account.id
409
+
410
+ # firm_id was explicitly selected, so it should be present
411
+ assert_equal(rails_core, account.firm)
412
+
413
+ # avg_credit_limit should be present as a virtual attribute
414
+ assert_equal(52.5, account.avg_credit_limit)
415
+ end
416
+
417
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
418
+ # Order column must be in the GROUP clause.
419
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
420
+ def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
421
+ rails_core = companies(:rails_core)
422
+
423
+ account = Account
424
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
425
+ .where(firm: rails_core)
426
+ .group(:firm_id)
427
+ .order(:firm_id)
428
+ .take!
429
+
430
+ # id was not selected, so it should be nil
431
+ # (cannot select id because it wasn't used in the GROUP BY clause)
432
+ assert_nil account.id
433
+
434
+ # firm_id was explicitly selected, so it should be present
435
+ assert_equal(rails_core, account.firm)
436
+
437
+ # avg_credit_limit should be present as a virtual attribute
438
+ assert_equal(52.5, account.avg_credit_limit)
439
+ end
440
+
441
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
442
+ # SELECT columns must be in the GROUP clause.
443
+ # Match SQL Server limit implementation.
444
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
445
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
446
+ rails_core = companies(:rails_core)
447
+
448
+ sql = <<~SQL
449
+ SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
450
+ FROM companies
451
+ INNER JOIN accounts ON companies.id = accounts.firm_id
452
+ WHERE companies.id = ?
453
+ GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description
454
+ ORDER BY companies.id
455
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
456
+ SQL
457
+
458
+ firm = DependentFirm.find_by_sql([sql, rails_core]).first
459
+
460
+ # all the DependentFirm attributes should be present
461
+ assert_equal rails_core, firm
462
+ assert_equal rails_core.name, firm.name
463
+
464
+ # avg_credit_limit should be present as a virtual attribute
465
+ assert_equal(52.5, firm.avg_credit_limit)
466
+ end
467
+
468
+
469
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
470
+ # SELECT columns must be in the GROUP clause.
471
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
472
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
473
+ rails_core = companies(:rails_core)
474
+
475
+ firm = DependentFirm
476
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
477
+ .where(id: rails_core)
478
+ .joins(:account)
479
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description)
480
+ .take!
481
+
482
+ # all the DependentFirm attributes should be present
483
+ assert_equal rails_core, firm
484
+ assert_equal rails_core.name, firm.name
485
+
486
+ # avg_credit_limit should be present as a virtual attribute
487
+ assert_equal(52.5, firm.avg_credit_limit)
208
488
  end
209
489
 
490
+ # Match SQL Server limit implementation
210
491
  coerce_tests! :test_limit_is_kept
211
492
  def test_limit_is_kept_coerced
212
493
  queries = capture_sql_ss { Account.limit(1).count }
213
494
  assert_equal 1, queries.length
214
- _(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1}
495
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
215
496
  end
216
497
 
498
+ # Match SQL Server limit implementation
217
499
  coerce_tests! :test_limit_with_offset_is_kept
218
500
  def test_limit_with_offset_is_kept_coerced
219
501
  queries = capture_sql_ss { Account.limit(1).offset(1).count }
220
502
  assert_equal 1, queries.length
221
- _(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1}
503
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
222
504
  end
223
505
 
224
506
  # SQL Server needs an alias for the calculated column
@@ -233,49 +515,65 @@ class CalculationsTest < ActiveRecord::TestCase
233
515
  coerce_tests! :test_having_with_strong_parameters
234
516
  end
235
517
 
236
-
237
-
238
518
  module ActiveRecord
239
519
  class Migration
240
520
  class ChangeSchemaTest < ActiveRecord::TestCase
241
- # We test these.
242
- coerce_tests! :test_create_table_with_bigint,
243
- :test_create_table_with_defaults
244
- end
245
-
246
- class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
247
- # In SQL Server you have to delete the tables yourself in the right order.
248
- coerce_tests! :test_create_table_with_force_cascade_drops_dependent_objects
249
- end
250
- end
251
- end
521
+ # Integer.default is a number and not a string
522
+ coerce_tests! :test_create_table_with_defaults
523
+ def test_create_table_with_defaults_coerce
524
+ connection.create_table :testings do |t|
525
+ t.column :one, :string, default: "hello"
526
+ t.column :two, :boolean, default: true
527
+ t.column :three, :boolean, default: false
528
+ t.column :four, :integer, default: 1
529
+ t.column :five, :text, default: "hello"
530
+ end
252
531
 
532
+ columns = connection.columns(:testings)
533
+ one = columns.detect { |c| c.name == "one" }
534
+ two = columns.detect { |c| c.name == "two" }
535
+ three = columns.detect { |c| c.name == "three" }
536
+ four = columns.detect { |c| c.name == "four" }
537
+ five = columns.detect { |c| c.name == "five" }
538
+
539
+ assert_equal "hello", one.default
540
+ assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
541
+ assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
542
+ assert_equal 1, four.default
543
+ assert_equal "hello", five.default
544
+ end
253
545
 
546
+ # Rails adds precision 6 by default, sql server uses datetime2 for datetimes with precision
547
+ coerce_tests! :test_add_column_with_postgresql_datetime_type
548
+ def test_add_column_with_postgresql_datetime_type_coerced
549
+ connection.create_table :testings do |t|
550
+ t.column :foo, :datetime
551
+ end
254
552
 
255
- module ActiveRecord
256
- module ConnectionAdapters
257
- class QuoteARBaseTest < ActiveRecord::TestCase
553
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
258
554
 
259
- # Use our date format.
260
- coerce_tests! :test_quote_ar_object
261
- def test_quote_ar_object_coerced
262
- value = DatetimePrimaryKey.new(id: @time)
263
- assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
555
+ assert_equal :datetime, column.type
556
+ assert_equal "datetime2(6)", column.sql_type
264
557
  end
265
558
 
266
- # Use our date format.
267
- coerce_tests! :test_type_cast_ar_object
268
- def test_type_cast_ar_object_coerced
269
- value = DatetimePrimaryKey.new(id: @time)
270
- assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
271
- end
559
+ # timestamp is datetime with default limit
560
+ coerce_tests! :test_change_column_with_timestamp_type
561
+ def test_change_column_with_timestamp_type_coerced
562
+ connection.create_table :testings do |t|
563
+ t.column :foo, :datetime, null: false
564
+ end
565
+
566
+ connection.change_column :testings, :foo, :timestamp
567
+
568
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
272
569
 
570
+ assert_equal :datetime, column.type
571
+ assert_equal "datetime", column.sql_type
572
+ end
273
573
  end
274
574
  end
275
575
  end
276
576
 
277
-
278
-
279
577
  module ActiveRecord
280
578
  class Migration
281
579
  class ColumnAttributesTest < ActiveRecord::TestCase
@@ -290,16 +588,13 @@ module ActiveRecord
290
588
  end
291
589
  end
292
590
 
293
-
294
-
295
-
296
591
  module ActiveRecord
297
592
  class Migration
298
- class ColumnsTest
593
+ class ColumnsTest < ActiveRecord::TestCase
299
594
  # Our defaults are real 70000 integers vs '70000' strings.
300
595
  coerce_tests! :test_rename_column_preserves_default_value_not_null
301
596
  def test_rename_column_preserves_default_value_not_null_coerced
302
- add_column 'test_models', 'salary', :integer, :default => 70000
597
+ add_column "test_models", "salary", :integer, :default => 70000
303
598
  default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
304
599
  assert_equal 70000, default_before
305
600
  rename_column "test_models", "salary", "annual_salary"
@@ -315,9 +610,9 @@ module ActiveRecord
315
610
  add_column "test_models", :hat_size, :integer
316
611
  add_column "test_models", :hat_style, :string, :limit => 100
317
612
  add_index "test_models", ["hat_style", "hat_size"], :unique => true
318
- assert_equal 1, connection.indexes('test_models').size
613
+ assert_equal 1, connection.indexes("test_models").size
319
614
  remove_column("test_models", "hat_size")
320
- assert_equal [], connection.indexes('test_models').map(&:name)
615
+ assert_equal [], connection.indexes("test_models").map(&:name)
321
616
  end
322
617
 
323
618
  # Choose `StatementInvalid` vs `ActiveRecordError`.
@@ -332,61 +627,49 @@ module ActiveRecord
332
627
  end
333
628
  end
334
629
 
335
-
336
-
337
-
338
630
  class MigrationTest < ActiveRecord::TestCase
339
- # We do not have do the DecimalWithoutScale type.
340
- coerce_tests! :test_add_table_with_decimals
341
- def test_add_table_with_decimals_coerced
342
- Person.connection.drop_table :big_numbers rescue nil
343
- assert !BigNumber.table_exists?
344
- GiveMeBigNumbers.up
345
- BigNumber.reset_column_information
346
- assert BigNumber.create(
347
- :bank_balance => 1586.43,
348
- :big_bank_balance => BigDecimal("1000234000567.95"),
349
- :world_population => 6000000000,
350
- :my_house_population => 3,
351
- :value_of_e => BigDecimal("2.7182818284590452353602875")
352
- )
353
- b = BigNumber.first
354
- assert_not_nil b
355
- assert_not_nil b.bank_balance
356
- assert_not_nil b.big_bank_balance
357
- assert_not_nil b.world_population
358
- assert_not_nil b.my_house_population
359
- assert_not_nil b.value_of_e
360
- assert_kind_of BigDecimal, b.world_population
361
- assert_equal '6000000000.0', b.world_population.to_s
362
- assert_kind_of Integer, b.my_house_population
363
- assert_equal 3, b.my_house_population
364
- assert_kind_of BigDecimal, b.bank_balance
365
- assert_equal BigDecimal("1586.43"), b.bank_balance
366
- assert_kind_of BigDecimal, b.big_bank_balance
367
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
368
- GiveMeBigNumbers.down
369
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
370
- end
371
-
372
631
  # For some reason our tests set Rails.@_env which breaks test env switching.
373
632
  coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
374
633
  coerce_tests! :test_internal_metadata_stores_environment
375
- end
376
634
 
635
+ # Same as original but using binary type instead of blob
636
+ coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
637
+ def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
638
+ migration_a = Class.new(ActiveRecord::Migration::Current) {
639
+ def version; 100 end
640
+ def migrate(x)
641
+ add_column "people", "last_name", :binary
642
+ end
643
+ }.new
644
+
645
+ migration_b = Class.new(ActiveRecord::Migration::Current) {
646
+ def version; 101 end
647
+ def migrate(x)
648
+ add_column "people", "last_name", :binary, if_not_exists: true
649
+ end
650
+ }.new
377
651
 
652
+ ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, 100).migrate
653
+ assert_column Person, :last_name, "migration_a should have created the last_name column on people"
378
654
 
655
+ assert_nothing_raised do
656
+ ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, 101).migrate
657
+ end
658
+ ensure
659
+ Person.reset_column_information
660
+ if Person.column_names.include?("last_name")
661
+ Person.connection.remove_column("people", "last_name")
662
+ end
663
+ end
664
+ end
379
665
 
380
666
  class CoreTest < ActiveRecord::TestCase
381
- # I think fixtures are useing the wrong time zone and the `:first`
667
+ # I think fixtures are using the wrong time zone and the `:first`
382
668
  # `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
383
669
  # getting local EST time for me and set to "09:28:00.0000000".
384
670
  coerce_tests! :test_pretty_print_persisted
385
671
  end
386
672
 
387
-
388
-
389
-
390
673
  module ActiveRecord
391
674
  module ConnectionAdapters
392
675
  # Just like PostgreSQLAdapter does.
@@ -400,27 +683,147 @@ module ActiveRecord
400
683
  end
401
684
  end
402
685
 
686
+ module ActiveRecord
687
+ # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
688
+ module DatabaseTasksSetupper
689
+ def setup
690
+ @sqlserver_tasks =
691
+ Class.new do
692
+ def create; end
693
+
694
+ def drop; end
403
695
 
696
+ def purge; end
404
697
 
698
+ def charset; end
405
699
 
406
- module ActiveRecord
407
- class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
408
- # Skip this test with /tmp/my_schema_cache.yml path on Windows.
409
- coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
700
+ def collation; end
701
+
702
+ def structure_dump(*); end
703
+
704
+ def structure_load(*); end
705
+ end.new
706
+
707
+ $stdout, @original_stdout = StringIO.new, $stdout
708
+ $stderr, @original_stderr = StringIO.new, $stderr
709
+ end
710
+
711
+ def with_stubbed_new
712
+ ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
713
+ yield
714
+ end
715
+ end
716
+ end
717
+
718
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
719
+ # Coerce PostgreSQL/SQLite/MySQL tests.
720
+ coerce_all_tests!
721
+
722
+ def test_sqlserver_create
723
+ with_stubbed_new do
724
+ assert_called(eval("@sqlserver_tasks"), :create) do
725
+ ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
726
+ end
727
+ end
728
+ end
729
+ end
730
+
731
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
732
+ # Coerce PostgreSQL/SQLite/MySQL tests.
733
+ coerce_all_tests!
734
+
735
+ def test_sqlserver_drop
736
+ with_stubbed_new do
737
+ assert_called(eval("@sqlserver_tasks"), :drop) do
738
+ ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
739
+ end
740
+ end
741
+ end
742
+ end
743
+
744
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
745
+ # Coerce PostgreSQL/SQLite/MySQL tests.
746
+ coerce_all_tests!
747
+
748
+ def test_sqlserver_purge
749
+ with_stubbed_new do
750
+ assert_called(eval("@sqlserver_tasks"), :purge) do
751
+ ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
752
+ end
753
+ end
754
+ end
755
+ end
756
+
757
+ class DatabaseTasksCharsetTest < ActiveRecord::TestCase
758
+ # Coerce PostgreSQL/SQLite/MySQL tests.
759
+ coerce_all_tests!
760
+
761
+ def test_sqlserver_charset
762
+ with_stubbed_new do
763
+ assert_called(eval("@sqlserver_tasks"), :charset) do
764
+ ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
765
+ end
766
+ end
767
+ end
768
+ end
769
+
770
+ class DatabaseTasksCollationTest < ActiveRecord::TestCase
771
+ # Coerce PostgreSQL/SQLite/MySQL tests.
772
+ coerce_all_tests!
773
+
774
+ def test_sqlserver_collation
775
+ with_stubbed_new do
776
+ assert_called(eval("@sqlserver_tasks"), :collation) do
777
+ ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
778
+ end
779
+ end
780
+ end
781
+ end
782
+
783
+ class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
784
+ # Coerce PostgreSQL/SQLite/MySQL tests.
785
+ coerce_all_tests!
786
+
787
+ def test_sqlserver_structure_dump
788
+ with_stubbed_new do
789
+ assert_called_with(
790
+ eval("@sqlserver_tasks"), :structure_dump,
791
+ ["awesome-file.sql", nil]
792
+ ) do
793
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
794
+ end
795
+ end
796
+ end
797
+ end
798
+
799
+ class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
800
+ # Coerce PostgreSQL/SQLite/MySQL tests.
801
+ coerce_all_tests!
802
+
803
+ def test_sqlserver_structure_load
804
+ with_stubbed_new do
805
+ assert_called_with(
806
+ eval("@sqlserver_tasks"),
807
+ :structure_load,
808
+ ["awesome-file.sql", nil]
809
+ ) do
810
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
811
+ end
812
+ end
813
+ end
410
814
  end
815
+
411
816
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
412
817
  # We extend `local_database?` so that common VM IPs can be used.
413
818
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
414
819
  end
820
+
415
821
  class DatabaseTasksDropAllTest < ActiveRecord::TestCase
416
822
  # We extend `local_database?` so that common VM IPs can be used.
417
823
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
418
824
  end
419
825
  end
420
826
 
421
-
422
-
423
-
424
827
  class DefaultScopingTest < ActiveRecord::TestCase
425
828
  # We are not doing order duplicate removal anymore.
426
829
  coerce_tests! :test_order_in_default_scope_should_not_prevail
@@ -434,16 +837,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
434
837
  end
435
838
  end
436
839
 
437
-
438
-
439
-
440
- require 'models/post'
441
- require 'models/subscriber'
840
+ require "models/post"
841
+ require "models/subscriber"
442
842
  class EachTest < ActiveRecord::TestCase
443
843
  # Quoting in tests does not cope with bracket quoting.
444
844
  coerce_tests! :test_find_in_batches_should_quote_batch_order
445
845
  def test_find_in_batches_should_quote_batch_order_coerced
446
- c = Post.connection
846
+ Post.connection
447
847
  assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
448
848
  Post.find_in_batches(:batch_size => 1) do |batch|
449
849
  assert_kind_of Array, batch
@@ -455,7 +855,7 @@ class EachTest < ActiveRecord::TestCase
455
855
  # Quoting in tests does not cope with bracket quoting.
456
856
  coerce_tests! :test_in_batches_should_quote_batch_order
457
857
  def test_in_batches_should_quote_batch_order_coerced
458
- c = Post.connection
858
+ Post.connection
459
859
  assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
460
860
  Post.in_batches(of: 1) do |relation|
461
861
  assert_kind_of ActiveRecord::Relation, relation
@@ -465,9 +865,6 @@ class EachTest < ActiveRecord::TestCase
465
865
  end
466
866
  end
467
867
 
468
-
469
-
470
-
471
868
  class EagerAssociationTest < ActiveRecord::TestCase
472
869
  # Use LEN() vs length() function.
473
870
  coerce_tests! :test_count_with_include
@@ -479,14 +876,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
479
876
  coerce_tests! %r{including association based on sql condition and no database column}
480
877
  end
481
878
 
482
-
483
-
484
-
485
- require 'models/topic'
879
+ require "models/topic"
880
+ require "models/customer"
881
+ require "models/non_primary_key"
486
882
  class FinderTest < ActiveRecord::TestCase
883
+ fixtures :customers, :topics, :authors
884
+
885
+ # We have implicit ordering, via FETCH.
487
886
  coerce_tests! %r{doesn't have implicit ordering},
488
- :test_find_doesnt_have_implicit_ordering # We have implicit ordering, via FETCH.
887
+ :test_find_doesnt_have_implicit_ordering
489
888
 
889
+ # Square brackets around column name
490
890
  coerce_tests! :test_exists_does_not_select_columns_without_alias
491
891
  def test_exists_does_not_select_columns_without_alias_coerced
492
892
  assert_sql(/SELECT\s+1 AS one FROM \[topics\].*OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) do
@@ -494,6 +894,7 @@ class FinderTest < ActiveRecord::TestCase
494
894
  end
495
895
  end
496
896
 
897
+ # Assert SQL Server limit implementation
497
898
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
498
899
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
499
900
  assert_sql(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
@@ -507,7 +908,7 @@ class FinderTest < ActiveRecord::TestCase
507
908
  # Can not use array condition due to not finding right type and hence fractional second quoting.
508
909
  coerce_tests! :test_condition_utc_time_interpolation_with_default_timezone_local
509
910
  def test_condition_utc_time_interpolation_with_default_timezone_local_coerced
510
- with_env_tz 'America/New_York' do
911
+ with_env_tz "America/New_York" do
511
912
  with_timezone_config default: :local do
512
913
  topic = Topic.first
513
914
  assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
@@ -518,17 +919,95 @@ class FinderTest < ActiveRecord::TestCase
518
919
  # Can not use array condition due to not finding right type and hence fractional second quoting.
519
920
  coerce_tests! :test_condition_local_time_interpolation_with_default_timezone_utc
520
921
  def test_condition_local_time_interpolation_with_default_timezone_utc_coerced
521
- with_env_tz 'America/New_York' do
922
+ with_env_tz "America/New_York" do
522
923
  with_timezone_config default: :utc do
523
924
  topic = Topic.first
524
925
  assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
525
926
  end
526
927
  end
527
928
  end
528
- end
529
929
 
930
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
931
+ coerce_tests! :test_include_on_unloaded_relation_with_match
932
+ def test_include_on_unloaded_relation_with_match_coerced
933
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
934
+ assert_equal true, Customer.where(name: "David").include?(customers(:david))
935
+ end
936
+ end
937
+
938
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
939
+ coerce_tests! :test_include_on_unloaded_relation_without_match
940
+ def test_include_on_unloaded_relation_without_match_coerced
941
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
942
+ assert_equal false, Customer.where(name: "David").include?(customers(:mary))
943
+ end
944
+ end
945
+
946
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
947
+ coerce_tests! :test_member_on_unloaded_relation_with_match
948
+ def test_member_on_unloaded_relation_with_match_coerced
949
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
950
+ assert_equal true, Customer.where(name: "David").member?(customers(:david))
951
+ end
952
+ end
953
+
954
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
955
+ coerce_tests! :test_member_on_unloaded_relation_without_match
956
+ def test_member_on_unloaded_relation_without_match_coerced
957
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
958
+ assert_equal false, Customer.where(name: "David").member?(customers(:mary))
959
+ end
960
+ end
961
+
962
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
963
+ coerce_tests! :test_implicit_order_column_is_configurable
964
+ def test_implicit_order_column_is_configurable_coerced
965
+ old_implicit_order_column = Topic.implicit_order_column
966
+ Topic.implicit_order_column = "title"
967
+
968
+ assert_equal topics(:fifth), Topic.first
969
+ assert_equal topics(:third), Topic.last
970
+
971
+ c = Topic.connection
972
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
973
+ Topic.last
974
+ }
975
+ ensure
976
+ Topic.implicit_order_column = old_implicit_order_column
977
+ end
978
+
979
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
980
+ coerce_tests! :test_implicit_order_set_to_primary_key
981
+ def test_implicit_order_set_to_primary_key_coerced
982
+ old_implicit_order_column = Topic.implicit_order_column
983
+ Topic.implicit_order_column = "id"
984
+
985
+ c = Topic.connection
986
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
987
+ Topic.last
988
+ }
989
+ ensure
990
+ Topic.implicit_order_column = old_implicit_order_column
991
+ end
992
+
993
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
994
+ coerce_tests! :test_implicit_order_for_model_without_primary_key
995
+ def test_implicit_order_for_model_without_primary_key_coerced
996
+ old_implicit_order_column = NonPrimaryKey.implicit_order_column
997
+ NonPrimaryKey.implicit_order_column = "created_at"
998
+
999
+ c = NonPrimaryKey.connection
530
1000
 
1001
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
1002
+ NonPrimaryKey.last
1003
+ }
1004
+ ensure
1005
+ NonPrimaryKey.implicit_order_column = old_implicit_order_column
1006
+ end
531
1007
 
1008
+ # SQL Server is unable to use aliased SELECT in the HAVING clause.
1009
+ coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
1010
+ end
532
1011
 
533
1012
  module ActiveRecord
534
1013
  class Migration
@@ -543,94 +1022,120 @@ module ActiveRecord
543
1022
  @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :restrict
544
1023
  end
545
1024
  end
546
- end
547
- end
548
- end
549
1025
 
1026
+ # Error message depends on the database adapter.
1027
+ coerce_tests! :test_add_foreign_key_with_if_not_exists_not_set
1028
+ def test_add_foreign_key_with_if_not_exists_not_set_coerced
1029
+ @connection.add_foreign_key :astronauts, :rockets
1030
+ assert_equal 1, @connection.foreign_keys("astronauts").size
550
1031
 
1032
+ error = assert_raises do
1033
+ @connection.add_foreign_key :astronauts, :rockets
1034
+ end
551
1035
 
1036
+ assert_match(/TinyTds::Error: There is already an object named '.*' in the database/, error.message)
1037
+ end
1038
+ end
1039
+ end
1040
+ end
552
1041
 
553
1042
  class HasOneAssociationsTest < ActiveRecord::TestCase
554
1043
  # We use OFFSET/FETCH vs TOP. So we always have an order.
555
1044
  coerce_tests! :test_has_one_does_not_use_order_by
556
- end
557
-
558
1045
 
1046
+ # Asserted SQL to get one row different from original test.
1047
+ coerce_tests! :test_has_one
1048
+ def test_has_one_coerced
1049
+ firm = companies(:first_firm)
1050
+ first_account = Account.find(1)
1051
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1052
+ assert_equal first_account, firm.account
1053
+ assert_equal first_account.credit_limit, firm.account.credit_limit
1054
+ end
1055
+ end
1056
+ end
559
1057
 
1058
+ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1059
+ # Asserted SQL to get one row different from original test.
1060
+ coerce_tests! :test_has_one_through_executes_limited_query
1061
+ def test_has_one_through_executes_limited_query_coerced
1062
+ boring_club = clubs(:boring_club)
1063
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1064
+ assert_equal boring_club, @member.general_club
1065
+ end
1066
+ end
1067
+ end
560
1068
 
561
- require 'models/company'
1069
+ require "models/company"
562
1070
  class InheritanceTest < ActiveRecord::TestCase
1071
+ # Rails test required inserting to a identity column.
563
1072
  coerce_tests! :test_a_bad_type_column
564
1073
  def test_a_bad_type_column_coerced
565
- Company.connection.with_identity_insert_enabled('companies') do
1074
+ Company.connection.with_identity_insert_enabled("companies") do
566
1075
  Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
567
1076
  end
568
1077
  assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
569
1078
  end
570
1079
 
1080
+ # Use Square brackets around column name
571
1081
  coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
572
1082
  def test_eager_load_belongs_to_primary_key_quoting_coerced
573
- con = Account.connection
1083
+ Account.connection
574
1084
  assert_sql(/\[companies\]\.\[id\] = @0.* @0 = 1/) do
575
1085
  Account.all.merge!(:includes => :firm).find(1)
576
1086
  end
577
1087
  end
578
1088
  end
579
1089
 
580
-
581
-
582
-
583
1090
  class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
584
1091
  # Uses || operator in SQL. Just trust core gets value out of this test.
585
1092
  coerce_tests! :test_does_not_override_select
586
1093
  end
587
1094
 
588
-
589
-
590
-
591
- class NamedScopingTest < ActiveRecord::TestCase
592
- # This works now because we add an `order(:id)` sort to break the order tie for deterministic results.
593
- coerce_tests! :test_scopes_honor_current_scopes_from_when_defined
594
- def test_scopes_honor_current_scopes_from_when_defined_coerced
595
- assert !Post.ranked_by_comments.order(:id).limit_by(5).empty?
596
- assert !authors(:david).posts.ranked_by_comments.order(:id).limit_by(5).empty?
597
- assert_not_equal Post.ranked_by_comments.order(:id).limit_by(5), authors(:david).posts.ranked_by_comments.order(:id).limit_by(5)
598
- assert_not_equal Post.order(:id).top(5), authors(:david).posts.order(:id).top(5)
599
- # Oracle sometimes sorts differently if WHERE condition is changed
600
- assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id)
601
- assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5)
602
- end
603
- end
604
-
605
-
606
-
607
-
608
- require 'models/developer'
609
- require 'models/computer'
1095
+ require "models/developer"
1096
+ require "models/computer"
610
1097
  class NestedRelationScopingTest < ActiveRecord::TestCase
1098
+ # Assert SQL Server limit implementation
611
1099
  coerce_tests! :test_merge_options
612
1100
  def test_merge_options_coerced
613
- Developer.where('salary = 80000').scoping do
1101
+ Developer.where("salary = 80000").scoping do
614
1102
  Developer.limit(10).scoping do
615
1103
  devs = Developer.all
616
1104
  sql = devs.to_sql
617
- assert_match '(salary = 80000)', sql
618
- assert_match 'FETCH NEXT 10 ROWS ONLY', sql
1105
+ assert_match "(salary = 80000)", sql
1106
+ assert_match "FETCH NEXT 10 ROWS ONLY", sql
619
1107
  end
620
1108
  end
621
1109
  end
622
1110
  end
623
1111
 
1112
+ require "models/topic"
1113
+ class PersistenceTest < ActiveRecord::TestCase
1114
+ # Rails test required updating a identity column.
1115
+ coerce_tests! :test_update_columns_changing_id
624
1116
 
1117
+ # Rails test required updating a identity column.
1118
+ coerce_tests! :test_update
1119
+ def test_update_coerced
1120
+ topic = Topic.find(1)
1121
+ assert_not_predicate topic, :approved?
1122
+ assert_equal "The First Topic", topic.title
625
1123
 
1124
+ topic.update("approved" => true, "title" => "The First Topic Updated")
1125
+ topic.reload
1126
+ assert_predicate topic, :approved?
1127
+ assert_equal "The First Topic Updated", topic.title
626
1128
 
627
- require 'models/parrot'
628
- require 'models/topic'
629
- class PersistenceTest < ActiveRecord::TestCase
630
- # We can not UPDATE identity columns.
631
- coerce_tests! :test_update_columns_changing_id
1129
+ topic.update(approved: false, title: "The First Topic")
1130
+ topic.reload
1131
+ assert_not_predicate topic, :approved?
1132
+ assert_equal "The First Topic", topic.title
1133
+ end
1134
+ end
632
1135
 
633
- # Previous test required updating a identity column.
1136
+ require "models/author"
1137
+ class UpdateAllTest < ActiveRecord::TestCase
1138
+ # Rails test required updating a identity column.
634
1139
  coerce_tests! :test_update_all_doesnt_ignore_order
635
1140
  def test_update_all_doesnt_ignore_order_coerced
636
1141
  david, mary = authors(:david), authors(:mary)
@@ -638,81 +1143,112 @@ class PersistenceTest < ActiveRecord::TestCase
638
1143
  _(mary.id).must_equal 2
639
1144
  _(david.name).wont_equal mary.name
640
1145
  assert_sql(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
641
- Author.where('[id] > 1').order(:id).update_all(name: 'Test')
1146
+ Author.where("[id] > 1").order(:id).update_all(name: "Test")
642
1147
  end
643
- _(david.reload.name).must_equal 'David'
644
- _(mary.reload.name).must_equal 'Test'
1148
+ _(david.reload.name).must_equal "David"
1149
+ _(mary.reload.name).must_equal "Test"
645
1150
  end
646
1151
 
647
- # We can not UPDATE identity columns.
648
- coerce_tests! :test_update_attributes
649
- def test_update_attributes_coerced
650
- topic = Topic.find(1)
651
- assert !topic.approved?
652
- assert_equal "The First Topic", topic.title
653
- topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
654
- topic.reload
655
- assert topic.approved?
656
- assert_equal "The First Topic Updated", topic.title
657
- topic.update_attributes(approved: false, title: "The First Topic")
658
- topic.reload
659
- assert !topic.approved?
660
- assert_equal "The First Topic", topic.title
661
- # SQLServer: Here is where it breaks down. No exceptions.
662
- # assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
663
- # topic.update_attributes(id: 3, title: "Hm is it possible?")
664
- # end
665
- # assert_not_equal "Hm is it possible?", Topic.find(3).title
666
- # topic.update_attributes(id: 1234)
667
- # assert_nothing_raised { topic.reload }
668
- # assert_equal topic.title, Topic.find(1234).title
1152
+ # SELECT columns must be in the GROUP clause.
1153
+ coerce_tests! :test_update_all_with_group_by
1154
+ def test_update_all_with_group_by_coerced
1155
+ minimum_comments_count = 2
1156
+
1157
+ Post.most_commented(minimum_comments_count).update_all(title: "ig")
1158
+ posts = Post.select(:id, :title).group(:title).most_commented(minimum_comments_count).all.to_a
1159
+
1160
+ assert_operator posts.length, :>, 0
1161
+ assert posts.all? { |post| post.comments.length >= minimum_comments_count }
1162
+ assert posts.all? { |post| "ig" == post.title }
1163
+
1164
+ post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
1165
+ assert_not_equal "ig", post.title
669
1166
  end
670
1167
  end
671
1168
 
1169
+ class DeleteAllTest < ActiveRecord::TestCase
1170
+ # SELECT columns must be in the GROUP clause.
1171
+ coerce_tests! :test_delete_all_with_group_by_and_having
1172
+ def test_delete_all_with_group_by_and_having_coerced
1173
+ minimum_comments_count = 2
1174
+ posts_to_be_deleted = Post.select(:id).most_commented(minimum_comments_count).all.to_a
1175
+ assert_operator posts_to_be_deleted.length, :>, 0
672
1176
 
1177
+ assert_difference("Post.count", -posts_to_be_deleted.length) do
1178
+ Post.most_commented(minimum_comments_count).delete_all
1179
+ end
673
1180
 
1181
+ posts_to_be_deleted.each do |deleted_post|
1182
+ assert_raise(ActiveRecord::RecordNotFound) { deleted_post.reload }
1183
+ end
1184
+ end
1185
+ end
674
1186
 
675
- require 'models/topic'
1187
+ require "models/topic"
676
1188
  module ActiveRecord
677
1189
  class PredicateBuilderTest < ActiveRecord::TestCase
1190
+ # Same as original test except string has `N` prefix to indicate unicode string.
678
1191
  coerce_tests! :test_registering_new_handlers
679
1192
  def test_registering_new_handlers_coerced
680
- Topic.predicate_builder.register_handler(Regexp, proc do |column, value|
681
- Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
682
- end)
683
- assert_match %r{\[topics\].\[title\] ~ rails}i, Topic.where(title: /rails/).to_sql
684
- ensure
685
- Topic.reset_column_information
1193
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Topic.where(title: /rails/).to_sql
1194
+ end
1195
+
1196
+ # Same as original test except string has `N` prefix to indicate unicode string.
1197
+ coerce_tests! :test_registering_new_handlers_for_association
1198
+ def test_registering_new_handlers_for_association_coerced
1199
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
686
1200
  end
687
1201
  end
688
1202
  end
689
1203
 
690
-
691
-
692
-
693
1204
  class PrimaryKeysTest < ActiveRecord::TestCase
694
- # Gonna trust Rails core for this. We end up with 2 querys vs 3 asserted
695
- # but as far as I can tell, this is only one for us anyway.
1205
+ # SQL Server does not have query for release_savepoint
696
1206
  coerce_tests! :test_create_without_primary_key_no_extra_query
1207
+ def test_create_without_primary_key_no_extra_query_coerced
1208
+ klass = Class.new(ActiveRecord::Base) do
1209
+ self.table_name = "dashboards"
1210
+ end
1211
+ klass.create! # warmup schema cache
1212
+ assert_queries(2, ignore_none: true) { klass.create! }
1213
+ end
697
1214
  end
698
1215
 
699
-
700
-
701
-
702
- require 'models/task'
1216
+ require "models/task"
703
1217
  class QueryCacheTest < ActiveRecord::TestCase
1218
+ # SQL Server adapter not in list of supported adapters in original test.
704
1219
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
705
1220
  def test_cache_does_not_wrap_results_in_arrays_coerced
706
1221
  Task.cache do
707
- assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
1222
+ assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
708
1223
  end
709
1224
  end
710
- end
711
1225
 
1226
+ # Same as original test except that we expect one query to be performed to retrieve the table's primary key
1227
+ # and we don't call `reload_type_map` because SQL Server adapter doesn't support it.
1228
+ # When we generate the SQL for the `find` it includes ordering on the primary key. If we reset the column
1229
+ # information then the primary key needs to be retrieved from the database again to generate the SQL causing the
1230
+ # original test's `assert_no_queries` assertion to fail. Assert that the query was to get the primary key.
1231
+ coerce_tests! :test_query_cached_even_when_types_are_reset
1232
+ def test_query_cached_even_when_types_are_reset_coerced
1233
+ Task.cache do
1234
+ # Warm the cache
1235
+ Task.find(1)
712
1236
 
1237
+ # Clear places where type information is cached
1238
+ Task.reset_column_information
1239
+ Task.initialize_find_by_cache
1240
+ Task.define_attribute_methods
713
1241
 
1242
+ assert_queries(1, ignore_none: true) do
1243
+ Task.find(1)
1244
+ end
714
1245
 
715
- require 'models/post'
1246
+ assert_includes ActiveRecord::SQLCounter.log_all.first, "TC.CONSTRAINT_TYPE = N''PRIMARY KEY''"
1247
+ end
1248
+ end
1249
+ end
1250
+
1251
+ require "models/post"
716
1252
  class RelationTest < ActiveRecord::TestCase
717
1253
  # Use LEN vs LENGTH function.
718
1254
  coerce_tests! :test_reverse_order_with_function
@@ -733,6 +1269,28 @@ class RelationTest < ActiveRecord::TestCase
733
1269
  # We have implicit ordering, via FETCH.
734
1270
  coerce_tests! %r{doesn't have implicit ordering}
735
1271
 
1272
+ # We have implicit ordering, via FETCH.
1273
+ coerce_tests! :test_reorder_with_take
1274
+ def test_reorder_with_take_coerced
1275
+ sql_log = capture_sql do
1276
+ assert Post.order(:title).reorder(nil).take
1277
+ end
1278
+ assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1279
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1280
+ end
1281
+
1282
+ # We have implicit ordering, via FETCH.
1283
+ coerce_tests! :test_reorder_with_first
1284
+ def test_reorder_with_first_coerced
1285
+ post = nil
1286
+ sql_log = capture_sql do
1287
+ post = Post.order(:title).reorder(nil).first
1288
+ end
1289
+ assert_equal posts(:welcome), post
1290
+ assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1291
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1292
+ end
1293
+
736
1294
  # We are not doing order duplicate removal anymore.
737
1295
  coerce_tests! :test_order_using_scoping
738
1296
 
@@ -756,17 +1314,17 @@ class RelationTest < ActiveRecord::TestCase
756
1314
  # so we are skipping all together.
757
1315
  coerce_tests! :test_empty_complex_chained_relations
758
1316
 
759
- # Can't apply offset withour ORDER
1317
+ # Can't apply offset without ORDER
760
1318
  coerce_tests! %r{using a custom table affects the wheres}
761
- test 'using a custom table affects the wheres coerced' do
1319
+ test "using a custom table affects the wheres coerced" do
762
1320
  post = posts(:welcome)
763
1321
 
764
1322
  assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
765
1323
  end
766
1324
 
767
- # Can't apply offset withour ORDER
1325
+ # Can't apply offset without ORDER
768
1326
  coerce_tests! %r{using a custom table with joins affects the joins}
769
- test 'using a custom table with joins affects the joins coerced' do
1327
+ test "using a custom table with joins affects the joins coerced" do
770
1328
  post = posts(:welcome)
771
1329
 
772
1330
  assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
@@ -780,45 +1338,55 @@ class RelationTest < ActiveRecord::TestCase
780
1338
  end
781
1339
  end
782
1340
 
783
- class ActiveRecord::RelationTest < ActiveRecord::TestCase
784
- coerce_tests! :test_relation_merging_with_merged_symbol_joins_is_aliased
785
- def test_relation_merging_with_merged_symbol_joins_is_aliased__coerced
786
- categorizations_with_authors = Categorization.joins(:author)
787
- queries = capture_sql { Post.joins(:author, :categorizations).merge(Author.select(:id)).merge(categorizations_with_authors).to_a }
788
-
789
- nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
790
- assert_equal 3, nb_inner_join, "Wrong amount of INNER JOIN in query"
791
-
792
- # using `\W` as the column separator
793
- query_matches = queries.any? do |sql|
794
- %r[INNER\s+JOIN\s+#{Regexp.escape(Author.quoted_table_name)}\s+\Wauthors_categorizations\W]i.match?(sql)
1341
+ module ActiveRecord
1342
+ class RelationTest < ActiveRecord::TestCase
1343
+ # Skipping this test. SQL Server doesn't support optimizer hint as comments
1344
+ coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
1345
+
1346
+ coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1347
+ def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1348
+ escaped_table = Post.connection.quote_table_name("posts")
1349
+ expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1350
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1351
+ assert_equal expected, query
795
1352
  end
796
1353
 
797
- assert query_matches, "Should be aliasing the child INNER JOINs in query"
1354
+ # Workaround for randomly failing test. Ordering of results not guaranteed.
1355
+ # TODO: Remove coerced test when https://github.com/rails/rails/pull/44168 merged.
1356
+ coerce_tests! :test_select_quotes_when_using_from_clause
1357
+ def test_select_quotes_when_using_from_clause_coerced
1358
+ quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
1359
+ selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
1360
+ assert_equal Post.pluck(:id).sort, selected.sort
1361
+ end
798
1362
  end
799
1363
  end
800
1364
 
801
-
802
-
803
-
804
- require 'models/post'
1365
+ require "models/post"
805
1366
  class SanitizeTest < ActiveRecord::TestCase
1367
+ # Use nvarchar string (N'') in assert
806
1368
  coerce_tests! :test_sanitize_sql_like_example_use_case
807
1369
  def test_sanitize_sql_like_example_use_case_coerced
808
1370
  searchable_post = Class.new(Post) do
809
- def self.search(term)
810
- where("title LIKE ?", sanitize_sql_like(term, '!'))
1371
+ def self.search_as_method(term)
1372
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
811
1373
  end
1374
+
1375
+ scope :search_as_scope, ->(term) {
1376
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
1377
+ }
1378
+ end
1379
+
1380
+ assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1381
+ searchable_post.search_as_method("20% _reduction_!").to_a
812
1382
  end
813
- assert_sql(/\(title LIKE N'20!% !_reduction!_!!'\)/) do
814
- searchable_post.search("20% _reduction_!").to_a
1383
+
1384
+ assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1385
+ searchable_post.search_as_scope("20% _reduction_!").to_a
815
1386
  end
816
1387
  end
817
1388
  end
818
1389
 
819
-
820
-
821
-
822
1390
  class SchemaDumperTest < ActiveRecord::TestCase
823
1391
  # We have precision to 38.
824
1392
  coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
@@ -850,19 +1418,14 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
850
1418
  coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
851
1419
  end
852
1420
 
853
-
854
-
855
-
856
1421
  class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
857
1422
  # We trust Rails on this since we do not want to install mysql.
858
1423
  coerce_tests! %r{inspect on Model class does not raise}
859
1424
  end
860
1425
 
861
-
862
-
863
-
864
- require 'models/topic'
1426
+ require "models/topic"
865
1427
  class TransactionTest < ActiveRecord::TestCase
1428
+ # SQL Server does not have query for release_savepoint
866
1429
  coerce_tests! :test_releasing_named_savepoints
867
1430
  def test_releasing_named_savepoints_coerced
868
1431
  Topic.transaction do
@@ -874,10 +1437,7 @@ class TransactionTest < ActiveRecord::TestCase
874
1437
  end
875
1438
  end
876
1439
 
877
-
878
-
879
-
880
- require 'models/tag'
1440
+ require "models/tag"
881
1441
  class TransactionIsolationTest < ActiveRecord::TestCase
882
1442
  # SQL Server will lock the table for counts even when both
883
1443
  # connections are `READ COMMITTED`. So we bypass with `READPAST`.
@@ -887,7 +1447,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase
887
1447
  assert_equal 0, Tag.count
888
1448
  Tag2.transaction do
889
1449
  Tag2.create
890
- assert_equal 0, Tag.lock('WITH(READPAST)').count
1450
+ assert_equal 0, Tag.lock("WITH(READPAST)").count
891
1451
  end
892
1452
  end
893
1453
  assert_equal 1, Tag.count
@@ -897,10 +1457,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase
897
1457
  coerce_tests! %r{repeatable read}
898
1458
  end
899
1459
 
900
-
901
-
902
-
903
- require 'models/book'
1460
+ require "models/book"
904
1461
  class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
905
1462
  # We have a few view tables. use includes vs equality.
906
1463
  coerce_tests! :test_views
@@ -912,9 +1469,10 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
912
1469
  coerce_tests! :test_does_not_assume_id_column_as_primary_key
913
1470
  def test_does_not_assume_id_column_as_primary_key_coerced
914
1471
  model = Class.new(ActiveRecord::Base) { self.table_name = "ebooks" }
915
- assert_equal 'id', model.primary_key
1472
+ assert_equal "id", model.primary_key
916
1473
  end
917
1474
  end
1475
+
918
1476
  class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
919
1477
  # We have a few view tables. use includes vs equality.
920
1478
  coerce_tests! :test_views
@@ -923,23 +1481,18 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
923
1481
  end
924
1482
  end
925
1483
 
926
-
927
-
928
-
929
- require 'models/author'
1484
+ require "models/author"
930
1485
  class YamlSerializationTest < ActiveRecord::TestCase
931
1486
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
932
1487
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
933
- author = Author.select('authors.*, 5 as posts_count').first
934
- dumped = YAML.load(YAML.dump(author))
1488
+ author = Author.select("authors.*, 5 as posts_count").first
1489
+ dumped_author = YAML.dump(author)
1490
+ dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
935
1491
  assert_equal 5, author.posts_count
936
1492
  assert_equal 5, dumped.posts_count
937
1493
  end
938
1494
  end
939
1495
 
940
-
941
-
942
-
943
1496
  class DateTimePrecisionTest < ActiveRecord::TestCase
944
1497
  # Original test had `7` which we support vs `8` which we use.
945
1498
  coerce_tests! :test_invalid_datetime_precision_raises_error
@@ -955,7 +1508,7 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
955
1508
  coerce_tests! :test_datetime_precision_is_truncated_on_assignment
956
1509
  def test_datetime_precision_is_truncated_on_assignment_coerced
957
1510
  @connection.create_table(:foos, force: true)
958
- @connection.add_column :foos, :created_at, :datetime, precision: 0
1511
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
959
1512
  @connection.add_column :foos, :updated_at, :datetime, precision: 6
960
1513
 
961
1514
  time = ::Time.now.change(nsec: 123456789)
@@ -972,8 +1525,6 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
972
1525
  end
973
1526
  end
974
1527
 
975
-
976
-
977
1528
  class TimePrecisionTest < ActiveRecord::TestCase
978
1529
  # datetime is rounded to increments of .000, .003, or .007 seconds
979
1530
  coerce_tests! :test_time_precision_is_truncated_on_assignment
@@ -994,9 +1545,10 @@ class TimePrecisionTest < ActiveRecord::TestCase
994
1545
  assert_equal 0, foo.start.nsec
995
1546
  assert_equal 123457000, foo.finish.nsec
996
1547
  end
997
- end
998
-
999
1548
 
1549
+ # SQL Server uses default precision for time.
1550
+ coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
1551
+ end
1000
1552
 
1001
1553
  class DefaultNumbersTest < ActiveRecord::TestCase
1002
1554
  # We do better with native types and do not return strings for everything.
@@ -1006,16 +1558,23 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1006
1558
  assert_equal 7, record.positive_integer
1007
1559
  assert_equal 7, record.positive_integer_before_type_cast
1008
1560
  end
1561
+
1562
+ # We do better with native types and do not return strings for everything.
1009
1563
  coerce_tests! :test_default_negative_integer
1010
1564
  def test_default_negative_integer_coerced
1011
1565
  record = DefaultNumber.new
1012
1566
  assert_equal -5, record.negative_integer
1013
1567
  assert_equal -5, record.negative_integer_before_type_cast
1014
1568
  end
1015
- end
1016
-
1017
-
1018
1569
 
1570
+ # We do better with native types and do not return strings for everything.
1571
+ coerce_tests! :test_default_decimal_number
1572
+ def test_default_decimal_number_coerced
1573
+ record = DefaultNumber.new
1574
+ assert_equal BigDecimal("2.78"), record.decimal_number
1575
+ assert_equal 2.78, record.decimal_number_before_type_cast
1576
+ end
1577
+ end
1019
1578
 
1020
1579
  module ActiveRecord
1021
1580
  class CollectionCacheKeyTest < ActiveRecord::TestCase
@@ -1024,114 +1583,179 @@ module ActiveRecord
1024
1583
  end
1025
1584
  end
1026
1585
 
1586
+ module ActiveRecord
1587
+ class CacheKeyTest < ActiveRecord::TestCase
1588
+ # Like Mysql2 and PostgreSQL, SQL Server doesn't return a string value for updated_at. In the Rails tests
1589
+ # the tests are skipped if adapter is Mysql2 or PostgreSQL.
1590
+ coerce_tests! %r{cache_version is the same when it comes from the DB or from the user}
1591
+ coerce_tests! %r{cache_version does NOT call updated_at when value is from the database}
1592
+ coerce_tests! %r{cache_version does not truncate zeros when timestamp ends in zeros}
1593
+ end
1594
+ end
1027
1595
 
1028
-
1029
-
1596
+ require "models/book"
1030
1597
  module ActiveRecord
1031
1598
  class StatementCacheTest < ActiveRecord::TestCase
1032
1599
  # Getting random failures.
1033
1600
  coerce_tests! :test_find_does_not_use_statement_cache_if_table_name_is_changed
1034
- end
1035
- end
1036
-
1037
1601
 
1602
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1603
+ coerce_tests! :test_statement_cache_values_differ
1604
+ def test_statement_cache_values_differ_coerced
1605
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1038
1606
 
1607
+ original_test_statement_cache_values_differ
1608
+ ensure
1609
+ Book.where(author_id: nil, name: 'my book').delete_all
1610
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1611
+ end
1612
+ end
1613
+ end
1039
1614
 
1040
1615
  module ActiveRecord
1041
1616
  module ConnectionAdapters
1042
1617
  class SchemaCacheTest < ActiveRecord::TestCase
1618
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1619
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1620
+
1621
+ # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1622
+ coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1623
+
1624
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1625
+ unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1626
+ def test_marshal_dump_and_load_with_gzip_coerced
1627
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1628
+ end
1629
+ def test_marshal_dump_and_load_via_disk_coerced
1630
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1631
+ end
1632
+ end
1633
+
1043
1634
  private
1635
+
1636
+ def with_marshable_time_defaults
1637
+ # Detect problems
1638
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1639
+ column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1640
+ current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1641
+ end
1642
+
1643
+ # Correct problems
1644
+ if current_default.present?
1645
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1646
+ end
1647
+
1648
+ # Run original test
1649
+ yield
1650
+ ensure
1651
+ # Revert changes
1652
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
1653
+ end
1654
+
1044
1655
  # We need to give the full path for this to work.
1045
1656
  def schema_dump_path
1046
- File.join ARTest::SQLServer.root_activerecord, 'test/assets/schema_dump_5_1.yml'
1657
+ File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
1047
1658
  end
1048
1659
  end
1049
1660
  end
1050
1661
  end
1051
1662
 
1663
+ require "models/post"
1664
+ require "models/comment"
1052
1665
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1053
- coerce_tests! %r{always allows Arel}
1054
- test 'order: always allows Arel' do
1055
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1056
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
1666
+ fixtures :posts
1057
1667
 
1058
- assert_equal ids_depr, ids_disabled
1059
- end
1060
-
1061
- test "pluck: always allows Arel" do
1062
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1063
- values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1668
+ # Use LEN() vs length() function.
1669
+ coerce_tests! %r{order: always allows Arel}
1670
+ test "order: always allows Arel" do
1671
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1064
1672
 
1065
- assert_equal values_depr, values_disabled
1673
+ assert_not_empty titles
1066
1674
  end
1067
1675
 
1676
+ # Use LEN() vs length() function.
1677
+ coerce_tests! %r{pluck: always allows Arel}
1678
+ test "pluck: always allows Arel" do
1679
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1680
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1068
1681
 
1069
- coerce_tests! %r{order: disallows invalid Array arguments}
1070
- test "order: disallows invalid Array arguments" do
1071
- with_unsafe_raw_sql_disabled do
1072
- assert_raises(ActiveRecord::UnknownAttributeReference) do
1073
- Post.order(["author_id", "len(title)"]).pluck(:id)
1074
- end
1075
- end
1682
+ assert_equal excepted_values, values
1076
1683
  end
1077
1684
 
1685
+ # Use LEN() vs length() function.
1078
1686
  coerce_tests! %r{order: allows valid Array arguments}
1079
1687
  test "order: allows valid Array arguments" do
1080
1688
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1081
1689
 
1082
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
1083
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
1690
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1084
1691
 
1085
- assert_equal ids_expected, ids_depr
1086
- assert_equal ids_expected, ids_disabled
1692
+ assert_equal ids_expected, ids
1087
1693
  end
1088
1694
 
1089
- coerce_tests! %r{order: logs deprecation warning for unrecognized column}
1090
- test "order: logs deprecation warning for unrecognized column" do
1091
- with_unsafe_raw_sql_deprecated do
1092
- assert_deprecated(/Dangerous query method/) do
1093
- Post.order("len(title)")
1094
- end
1095
- end
1695
+ test "order: allows string column names that are quoted" do
1696
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1697
+
1698
+ ids = Post.order("[id]").pluck(:id)
1699
+
1700
+ assert_equal ids_expected, ids
1096
1701
  end
1097
1702
 
1098
- coerce_tests! %r{pluck: disallows invalid column name}
1099
- test "pluck: disallows invalid column name" do
1100
- with_unsafe_raw_sql_disabled do
1101
- assert_raises(ActiveRecord::UnknownAttributeReference) do
1102
- Post.pluck("len(title)")
1103
- end
1104
- end
1105
- end
1106
-
1107
- coerce_tests! %r{pluck: disallows invalid column name amongst valid names}
1108
- test "pluck: disallows invalid column name amongst valid names" do
1109
- with_unsafe_raw_sql_disabled do
1110
- assert_raises(ActiveRecord::UnknownAttributeReference) do
1111
- Post.pluck(:title, "len(title)")
1112
- end
1113
- end
1114
- end
1115
-
1116
- coerce_tests! %r{pluck: disallows invalid column names with includes}
1117
- test "pluck: disallows invalid column names with includes" do
1118
- with_unsafe_raw_sql_disabled do
1119
- assert_raises(ActiveRecord::UnknownAttributeReference) do
1120
- Post.includes(:comments).pluck(:title, "len(title)")
1121
- end
1122
- end
1123
- end
1124
-
1125
- coerce_tests! %r{pluck: logs deprecation warning}
1126
- test "pluck: logs deprecation warning" do
1127
- with_unsafe_raw_sql_deprecated do
1128
- assert_deprecated(/Dangerous query method/) do
1129
- Post.includes(:comments).pluck(:title, "len(title)")
1130
- end
1131
- end
1132
- end
1133
- end
1703
+ test "order: allows string column names that are quoted with table" do
1704
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1705
+
1706
+ ids = Post.order("[posts].[id]").pluck(:id)
1707
+
1708
+ assert_equal ids_expected, ids
1709
+ end
1710
+
1711
+ test "order: allows string column names that are quoted with table and user" do
1712
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1713
+
1714
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1715
+
1716
+ assert_equal ids_expected, ids
1717
+ end
1718
+
1719
+ test "order: allows string column names that are quoted with table, user and database" do
1720
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1721
+
1722
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1723
+
1724
+ assert_equal ids_expected, ids
1725
+ end
1726
+
1727
+ test "pluck: allows string column name that are quoted" do
1728
+ titles_expected = Post.pluck(Arel.sql("title"))
1729
+
1730
+ titles = Post.pluck("[title]")
1731
+
1732
+ assert_equal titles_expected, titles
1733
+ end
1734
+
1735
+ test "pluck: allows string column name that are quoted with table" do
1736
+ titles_expected = Post.pluck(Arel.sql("title"))
1737
+
1738
+ titles = Post.pluck("[posts].[title]")
1739
+
1740
+ assert_equal titles_expected, titles
1741
+ end
1134
1742
 
1743
+ test "pluck: allows string column name that are quoted with table and user" do
1744
+ titles_expected = Post.pluck(Arel.sql("title"))
1745
+
1746
+ titles = Post.pluck("[dbo].[posts].[title]")
1747
+
1748
+ assert_equal titles_expected, titles
1749
+ end
1750
+
1751
+ test "pluck: allows string column name that are quoted with table, user and database" do
1752
+ titles_expected = Post.pluck(Arel.sql("title"))
1753
+
1754
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1755
+
1756
+ assert_equal titles_expected, titles
1757
+ end
1758
+ end
1135
1759
 
1136
1760
  class ReservedWordTest < ActiveRecord::TestCase
1137
1761
  coerce_tests! :test_change_columns
@@ -1143,41 +1767,441 @@ class ReservedWordTest < ActiveRecord::TestCase
1143
1767
  end
1144
1768
  end
1145
1769
 
1146
-
1147
-
1148
1770
  class OptimisticLockingTest < ActiveRecord::TestCase
1149
1771
  # We do not allow updating identities, but we can test using a non-identity key
1150
1772
  coerce_tests! :test_update_with_dirty_primary_key
1151
1773
  def test_update_with_dirty_primary_key_coerced
1152
1774
  assert_raises(ActiveRecord::RecordNotUnique) do
1153
- record = StringKeyObject.find('record1')
1154
- record.id = 'record2'
1775
+ record = StringKeyObject.find("record1")
1776
+ record.id = "record2"
1155
1777
  record.save!
1156
1778
  end
1157
1779
 
1158
- record = StringKeyObject.find('record1')
1159
- record.id = 'record42'
1780
+ record = StringKeyObject.find("record1")
1781
+ record.id = "record42"
1160
1782
  record.save!
1161
1783
 
1162
- assert StringKeyObject.find('record42')
1784
+ assert StringKeyObject.find("record42")
1163
1785
  assert_raises(ActiveRecord::RecordNotFound) do
1164
- StringKeyObject.find('record1')
1786
+ StringKeyObject.find("record1")
1165
1787
  end
1166
1788
  end
1167
1789
  end
1168
1790
 
1169
-
1170
-
1171
1791
  class RelationMergingTest < ActiveRecord::TestCase
1792
+ # Use nvarchar string (N'') in assert
1172
1793
  coerce_tests! :test_merging_with_order_with_binds
1173
1794
  def test_merging_with_order_with_binds_coerced
1174
1795
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1175
1796
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1176
1797
  end
1798
+
1799
+ # Same as original but change first regexp to match sp_executesql binding syntax
1800
+ coerce_tests! :test_merge_doesnt_duplicate_same_clauses
1801
+ def test_merge_doesnt_duplicate_same_clauses_coerced
1802
+ david, mary, bob = authors(:david, :mary, :bob)
1803
+
1804
+ non_mary_and_bob = Author.where.not(id: [mary, bob])
1805
+
1806
+ author_id = Author.connection.quote_table_name("authors.id")
1807
+ assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
1808
+ assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
1809
+ end
1810
+
1811
+ only_david = Author.where("#{author_id} IN (?)", david)
1812
+
1813
+ assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
1814
+ assert_equal [david], only_david.merge(only_david)
1815
+ end
1816
+ end
1177
1817
  end
1178
1818
 
1819
+ module ActiveRecord
1820
+ class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
1821
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
1822
+ # constraints. As this test truncates all tables we would need to remove all foreign
1823
+ # key constraints and then restore them afterwards to get this test to pass.
1824
+ coerce_tests! :test_truncate_tables
1825
+ end
1826
+ end
1827
+
1828
+ require "models/book"
1829
+ class EnumTest < ActiveRecord::TestCase
1830
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1831
+ coerce_tests! %r{enums are distinct per class}
1832
+ test "enums are distinct per class coerced" do
1833
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1834
+
1835
+ send(:'original_enums are distinct per class')
1836
+ ensure
1837
+ Book.where(author_id: nil, name: nil).delete_all
1838
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1839
+ end
1840
+
1841
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1842
+ coerce_tests! %r{creating new objects with enum scopes}
1843
+ test "creating new objects with enum scopes coerced" do
1844
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1845
+
1846
+ send(:'original_creating new objects with enum scopes')
1847
+ ensure
1848
+ Book.where(author_id: nil, name: nil).delete_all
1849
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1850
+ end
1851
+
1852
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1853
+ coerce_tests! %r{enums are inheritable}
1854
+ test "enums are inheritable coerced" do
1855
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1856
+
1857
+ send(:'original_enums are inheritable')
1858
+ ensure
1859
+ Book.where(author_id: nil, name: nil).delete_all
1860
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1861
+ end
1862
+
1863
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1864
+ coerce_tests! %r{declare multiple enums at a time}
1865
+ test "declare multiple enums at a time coerced" do
1866
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1867
+
1868
+ send(:'original_declare multiple enums at a time')
1869
+ ensure
1870
+ Book.where(author_id: nil, name: nil).delete_all
1871
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1872
+ end
1873
+
1874
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1875
+ coerce_tests! %r{serializable\? with large number label}
1876
+ test "serializable? with large number label coerced" do
1877
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1878
+
1879
+ send(:'original_serializable\? with large number label')
1880
+ ensure
1881
+ Book.where(author_id: nil, name: nil).delete_all
1882
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1883
+ end
1884
+ end
1179
1885
 
1886
+ require "models/task"
1887
+ class QueryCacheExpiryTest < ActiveRecord::TestCase
1888
+ # SQL Server does not support skipping or upserting duplicates.
1889
+ coerce_tests! :test_insert_all
1890
+ def test_insert_all_coerced
1891
+ assert_raises(ArgumentError, /does not support skipping duplicates/) do
1892
+ Task.cache { Task.insert({ starting: Time.now }) }
1893
+ end
1894
+
1895
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1896
+ Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1897
+ end
1898
+
1899
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1900
+ Task.cache { Task.insert!({ starting: Time.now }) }
1901
+ end
1902
+
1903
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1904
+ Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1905
+ end
1906
+
1907
+ assert_raises(ArgumentError, /does not support upsert/) do
1908
+ Task.cache { Task.upsert({ starting: Time.now }) }
1909
+ end
1910
+
1911
+ assert_raises(ArgumentError, /does not support upsert/) do
1912
+ Task.cache { Task.upsert_all([{ starting: Time.now }]) }
1913
+ end
1914
+ end
1915
+ end
1916
+
1917
+ require "models/citation"
1180
1918
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1181
- # Temporarily coerce this test due to https://github.com/rails/rails/issues/34945
1182
- coerce_tests! :test_eager_loading_too_may_ids
1919
+ fixtures :citations
1920
+
1921
+ # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1922
+ # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1923
+ # (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
1924
+ # However, you cannot change the compatibility level during a test. The purpose of the test is to ensure that an
1925
+ # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1926
+ # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1927
+ # adapter is 2,098.
1928
+ coerce_tests! :test_eager_loading_too_many_ids
1929
+ def test_eager_loading_too_many_ids_coerced
1930
+ # Remove excess records.
1931
+ Citation.limit(32768).order(id: :desc).delete_all
1932
+
1933
+ # Perform test
1934
+ citation_count = Citation.count
1935
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1936
+ assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1937
+ end
1938
+ end
1939
+ end
1940
+
1941
+ class LogSubscriberTest < ActiveRecord::TestCase
1942
+ # Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
1943
+ coerce_tests! :test_verbose_query_logs
1944
+ def test_verbose_query_logs_coerced
1945
+ original_test_verbose_query_logs
1946
+ end
1947
+
1948
+ # Bindings logged slightly differently.
1949
+ coerce_tests! :test_where_in_binds_logging_include_attribute_names
1950
+ def test_where_in_binds_logging_include_attribute_names_coerced
1951
+ Developer.where(id: [1, 2, 3, 4, 5]).load
1952
+ wait
1953
+ assert_match(%{@0 = 1, @1 = 2, @2 = 3, @3 = 4, @4 = 5 [["id", nil], ["id", nil], ["id", nil], ["id", nil], ["id", nil]]}, @logger.logged(:debug).last)
1954
+ end
1955
+ end
1956
+
1957
+ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1958
+ # Workaround for randomly failing test.
1959
+ coerce_tests! :test_has_primary_key
1960
+ def test_has_primary_key_coerced
1961
+ @schema_migration.reset_column_information
1962
+ original_test_has_primary_key
1963
+ end
1964
+ end
1965
+
1966
+ class ReloadModelsTest < ActiveRecord::TestCase
1967
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
1968
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1969
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1970
+ end
1971
+
1972
+ class MarshalSerializationTest < ActiveRecord::TestCase
1973
+ private
1974
+
1975
+ def marshal_fixture_path(file_name)
1976
+ File.expand_path(
1977
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
1978
+ ARTest::SQLServer.test_root_sqlserver
1979
+ )
1980
+ end
1981
+ end
1982
+
1983
+ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1984
+ # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
1985
+ # "A column has been specified more than once in the order by list"
1986
+ # Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
1987
+ coerce_tests! :test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins, :test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
1988
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
1989
+ # preload table schemas
1990
+ Author.joins(:category_post_comments).first
1991
+
1992
+ assert_includes_and_joins_equal(
1993
+ Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
1994
+ [authors(:david), authors(:mary)], :category_post_comments
1995
+ )
1996
+ end
1997
+
1998
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
1999
+ # preload table schemas
2000
+ Category.joins(:post_comments).first
2001
+
2002
+ assert_includes_and_joins_equal(
2003
+ Category.where("comments.id" => comments(:more_greetings).id).order(:id),
2004
+ [categories(:general), categories(:technology)], :post_comments
2005
+ )
2006
+ end
2007
+ end
2008
+
2009
+ class BasePreventWritesTest < ActiveRecord::TestCase
2010
+ # SQL Server does not have query for release_savepoint
2011
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
2012
+ test "an empty transaction does not raise if preventing writes coerced" do
2013
+ ActiveRecord::Base.while_preventing_writes do
2014
+ assert_queries(1, ignore_none: true) do
2015
+ Bird.transaction do
2016
+ ActiveRecord::Base.connection.materialize_transactions
2017
+ end
2018
+ end
2019
+ end
2020
+ end
2021
+
2022
+ class BasePreventWritesLegacyTest < ActiveRecord::TestCase
2023
+ # SQL Server does not have query for release_savepoint
2024
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
2025
+ test "an empty transaction does not raise if preventing writes coerced" do
2026
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
2027
+ assert_queries(1, ignore_none: true) do
2028
+ Bird.transaction do
2029
+ ActiveRecord::Base.connection.materialize_transactions
2030
+ end
2031
+ end
2032
+ end
2033
+ end
2034
+ end
2035
+ end
2036
+
2037
+ class MigratorTest < ActiveRecord::TestCase
2038
+ # Test fails on Windows AppVeyor CI for unknown reason.
2039
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2040
+ end
2041
+
2042
+ class MultiDbMigratorTest < ActiveRecord::TestCase
2043
+ # Test fails on Windows AppVeyor CI for unknown reason.
2044
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2045
+ end
2046
+
2047
+ require "models/book"
2048
+ class FieldOrderedValuesTest < ActiveRecord::TestCase
2049
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2050
+ coerce_tests! :test_in_order_of_with_enums_values
2051
+ def test_in_order_of_with_enums_values_coerced
2052
+ Book.connection.remove_index(:books, column: [:author_id, :name])
2053
+
2054
+ original_test_in_order_of_with_enums_values
2055
+ ensure
2056
+ Book.where(author_id: nil, name: nil).delete_all
2057
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
2058
+ end
2059
+
2060
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2061
+ coerce_tests! :test_in_order_of_with_enums_keys
2062
+ def test_in_order_of_with_enums_keys_coerced
2063
+ Book.connection.remove_index(:books, column: [:author_id, :name])
2064
+
2065
+ original_test_in_order_of_with_enums_keys
2066
+ ensure
2067
+ Book.where(author_id: nil, name: nil).delete_all
2068
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
2069
+ end
2070
+ end
2071
+
2072
+ require "models/dashboard"
2073
+ class QueryLogsTest < ActiveRecord::TestCase
2074
+ # Same as original coerced test except our SQL ends with binding.
2075
+ # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44053)
2076
+ coerce_tests! :test_custom_basic_tags, :test_custom_proc_tags, :test_multiple_custom_tags, :test_custom_proc_context_tags
2077
+ def test_custom_basic_tags_coerced
2078
+ ActiveRecord::QueryLogs.tags = [ :application, { custom_string: "test content" } ]
2079
+
2080
+ assert_sql(%r{/\*application:active_record,custom_string:test content\*/}) do
2081
+ Dashboard.first
2082
+ end
2083
+ end
2084
+
2085
+ def test_custom_proc_tags_coerced
2086
+ ActiveRecord::QueryLogs.tags = [ :application, { custom_proc: -> { "test content" } } ]
2087
+
2088
+ assert_sql(%r{/\*application:active_record,custom_proc:test content\*/}) do
2089
+ Dashboard.first
2090
+ end
2091
+ end
2092
+
2093
+ def test_multiple_custom_tags_coerced
2094
+ ActiveRecord::QueryLogs.tags = [
2095
+ :application,
2096
+ { custom_proc: -> { "test content" }, another_proc: -> { "more test content" } },
2097
+ ]
2098
+
2099
+ assert_sql(%r{/\*application:active_record,custom_proc:test content,another_proc:more test content\*/}) do
2100
+ Dashboard.first
2101
+ end
2102
+ end
2103
+
2104
+ def test_custom_proc_context_tags_coerced
2105
+ ActiveSupport::ExecutionContext[:foo] = "bar"
2106
+ ActiveRecord::QueryLogs.tags = [ :application, { custom_context_proc: ->(context) { context[:foo] } } ]
2107
+
2108
+ assert_sql(%r{/\*application:active_record,custom_context_proc:bar\*/}) do
2109
+ Dashboard.first
2110
+ end
2111
+ end
2112
+ end
2113
+
2114
+ # SQL Server does not support upsert yet
2115
+ # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44050)
2116
+ class InsertAllTest < ActiveRecord::TestCase
2117
+ coerce_tests! :test_upsert_all_only_updates_the_column_provided_via_update_only
2118
+ def test_upsert_all_only_updates_the_column_provided_via_update_only_coerced
2119
+ assert_raises(ArgumentError, /does not support upsert/) do
2120
+ original_test_upsert_all_only_updates_the_column_provided_via_update_only
2121
+ end
2122
+ end
2123
+
2124
+ coerce_tests! :test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2125
+ def test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only_coerced
2126
+ assert_raises(ArgumentError, /does not support upsert/) do
2127
+ original_test_upsert_all_only_updates_the_list_of_columns_provided_via_update_only
2128
+ end
2129
+ end
2130
+
2131
+ coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2132
+ def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true_coerced
2133
+ assert_raises(ArgumentError, /does not support upsert/) do
2134
+ original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_true
2135
+ end
2136
+ end
2137
+
2138
+ coerce_tests! :test_upsert_all_respects_created_at_precision_when_touched_implicitly
2139
+ def test_upsert_all_respects_created_at_precision_when_touched_implicitly_coerced
2140
+ assert_raises(ArgumentError, /does not support upsert/) do
2141
+ original_test_upsert_all_respects_created_at_precision_when_touched_implicitly
2142
+ end
2143
+ end
2144
+
2145
+ coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden
2146
+ def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden_coerced
2147
+ assert_raises(ArgumentError, /does not support upsert/) do
2148
+ original_test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_true_but_overridden
2149
+ end
2150
+ end
2151
+
2152
+ coerce_tests! :test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2153
+ def test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false_coerced
2154
+ assert_raises(ArgumentError, /does not support upsert/) do
2155
+ original_test_upsert_all_does_not_implicitly_set_timestamps_on_create_when_model_record_timestamps_is_false
2156
+ end
2157
+ end
2158
+
2159
+ coerce_tests! :test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2160
+ def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden_coerced
2161
+ assert_raises(ArgumentError, /does not support upsert/) do
2162
+ original_test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_timestamps_is_false_but_overridden
2163
+ end
2164
+ end
2165
+ end
2166
+
2167
+ class HasOneThroughDisableJoinsAssociationsTest < ActiveRecord::TestCase
2168
+ # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44051)
2169
+ coerce_tests! :test_disable_joins_through_with_enum_type
2170
+ def test_disable_joins_through_with_enum_type_coerced
2171
+ joins = capture_sql { @member.club }
2172
+ no_joins = capture_sql { @member.club_without_joins }
2173
+
2174
+ assert_equal 1, joins.size
2175
+ assert_equal 2, no_joins.size
2176
+
2177
+ assert_match(/INNER JOIN/, joins.first)
2178
+ no_joins.each do |nj|
2179
+ assert_no_match(/INNER JOIN/, nj)
2180
+ end
2181
+
2182
+ assert_match(/\[memberships\]\.\[type\]/, no_joins.first)
2183
+ end
2184
+ end
2185
+
2186
+ class InsertAllTest < ActiveRecord::TestCase
2187
+ coerce_tests! :test_insert_all_returns_requested_sql_fields
2188
+ # Same as original but using INSERTED.name as UPPER argument
2189
+ def test_insert_all_returns_requested_sql_fields_coerced
2190
+ skip unless supports_insert_returning?
2191
+
2192
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2193
+ assert_equal %w[ REWORK ], result.pluck("name")
2194
+ end
2195
+ end
2196
+
2197
+ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::EncryptionTestCase
2198
+ # TODO: Remove coerce after Rails 7.1.0 (see https://github.com/rails/rails/pull/44052)
2199
+ # Same as original but SQL Server string is varchar(4000), not varchar(255) as other adapters. Produce invalid strings with 4001 characters
2200
+ coerce_tests! %r{validate column sizes}
2201
+ test "validate column sizes coerced" do
2202
+ assert EncryptedAuthor.new(name: "jorge").valid?
2203
+ assert_not EncryptedAuthor.new(name: "a" * 4001).valid?
2204
+ author = EncryptedAuthor.create(name: "a" * 4001)
2205
+ assert_not author.valid?
2206
+ end
1183
2207
  end