activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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