activerecord-sqlserver-adapter 5.2.1 → 6.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.travis.yml +6 -8
  5. data/CHANGELOG.md +22 -32
  6. data/{Dockerfile → Dockerfile.ci} +1 -1
  7. data/Gemfile +42 -41
  8. data/README.md +9 -30
  9. data/RUNNING_UNIT_TESTS.md +3 -0
  10. data/Rakefile +2 -0
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +25 -14
  13. data/appveyor.yml +24 -17
  14. data/docker-compose.ci.yml +7 -5
  15. data/guides/RELEASING.md +11 -0
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -0
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +2 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -1
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +6 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +4 -1
  24. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  25. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +55 -14
  26. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +2 -0
  27. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -0
  28. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +38 -0
  29. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -3
  30. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +93 -70
  32. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +2 -0
  33. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -0
  34. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -0
  35. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -0
  36. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +42 -40
  37. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type.rb +2 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -2
  41. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +5 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -1
  45. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +7 -6
  46. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -1
  50. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -1
  51. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -1
  54. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -2
  56. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -1
  57. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -1
  59. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +5 -4
  60. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +2 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -1
  62. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -1
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -0
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -1
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -3
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +3 -1
  69. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -3
  70. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -2
  71. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -2
  73. data/lib/active_record/connection_adapters/sqlserver/utils.rb +2 -0
  74. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -0
  75. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +44 -10
  76. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -3
  77. data/lib/active_record/sqlserver_base.rb +8 -0
  78. data/lib/active_record/tasks/sqlserver_database_tasks.rb +2 -0
  79. data/lib/activerecord-sqlserver-adapter.rb +2 -0
  80. data/lib/arel/visitors/sqlserver.rb +40 -10
  81. data/lib/arel_sqlserver.rb +2 -0
  82. data/test/appveyor/dbsetup.ps1 +4 -4
  83. data/test/cases/adapter_test_sqlserver.rb +65 -1
  84. data/test/cases/change_column_null_test_sqlserver.rb +2 -0
  85. data/test/cases/coerced_tests.rb +644 -187
  86. data/test/cases/column_test_sqlserver.rb +2 -1
  87. data/test/cases/connection_test_sqlserver.rb +2 -0
  88. data/test/cases/execute_procedure_test_sqlserver.rb +2 -0
  89. data/test/cases/fetch_test_sqlserver.rb +2 -0
  90. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +4 -2
  91. data/test/cases/helper_sqlserver.rb +2 -0
  92. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  93. data/test/cases/index_test_sqlserver.rb +2 -0
  94. data/test/cases/json_test_sqlserver.rb +2 -0
  95. data/test/cases/migration_test_sqlserver.rb +4 -2
  96. data/test/cases/order_test_sqlserver.rb +2 -0
  97. data/test/cases/pessimistic_locking_test_sqlserver.rb +2 -0
  98. data/test/cases/rake_test_sqlserver.rb +2 -0
  99. data/test/cases/schema_dumper_test_sqlserver.rb +3 -1
  100. data/test/cases/schema_test_sqlserver.rb +2 -0
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -0
  102. data/test/cases/showplan_test_sqlserver.rb +4 -2
  103. data/test/cases/specific_schema_test_sqlserver.rb +2 -0
  104. data/test/cases/transaction_test_sqlserver.rb +2 -1
  105. data/test/cases/trigger_test_sqlserver.rb +2 -1
  106. data/test/cases/utils_test_sqlserver.rb +2 -0
  107. data/test/cases/uuid_test_sqlserver.rb +2 -1
  108. data/test/debug.rb +2 -0
  109. data/test/migrations/create_clients_and_change_column_null.rb +2 -0
  110. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -0
  111. data/test/models/sqlserver/booking.rb +2 -0
  112. data/test/models/sqlserver/customers_view.rb +2 -0
  113. data/test/models/sqlserver/datatype.rb +2 -0
  114. data/test/models/sqlserver/datatype_migration.rb +2 -0
  115. data/test/models/sqlserver/dollar_table_name.rb +2 -0
  116. data/test/models/sqlserver/edge_schema.rb +2 -0
  117. data/test/models/sqlserver/fk_has_fk.rb +2 -0
  118. data/test/models/sqlserver/fk_has_pk.rb +2 -0
  119. data/test/models/sqlserver/natural_pk_data.rb +2 -0
  120. data/test/models/sqlserver/natural_pk_int_data.rb +2 -0
  121. data/test/models/sqlserver/no_pk_data.rb +2 -0
  122. data/test/models/sqlserver/object_default.rb +2 -0
  123. data/test/models/sqlserver/quoted_table.rb +2 -0
  124. data/test/models/sqlserver/quoted_view_1.rb +2 -0
  125. data/test/models/sqlserver/quoted_view_2.rb +2 -0
  126. data/test/models/sqlserver/sst_memory.rb +2 -0
  127. data/test/models/sqlserver/string_default.rb +2 -0
  128. data/test/models/sqlserver/string_defaults_big_view.rb +2 -0
  129. data/test/models/sqlserver/string_defaults_view.rb +2 -0
  130. data/test/models/sqlserver/tinyint_pk.rb +2 -0
  131. data/test/models/sqlserver/trigger.rb +2 -0
  132. data/test/models/sqlserver/trigger_history.rb +2 -0
  133. data/test/models/sqlserver/upper.rb +2 -0
  134. data/test/models/sqlserver/uppered.rb +2 -0
  135. data/test/models/sqlserver/uuid.rb +2 -0
  136. data/test/schema/sqlserver_specific_schema.rb +2 -0
  137. data/test/support/coerceable_test_sqlserver.rb +14 -5
  138. data/test/support/connection_reflection.rb +2 -0
  139. data/test/support/core_ext/query_cache.rb +3 -0
  140. data/test/support/load_schema_sqlserver.rb +2 -0
  141. data/test/support/minitest_sqlserver.rb +2 -0
  142. data/test/support/paths_sqlserver.rb +2 -0
  143. data/test/support/rake_helpers.rb +1 -0
  144. data/test/support/sql_counter_sqlserver.rb +3 -0
  145. data/test/support/test_in_memory_oltp.rb +2 -0
  146. metadata +18 -9
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cases/helper_sqlserver'
2
4
  require 'migrations/create_clients_and_change_column_null'
3
5
 
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cases/helper_sqlserver'
2
4
 
3
5
 
4
6
 
5
7
  require 'models/event'
6
8
  class UniquenessValidationTest < ActiveRecord::TestCase
7
- # So sp_executesql swallows this exception. Run without prpared to see it.
9
+ # So sp_executesql swallows this exception. Run without prepared to see it.
8
10
  coerce_tests! :test_validate_uniqueness_with_limit
9
11
  def test_validate_uniqueness_with_limit_coerced
10
12
  connection.unprepared_statement do
@@ -14,7 +16,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
14
16
  end
15
17
  end
16
18
 
17
- # So sp_executesql swallows this exception. Run without prpared to see it.
19
+ # So sp_executesql swallows this exception. Run without prepared to see it.
18
20
  coerce_tests! :test_validate_uniqueness_with_limit_and_utf8
19
21
  def test_validate_uniqueness_with_limit_and_utf8_coerced
20
22
  connection.unprepared_statement do
@@ -23,6 +25,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
23
25
  end
24
26
  end
25
27
  end
28
+
29
+ # Skip the test if database is case-insensitive.
30
+ coerce_tests! :test_validate_case_sensitive_uniqueness_by_default
31
+ def test_validate_case_sensitive_uniqueness_by_default_coerced
32
+ database_collation = connection.select_one("SELECT collation_name FROM sys.databases WHERE name = 'activerecord_unittest'").values.first
33
+ skip if database_collation.include?('_CI_')
34
+
35
+ original_test_validate_case_sensitive_uniqueness_by_default_coerced
36
+ end
26
37
  end
27
38
 
28
39
 
@@ -31,14 +42,14 @@ end
31
42
  require 'models/event'
32
43
  module ActiveRecord
33
44
  class AdapterTest < ActiveRecord::TestCase
34
- # I really dont think we can support legacy binds.
45
+ # I really don`t think we can support legacy binds.
35
46
  coerce_tests! :test_select_all_with_legacy_binds
36
47
  coerce_tests! :test_insert_update_delete_with_legacy_binds
37
48
 
38
49
  # As far as I can tell, SQL Server does not support null bytes in strings.
39
50
  coerce_tests! :test_update_prepared_statement
40
51
 
41
- # So sp_executesql swallows this exception. Run without prpared to see it.
52
+ # So sp_executesql swallows this exception. Run without prepared to see it.
42
53
  coerce_tests! :test_value_limit_violations_are_translated_to_specific_exception
43
54
  def test_value_limit_violations_are_translated_to_specific_exception_coerced
44
55
  connection.unprepared_statement do
@@ -48,6 +59,69 @@ module ActiveRecord
48
59
  assert_not_nil error.cause
49
60
  end
50
61
  end
62
+
63
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
64
+ coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
65
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
66
+ Subscriber.send(:load_schema!)
67
+ original_test_errors_when_an_insert_query_is_called_while_preventing_writes
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+
74
+
75
+ module ActiveRecord
76
+ class AdapterTestWithoutTransaction < ActiveRecord::TestCase
77
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
78
+ # constraints. So manually remove/add foreign keys in test.
79
+ coerce_tests! :test_truncate_tables
80
+ def test_truncate_tables_coerced
81
+ # Remove foreign key constraint to allow truncation.
82
+ @connection.remove_foreign_key :authors, :author_addresses
83
+
84
+ assert_operator Post.count, :>, 0
85
+ assert_operator Author.count, :>, 0
86
+ assert_operator AuthorAddress.count, :>, 0
87
+
88
+ @connection.truncate_tables("author_addresses", "authors", "posts")
89
+
90
+ assert_equal 0, Post.count
91
+ assert_equal 0, Author.count
92
+ assert_equal 0, AuthorAddress.count
93
+ ensure
94
+ reset_fixtures("posts", "authors", "author_addresses")
95
+
96
+ # Restore foreign key constraint.
97
+ @connection.add_foreign_key :authors, :author_addresses
98
+ end
99
+
100
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
101
+ # constraints. So manually remove/add foreign keys in test.
102
+ coerce_tests! :test_truncate_tables_with_query_cache
103
+ def test_truncate_tables_with_query_cache
104
+ # Remove foreign key constraint to allow truncation.
105
+ @connection.remove_foreign_key :authors, :author_addresses
106
+
107
+ @connection.enable_query_cache!
108
+
109
+ assert_operator Post.count, :>, 0
110
+ assert_operator Author.count, :>, 0
111
+ assert_operator AuthorAddress.count, :>, 0
112
+
113
+ @connection.truncate_tables("author_addresses", "authors", "posts")
114
+
115
+ assert_equal 0, Post.count
116
+ assert_equal 0, Author.count
117
+ assert_equal 0, AuthorAddress.count
118
+ ensure
119
+ reset_fixtures("posts", "authors", "author_addresses")
120
+ @connection.disable_query_cache!
121
+
122
+ # Restore foreign key constraint.
123
+ @connection.add_foreign_key :authors, :author_addresses
124
+ end
51
125
  end
52
126
  end
53
127
 
@@ -56,29 +130,28 @@ end
56
130
 
57
131
  require 'models/topic'
58
132
  class AttributeMethodsTest < ActiveRecord::TestCase
133
+ # Use IFF for boolean statement in SELECT
59
134
  coerce_tests! %r{typecast attribute from select to false}
60
135
  def test_typecast_attribute_from_select_to_false_coerced
61
136
  Topic.create(:title => 'Budget')
62
137
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
63
- assert !topic.is_test?
138
+ assert_not_predicate topic, :is_test?
64
139
  end
65
140
 
141
+ # Use IFF for boolean statement in SELECT
66
142
  coerce_tests! %r{typecast attribute from select to true}
67
143
  def test_typecast_attribute_from_select_to_true_coerced
68
144
  Topic.create(:title => 'Budget')
69
145
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
70
- assert topic.is_test?
146
+ assert_predicate topic, :is_test?
71
147
  end
72
148
  end
73
149
 
74
150
 
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
151
+
80
152
 
81
153
  class BasicsTest < ActiveRecord::TestCase
154
+ # Use square brackets as SQL Server escaped character
82
155
  coerce_tests! :test_column_names_are_escaped
83
156
  def test_column_names_are_escaped_coerced
84
157
  conn = ActiveRecord::Base.connection
@@ -96,7 +169,7 @@ class BasicsTest < ActiveRecord::TestCase
96
169
  Time.use_zone("Eastern Time (US & Canada)") do
97
170
  topic = Topic.find(1)
98
171
  time = Time.zone.parse("2017-07-17 10:56")
99
- topic.update_attributes!(written_on: time)
172
+ topic.update!(written_on: time)
100
173
  assert_equal(time, topic.written_on)
101
174
  end
102
175
  end
@@ -107,21 +180,23 @@ class BasicsTest < ActiveRecord::TestCase
107
180
  Time.use_zone("Eastern Time (US & Canada)") do
108
181
  topic = Topic.find(1)
109
182
  time = Time.zone.parse("2017-07-17 10:56")
110
- topic.update_attributes!(written_on: time)
183
+ topic.update!(written_on: time)
111
184
  assert_equal(time, topic.written_on)
112
185
  end
113
186
  end
114
187
  end
115
188
  end
116
189
 
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)
190
+ # SQL Server does not have query for release_savepoint
191
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
192
+ test "an empty transaction does not raise if preventing writes coerced" do
193
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
194
+ assert_queries(1, ignore_none: true) do
195
+ Bird.transaction do
196
+ ActiveRecord::Base.connection.materialize_transactions
197
+ end
198
+ end
199
+ end
125
200
  end
126
201
  end
127
202
 
@@ -132,12 +207,24 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
132
207
  # Since @client.firm is a single first/top, and we use FETCH the order clause is used.
133
208
  coerce_tests! :test_belongs_to_does_not_use_order_by
134
209
 
210
+ # Square brackets around column name
135
211
  coerce_tests! :test_belongs_to_with_primary_key_joins_on_correct_column
136
212
  def test_belongs_to_with_primary_key_joins_on_correct_column_coerced
137
213
  sql = Client.joins(:firm_with_primary_key).to_sql
138
214
  assert_no_match(/\[firm_with_primary_keys_companies\]\.\[id\]/, sql)
139
215
  assert_match(/\[firm_with_primary_keys_companies\]\.\[name\]/, sql)
140
216
  end
217
+
218
+ # Asserted SQL to get one row different from original test.
219
+ coerce_tests! :test_belongs_to
220
+ def test_belongs_to_coerced
221
+ client = Client.find(3)
222
+ first_firm = companies(:first_firm)
223
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
224
+ assert_equal first_firm, client.firm
225
+ assert_equal first_firm.name, client.firm.name
226
+ end
227
+ end
141
228
  end
142
229
 
143
230
 
@@ -163,6 +250,7 @@ module ActiveRecord
163
250
  # SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
164
251
  coerce_tests! :test_statement_cache
165
252
  coerce_tests! :test_statement_cache_with_query_cache
253
+ coerce_tests! :test_statement_cache_with_find
166
254
  coerce_tests! :test_statement_cache_with_find_by
167
255
  coerce_tests! :test_statement_cache_with_in_clause
168
256
  coerce_tests! :test_statement_cache_with_sql_string_literal
@@ -170,34 +258,28 @@ module ActiveRecord
170
258
  end
171
259
 
172
260
 
261
+
262
+
173
263
  module ActiveRecord
174
264
  class InstrumentationTest < ActiveRecord::TestCase
175
- # This fails randomly due to schema cache being lost?
265
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
176
266
  coerce_tests! :test_payload_name_on_load
177
267
  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
268
+ Book.send(:load_schema!)
269
+ original_test_payload_name_on_load
189
270
  end
190
271
  end
191
272
  end
192
273
 
274
+
275
+
276
+
193
277
  class CalculationsTest < ActiveRecord::TestCase
194
- # This fails randomly due to schema cache being lost?
278
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
195
279
  coerce_tests! :test_offset_is_kept
196
280
  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)
281
+ Account.send(:load_schema!)
282
+ original_test_offset_is_kept
201
283
  end
202
284
 
203
285
  # Are decimal, not integer.
@@ -207,18 +289,20 @@ class CalculationsTest < ActiveRecord::TestCase
207
289
  assert_equal BigDecimal('3.0').to_s, BigDecimal(value).to_s
208
290
  end
209
291
 
292
+ # Match SQL Server limit implementation
210
293
  coerce_tests! :test_limit_is_kept
211
294
  def test_limit_is_kept_coerced
212
295
  queries = capture_sql_ss { Account.limit(1).count }
213
296
  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}
297
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
215
298
  end
216
299
 
300
+ # Match SQL Server limit implementation
217
301
  coerce_tests! :test_limit_with_offset_is_kept
218
302
  def test_limit_with_offset_is_kept_coerced
219
303
  queries = capture_sql_ss { Account.limit(1).offset(1).count }
220
304
  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}
305
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
222
306
  end
223
307
 
224
308
  # SQL Server needs an alias for the calculated column
@@ -235,27 +319,44 @@ end
235
319
 
236
320
 
237
321
 
322
+
238
323
  module ActiveRecord
239
324
  class Migration
240
325
  class ChangeSchemaTest < ActiveRecord::TestCase
241
- # We test these.
242
- coerce_tests! :test_create_table_with_bigint,
243
- :test_create_table_with_defaults
244
- end
326
+ # Integer.default is a number and not a string
327
+ coerce_tests! :test_create_table_with_defaults
328
+ def test_create_table_with_defaults_coerce
329
+ connection.create_table :testings do |t|
330
+ t.column :one, :string, default: "hello"
331
+ t.column :two, :boolean, default: true
332
+ t.column :three, :boolean, default: false
333
+ t.column :four, :integer, default: 1
334
+ t.column :five, :text, default: "hello"
335
+ end
245
336
 
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
337
+ columns = connection.columns(:testings)
338
+ one = columns.detect { |c| c.name == "one" }
339
+ two = columns.detect { |c| c.name == "two" }
340
+ three = columns.detect { |c| c.name == "three" }
341
+ four = columns.detect { |c| c.name == "four" }
342
+ five = columns.detect { |c| c.name == "five" }
343
+
344
+ assert_equal "hello", one.default
345
+ assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
346
+ assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
347
+ assert_equal 1, four.default
348
+ assert_equal "hello", five.default
349
+ end
249
350
  end
250
351
  end
251
352
  end
252
353
 
253
354
 
254
355
 
356
+
255
357
  module ActiveRecord
256
358
  module ConnectionAdapters
257
359
  class QuoteARBaseTest < ActiveRecord::TestCase
258
-
259
360
  # Use our date format.
260
361
  coerce_tests! :test_quote_ar_object
261
362
  def test_quote_ar_object_coerced
@@ -269,13 +370,13 @@ module ActiveRecord
269
370
  value = DatetimePrimaryKey.new(id: @time)
270
371
  assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
271
372
  end
272
-
273
373
  end
274
374
  end
275
375
  end
276
376
 
277
377
 
278
378
 
379
+
279
380
  module ActiveRecord
280
381
  class Migration
281
382
  class ColumnAttributesTest < ActiveRecord::TestCase
@@ -295,7 +396,7 @@ end
295
396
 
296
397
  module ActiveRecord
297
398
  class Migration
298
- class ColumnsTest
399
+ class ColumnsTest < ActiveRecord::TestCase
299
400
  # Our defaults are real 70000 integers vs '70000' strings.
300
401
  coerce_tests! :test_rename_column_preserves_default_value_not_null
301
402
  def test_rename_column_preserves_default_value_not_null_coerced
@@ -378,7 +479,7 @@ end
378
479
 
379
480
 
380
481
  class CoreTest < ActiveRecord::TestCase
381
- # I think fixtures are useing the wrong time zone and the `:first`
482
+ # I think fixtures are using the wrong time zone and the `:first`
382
483
  # `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
383
484
  # getting local EST time for me and set to "09:28:00.0000000".
384
485
  coerce_tests! :test_pretty_print_persisted
@@ -404,14 +505,140 @@ end
404
505
 
405
506
 
406
507
  module ActiveRecord
508
+ # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
509
+ module DatabaseTasksSetupper
510
+ def setup
511
+ @sqlserver_tasks =
512
+ Class.new do
513
+ def create; end
514
+ def drop; end
515
+ def purge; end
516
+ def charset; end
517
+ def collation; end
518
+ def structure_dump(*); end
519
+ def structure_load(*); end
520
+ end.new
521
+
522
+ $stdout, @original_stdout = StringIO.new, $stdout
523
+ $stderr, @original_stderr = StringIO.new, $stderr
524
+ end
525
+
526
+ def with_stubbed_new
527
+ ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
528
+ yield
529
+ end
530
+ end
531
+ end
532
+
533
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
534
+ # Coerce PostgreSQL/SQLite/MySQL tests.
535
+ coerce_all_tests!
536
+
537
+ def test_sqlserver_create
538
+ with_stubbed_new do
539
+ assert_called(eval("@sqlserver_tasks"), :create) do
540
+ ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
541
+ end
542
+ end
543
+ end
544
+ end
545
+
546
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
547
+ # Coerce PostgreSQL/SQLite/MySQL tests.
548
+ coerce_all_tests!
549
+
550
+ def test_sqlserver_drop
551
+ with_stubbed_new do
552
+ assert_called(eval("@sqlserver_tasks"), :drop) do
553
+ ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
554
+ end
555
+ end
556
+ end
557
+ end
558
+
559
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
560
+ # Coerce PostgreSQL/SQLite/MySQL tests.
561
+ coerce_all_tests!
562
+
563
+ def test_sqlserver_purge
564
+ with_stubbed_new do
565
+ assert_called(eval("@sqlserver_tasks"), :purge) do
566
+ ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
567
+ end
568
+ end
569
+ end
570
+ end
571
+
572
+ class DatabaseTasksCharsetTest < ActiveRecord::TestCase
573
+ # Coerce PostgreSQL/SQLite/MySQL tests.
574
+ coerce_all_tests!
575
+
576
+ def test_sqlserver_charset
577
+ with_stubbed_new do
578
+ assert_called(eval("@sqlserver_tasks"), :charset) do
579
+ ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
580
+ end
581
+ end
582
+ end
583
+
584
+ end
585
+
586
+ class DatabaseTasksCollationTest < ActiveRecord::TestCase
587
+ # Coerce PostgreSQL/SQLite/MySQL tests.
588
+ coerce_all_tests!
589
+
590
+ def test_sqlserver_collation
591
+ with_stubbed_new do
592
+ assert_called(eval("@sqlserver_tasks"), :collation) do
593
+ ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
594
+ end
595
+ end
596
+ end
597
+ end
598
+
599
+ class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
600
+ # Coerce PostgreSQL/SQLite/MySQL tests.
601
+ coerce_all_tests!
602
+
603
+ def test_sqlserver_structure_dump
604
+ with_stubbed_new do
605
+ assert_called_with(
606
+ eval("@sqlserver_tasks"), :structure_dump,
607
+ ["awesome-file.sql", nil]
608
+ ) do
609
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
610
+ end
611
+ end
612
+ end
613
+ end
614
+
615
+ class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
616
+ # Coerce PostgreSQL/SQLite/MySQL tests.
617
+ coerce_all_tests!
618
+
619
+ def test_sqlserver_structure_load
620
+ with_stubbed_new do
621
+ assert_called_with(
622
+ eval("@sqlserver_tasks"),
623
+ :structure_load,
624
+ ["awesome-file.sql", nil]
625
+ ) do
626
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
627
+ end
628
+ end
629
+ end
630
+ end
631
+
407
632
  class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
408
633
  # Skip this test with /tmp/my_schema_cache.yml path on Windows.
409
634
  coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
410
635
  end
636
+
411
637
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
412
638
  # We extend `local_database?` so that common VM IPs can be used.
413
639
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
414
640
  end
641
+
415
642
  class DatabaseTasksDropAllTest < ActiveRecord::TestCase
416
643
  # We extend `local_database?` so that common VM IPs can be used.
417
644
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
@@ -484,9 +711,11 @@ end
484
711
 
485
712
  require 'models/topic'
486
713
  class FinderTest < ActiveRecord::TestCase
714
+ # We have implicit ordering, via FETCH.
487
715
  coerce_tests! %r{doesn't have implicit ordering},
488
- :test_find_doesnt_have_implicit_ordering # We have implicit ordering, via FETCH.
716
+ :test_find_doesnt_have_implicit_ordering
489
717
 
718
+ # Square brackets around column name
490
719
  coerce_tests! :test_exists_does_not_select_columns_without_alias
491
720
  def test_exists_does_not_select_columns_without_alias_coerced
492
721
  assert_sql(/SELECT\s+1 AS one FROM \[topics\].*OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) do
@@ -494,6 +723,7 @@ class FinderTest < ActiveRecord::TestCase
494
723
  end
495
724
  end
496
725
 
726
+ # Assert SQL Server limit implementation
497
727
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
498
728
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
499
729
  assert_sql(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
@@ -553,13 +783,40 @@ end
553
783
  class HasOneAssociationsTest < ActiveRecord::TestCase
554
784
  # We use OFFSET/FETCH vs TOP. So we always have an order.
555
785
  coerce_tests! :test_has_one_does_not_use_order_by
786
+
787
+ # Asserted SQL to get one row different from original test.
788
+ coerce_tests! :test_has_one
789
+ def test_has_one_coerced
790
+ firm = companies(:first_firm)
791
+ first_account = Account.find(1)
792
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
793
+ assert_equal first_account, firm.account
794
+ assert_equal first_account.credit_limit, firm.account.credit_limit
795
+ end
796
+ end
797
+ end
798
+
799
+
800
+
801
+
802
+ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
803
+ # Asserted SQL to get one row different from original test.
804
+ coerce_tests! :test_has_one_through_executes_limited_query
805
+ def test_has_one_through_executes_limited_query_coerced
806
+ boring_club = clubs(:boring_club)
807
+ assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
808
+ assert_equal boring_club, @member.general_club
809
+ end
810
+ end
556
811
  end
557
812
 
558
813
 
559
814
 
560
815
 
816
+
561
817
  require 'models/company'
562
818
  class InheritanceTest < ActiveRecord::TestCase
819
+ # Rails test required inserting to a identity column.
563
820
  coerce_tests! :test_a_bad_type_column
564
821
  def test_a_bad_type_column_coerced
565
822
  Company.connection.with_identity_insert_enabled('companies') do
@@ -568,6 +825,7 @@ class InheritanceTest < ActiveRecord::TestCase
568
825
  assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
569
826
  end
570
827
 
828
+ # Use Square brackets around column name
571
829
  coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
572
830
  def test_eager_load_belongs_to_primary_key_quoting_coerced
573
831
  con = Account.connection
@@ -588,26 +846,10 @@ end
588
846
 
589
847
 
590
848
 
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
849
  require 'models/developer'
609
850
  require 'models/computer'
610
851
  class NestedRelationScopingTest < ActiveRecord::TestCase
852
+ # Assert SQL Server limit implementation
611
853
  coerce_tests! :test_merge_options
612
854
  def test_merge_options_coerced
613
855
  Developer.where('salary = 80000').scoping do
@@ -624,13 +866,36 @@ end
624
866
 
625
867
 
626
868
 
627
- require 'models/parrot'
628
869
  require 'models/topic'
629
870
  class PersistenceTest < ActiveRecord::TestCase
630
- # We can not UPDATE identity columns.
871
+ # Rails test required updating a identity column.
631
872
  coerce_tests! :test_update_columns_changing_id
632
873
 
633
- # Previous test required updating a identity column.
874
+ # Rails test required updating a identity column.
875
+ coerce_tests! :test_update
876
+ def test_update_coerced
877
+ topic = Topic.find(1)
878
+ assert_not_predicate topic, :approved?
879
+ assert_equal "The First Topic", topic.title
880
+
881
+ topic.update("approved" => true, "title" => "The First Topic Updated")
882
+ topic.reload
883
+ assert_predicate topic, :approved?
884
+ assert_equal "The First Topic Updated", topic.title
885
+
886
+ topic.update(approved: false, title: "The First Topic")
887
+ topic.reload
888
+ assert_not_predicate topic, :approved?
889
+ assert_equal "The First Topic", topic.title
890
+ end
891
+ end
892
+
893
+
894
+
895
+
896
+ require 'models/author'
897
+ class UpdateAllTest < ActiveRecord::TestCase
898
+ # Rails test required updating a identity column.
634
899
  coerce_tests! :test_update_all_doesnt_ignore_order
635
900
  def test_update_all_doesnt_ignore_order_coerced
636
901
  david, mary = authors(:david), authors(:mary)
@@ -643,30 +908,6 @@ class PersistenceTest < ActiveRecord::TestCase
643
908
  _(david.reload.name).must_equal 'David'
644
909
  _(mary.reload.name).must_equal 'Test'
645
910
  end
646
-
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
669
- end
670
911
  end
671
912
 
672
913
 
@@ -675,14 +916,16 @@ end
675
916
  require 'models/topic'
676
917
  module ActiveRecord
677
918
  class PredicateBuilderTest < ActiveRecord::TestCase
919
+ # Same as original test except string has `N` prefix to indicate unicode string.
678
920
  coerce_tests! :test_registering_new_handlers
679
921
  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
922
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Topic.where(title: /rails/).to_sql
923
+ end
924
+
925
+ # Same as original test except string has `N` prefix to indicate unicode string.
926
+ coerce_tests! :test_registering_new_handlers_for_association
927
+ def test_registering_new_handlers_for_association_coerced
928
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
686
929
  end
687
930
  end
688
931
  end
@@ -691,9 +934,15 @@ end
691
934
 
692
935
 
693
936
  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.
937
+ # SQL Server does not have query for release_savepoint
696
938
  coerce_tests! :test_create_without_primary_key_no_extra_query
939
+ def test_create_without_primary_key_no_extra_query_coerced
940
+ klass = Class.new(ActiveRecord::Base) do
941
+ self.table_name = "dashboards"
942
+ end
943
+ klass.create! # warmup schema cache
944
+ assert_queries(2, ignore_none: true) { klass.create! }
945
+ end
697
946
  end
698
947
 
699
948
 
@@ -701,10 +950,37 @@ end
701
950
 
702
951
  require 'models/task'
703
952
  class QueryCacheTest < ActiveRecord::TestCase
953
+ # SQL Server adapter not in list of supported adapters in original test.
704
954
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
705
955
  def test_cache_does_not_wrap_results_in_arrays_coerced
706
956
  Task.cache do
707
- assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
957
+ assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
958
+ end
959
+ end
960
+
961
+ # Same as original test except that we expect one query to be performed to retrieve the table's primary key.
962
+ # When we generate the SQL for the `find` it includes ordering on the primary key. If we reset the column
963
+ # information then the primary key needs to be retrieved from the database again to generate the SQL causing the
964
+ # original test's `assert_no_queries` assertion to fail. Assert that the query was to get the primary key.
965
+ coerce_tests! :test_query_cached_even_when_types_are_reset
966
+ def test_query_cached_even_when_types_are_reset_coerced
967
+ Task.cache do
968
+ # Warm the cache
969
+ Task.find(1)
970
+
971
+ # Preload the type cache again (so we don't have those queries issued during our assertions)
972
+ Task.connection.send(:reload_type_map)
973
+
974
+ # Clear places where type information is cached
975
+ Task.reset_column_information
976
+ Task.initialize_find_by_cache
977
+ Task.define_attribute_methods
978
+
979
+ assert_queries(1, ignore_none: true) do
980
+ Task.find(1)
981
+ end
982
+
983
+ assert_includes ActiveRecord::SQLCounter.log_all.first , "TC.CONSTRAINT_TYPE = N''PRIMARY KEY''"
708
984
  end
709
985
  end
710
986
  end
@@ -733,6 +1009,27 @@ class RelationTest < ActiveRecord::TestCase
733
1009
  # We have implicit ordering, via FETCH.
734
1010
  coerce_tests! %r{doesn't have implicit ordering}
735
1011
 
1012
+ # We have implicit ordering, via FETCH.
1013
+ coerce_tests! :test_reorder_with_take
1014
+ def test_reorder_with_take_coerced
1015
+ sql_log = capture_sql do
1016
+ assert Post.order(:title).reorder(nil).take
1017
+ end
1018
+ assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1019
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1020
+ end
1021
+
1022
+ # We have implicit ordering, via FETCH.
1023
+ coerce_tests! :test_reorder_with_first
1024
+ def test_reorder_with_first_coerced
1025
+ sql_log = capture_sql do
1026
+ assert Post.order(:title).reorder(nil).first
1027
+ end
1028
+ assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1029
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1030
+ end
1031
+
1032
+
736
1033
  # We are not doing order duplicate removal anymore.
737
1034
  coerce_tests! :test_order_using_scoping
738
1035
 
@@ -756,7 +1053,7 @@ class RelationTest < ActiveRecord::TestCase
756
1053
  # so we are skipping all together.
757
1054
  coerce_tests! :test_empty_complex_chained_relations
758
1055
 
759
- # Can't apply offset withour ORDER
1056
+ # Can't apply offset without ORDER
760
1057
  coerce_tests! %r{using a custom table affects the wheres}
761
1058
  test 'using a custom table affects the wheres coerced' do
762
1059
  post = posts(:welcome)
@@ -764,7 +1061,7 @@ class RelationTest < ActiveRecord::TestCase
764
1061
  assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
765
1062
  end
766
1063
 
767
- # Can't apply offset withour ORDER
1064
+ # Can't apply offset without ORDER
768
1065
  coerce_tests! %r{using a custom table with joins affects the joins}
769
1066
  test 'using a custom table with joins affects the joins coerced' do
770
1067
  post = posts(:welcome)
@@ -780,38 +1077,30 @@ class RelationTest < ActiveRecord::TestCase
780
1077
  end
781
1078
  end
782
1079
 
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)
795
- end
796
-
797
- assert query_matches, "Should be aliasing the child INNER JOINs in query"
798
- end
799
- end
800
-
801
1080
 
802
1081
 
803
1082
 
804
1083
  require 'models/post'
805
1084
  class SanitizeTest < ActiveRecord::TestCase
1085
+ # Use nvarchar string (N'') in assert
806
1086
  coerce_tests! :test_sanitize_sql_like_example_use_case
807
1087
  def test_sanitize_sql_like_example_use_case_coerced
808
1088
  searchable_post = Class.new(Post) do
809
- def self.search(term)
810
- where("title LIKE ?", sanitize_sql_like(term, '!'))
1089
+ def self.search_as_method(term)
1090
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
811
1091
  end
1092
+
1093
+ scope :search_as_scope, -> (term) {
1094
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
1095
+ }
812
1096
  end
813
- assert_sql(/\(title LIKE N'20!% !_reduction!_!!'\)/) do
814
- searchable_post.search("20% _reduction_!").to_a
1097
+
1098
+ assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1099
+ searchable_post.search_as_method("20% _reduction_!").to_a
1100
+ end
1101
+
1102
+ assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1103
+ searchable_post.search_as_scope("20% _reduction_!").to_a
815
1104
  end
816
1105
  end
817
1106
  end
@@ -845,6 +1134,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
845
1134
  end
846
1135
  end
847
1136
 
1137
+
1138
+
1139
+
848
1140
  class SchemaDumperDefaultsTest < ActiveRecord::TestCase
849
1141
  # These date formats do not match ours. We got these covered in our dumper tests.
850
1142
  coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
@@ -863,6 +1155,7 @@ end
863
1155
 
864
1156
  require 'models/topic'
865
1157
  class TransactionTest < ActiveRecord::TestCase
1158
+ # SQL Server does not have query for release_savepoint
866
1159
  coerce_tests! :test_releasing_named_savepoints
867
1160
  def test_releasing_named_savepoints_coerced
868
1161
  Topic.transaction do
@@ -915,6 +1208,10 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
915
1208
  assert_equal 'id', model.primary_key
916
1209
  end
917
1210
  end
1211
+
1212
+
1213
+
1214
+
918
1215
  class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
919
1216
  # We have a few view tables. use includes vs equality.
920
1217
  coerce_tests! :test_views
@@ -974,6 +1271,7 @@ end
974
1271
 
975
1272
 
976
1273
 
1274
+
977
1275
  class TimePrecisionTest < ActiveRecord::TestCase
978
1276
  # datetime is rounded to increments of .000, .003, or .007 seconds
979
1277
  coerce_tests! :test_time_precision_is_truncated_on_assignment
@@ -994,10 +1292,14 @@ class TimePrecisionTest < ActiveRecord::TestCase
994
1292
  assert_equal 0, foo.start.nsec
995
1293
  assert_equal 123457000, foo.finish.nsec
996
1294
  end
1295
+
1296
+ # SQL Server uses default precision for time.
1297
+ coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
997
1298
  end
998
1299
 
999
1300
 
1000
1301
 
1302
+
1001
1303
  class DefaultNumbersTest < ActiveRecord::TestCase
1002
1304
  # We do better with native types and do not return strings for everything.
1003
1305
  coerce_tests! :test_default_positive_integer
@@ -1006,12 +1308,22 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1006
1308
  assert_equal 7, record.positive_integer
1007
1309
  assert_equal 7, record.positive_integer_before_type_cast
1008
1310
  end
1311
+
1312
+ # We do better with native types and do not return strings for everything.
1009
1313
  coerce_tests! :test_default_negative_integer
1010
1314
  def test_default_negative_integer_coerced
1011
1315
  record = DefaultNumber.new
1012
1316
  assert_equal -5, record.negative_integer
1013
1317
  assert_equal -5, record.negative_integer_before_type_cast
1014
1318
  end
1319
+
1320
+ # We do better with native types and do not return strings for everything.
1321
+ coerce_tests! :test_default_decimal_number
1322
+ def test_default_decimal_number_coerced
1323
+ record = DefaultNumber.new
1324
+ assert_equal BigDecimal("2.78"), record.decimal_number
1325
+ assert_equal 2.78, record.decimal_number_before_type_cast
1326
+ end
1015
1327
  end
1016
1328
 
1017
1329
 
@@ -1027,10 +1339,34 @@ end
1027
1339
 
1028
1340
 
1029
1341
 
1342
+ module ActiveRecord
1343
+ class CacheKeyTest < ActiveRecord::TestCase
1344
+ # Like Mysql2 and PostgreSQL, SQL Server doesn't return a string value for updated_at. In the Rails tests
1345
+ # the tests are skipped if adapter is Mysql2 or PostgreSQL.
1346
+ coerce_tests! %r{cache_version is the same when it comes from the DB or from the user}
1347
+ coerce_tests! %r{cache_version does NOT call updated_at when value is from the database}
1348
+ coerce_tests! %r{cache_version does not truncate zeros when timestamp ends in zeros}
1349
+ end
1350
+ end
1351
+
1352
+
1353
+
1354
+
1355
+ require "models/book"
1030
1356
  module ActiveRecord
1031
1357
  class StatementCacheTest < ActiveRecord::TestCase
1032
1358
  # Getting random failures.
1033
1359
  coerce_tests! :test_find_does_not_use_statement_cache_if_table_name_is_changed
1360
+
1361
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1362
+ coerce_tests! :test_statement_cache_values_differ
1363
+ def test_statement_cache_values_differ_coerced
1364
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1365
+
1366
+ original_test_statement_cache_values_differ
1367
+ ensure
1368
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1369
+ end
1034
1370
  end
1035
1371
  end
1036
1372
 
@@ -1049,8 +1385,12 @@ module ActiveRecord
1049
1385
  end
1050
1386
  end
1051
1387
 
1388
+
1389
+
1390
+
1052
1391
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1053
- coerce_tests! %r{always allows Arel}
1392
+ # Use LEN() vs length() function.
1393
+ coerce_tests! %r{order: always allows Arel}
1054
1394
  test 'order: always allows Arel' do
1055
1395
  ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1056
1396
  ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
@@ -1058,6 +1398,8 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1058
1398
  assert_equal ids_depr, ids_disabled
1059
1399
  end
1060
1400
 
1401
+ # Use LEN() vs length() function.
1402
+ coerce_tests! %r{pluck: always allows Arel}
1061
1403
  test "pluck: always allows Arel" do
1062
1404
  values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1063
1405
  values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
@@ -1065,72 +1407,20 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1065
1407
  assert_equal values_depr, values_disabled
1066
1408
  end
1067
1409
 
1068
-
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
1076
- end
1077
-
1410
+ # Use LEN() vs length() function.
1078
1411
  coerce_tests! %r{order: allows valid Array arguments}
1079
1412
  test "order: allows valid Array arguments" do
1080
1413
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1081
1414
 
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) }
1415
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1416
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "len(title)"]).pluck(:id) }
1084
1417
 
1085
1418
  assert_equal ids_expected, ids_depr
1086
1419
  assert_equal ids_expected, ids_disabled
1087
1420
  end
1421
+ end
1088
1422
 
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
1096
- end
1097
1423
 
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
1134
1424
 
1135
1425
 
1136
1426
  class ReservedWordTest < ActiveRecord::TestCase
@@ -1145,6 +1435,7 @@ end
1145
1435
 
1146
1436
 
1147
1437
 
1438
+
1148
1439
  class OptimisticLockingTest < ActiveRecord::TestCase
1149
1440
  # We do not allow updating identities, but we can test using a non-identity key
1150
1441
  coerce_tests! :test_update_with_dirty_primary_key
@@ -1168,7 +1459,9 @@ end
1168
1459
 
1169
1460
 
1170
1461
 
1462
+
1171
1463
  class RelationMergingTest < ActiveRecord::TestCase
1464
+ # Use nvarchar string (N'') in assert
1172
1465
  coerce_tests! :test_merging_with_order_with_binds
1173
1466
  def test_merging_with_order_with_binds_coerced
1174
1467
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
@@ -1177,7 +1470,171 @@ class RelationMergingTest < ActiveRecord::TestCase
1177
1470
  end
1178
1471
 
1179
1472
 
1473
+
1474
+
1475
+ module ActiveRecord
1476
+ class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
1477
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
1478
+ # constraints. As this test truncates all tables we would need to remove all foreign
1479
+ # key constraints and then restore them afterwards to get this test to pass.
1480
+ coerce_tests! :test_truncate_tables
1481
+ end
1482
+ end
1483
+
1484
+
1485
+
1486
+
1487
+ require "models/book"
1488
+ class EnumTest < ActiveRecord::TestCase
1489
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1490
+ coerce_tests! %r{enums are distinct per class}
1491
+ test "enums are distinct per class coerced" do
1492
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1493
+
1494
+ send(:'original_enums are distinct per class')
1495
+ ensure
1496
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1497
+ end
1498
+
1499
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1500
+ coerce_tests! %r{creating new objects with enum scopes}
1501
+ test "creating new objects with enum scopes coerced" do
1502
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1503
+
1504
+ send(:'original_creating new objects with enum scopes')
1505
+ ensure
1506
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1507
+ end
1508
+
1509
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1510
+ coerce_tests! %r{enums are inheritable}
1511
+ test "enums are inheritable coerced" do
1512
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1513
+
1514
+ send(:'original_enums are inheritable')
1515
+ ensure
1516
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1517
+ end
1518
+
1519
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1520
+ coerce_tests! %r{declare multiple enums at a time}
1521
+ test "declare multiple enums at a time coerced" do
1522
+ Book.connection.remove_index(:books, column: [:author_id, :name])
1523
+
1524
+ send(:'original_declare multiple enums at a time')
1525
+ ensure
1526
+ Book.connection.add_index(:books, [:author_id, :name], unique: true)
1527
+ end
1528
+ end
1529
+
1530
+
1531
+
1532
+
1533
+
1534
+ require 'models/task'
1535
+ class QueryCacheExpiryTest < ActiveRecord::TestCase
1536
+
1537
+ # SQL Server does not support skipping or upserting duplicates.
1538
+ coerce_tests! :test_insert_all
1539
+ def test_insert_all_coerced
1540
+ assert_raises(ArgumentError, /does not support skipping duplicates/) do
1541
+ Task.cache { Task.insert({ starting: Time.now }) }
1542
+ end
1543
+
1544
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1545
+ Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1546
+ end
1547
+
1548
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1549
+ Task.cache { Task.insert!({ starting: Time.now }) }
1550
+ end
1551
+
1552
+ assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1553
+ Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1554
+ end
1555
+
1556
+ assert_raises(ArgumentError, /does not support upsert/) do
1557
+ Task.cache { Task.upsert({ starting: Time.now }) }
1558
+ end
1559
+
1560
+ assert_raises(ArgumentError, /does not support upsert/) do
1561
+ Task.cache { Task.upsert_all([{ starting: Time.now }]) }
1562
+ end
1563
+ end
1564
+ end
1565
+
1566
+
1567
+
1568
+ require 'models/citation'
1180
1569
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1181
- # Temporarily coerce this test due to https://github.com/rails/rails/issues/34945
1570
+ # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1571
+ # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1572
+ # (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
1573
+ # However, you cannot change the compatibility level during a test. The purpose of the test is to ensure that an
1574
+ # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1575
+ # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1576
+ # adapter is 2,098.
1182
1577
  coerce_tests! :test_eager_loading_too_may_ids
1578
+ def test_eager_loading_too_may_ids_coerced
1579
+ # Remove excess records.
1580
+ Citation.limit(32768).order(id: :desc).delete_all
1581
+
1582
+ # Perform test
1583
+ citation_count = Citation.count
1584
+ assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1585
+ assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1586
+ end
1587
+ end
1588
+ end
1589
+
1590
+
1591
+
1592
+
1593
+ class LogSubscriberTest < ActiveRecord::TestCase
1594
+ # Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
1595
+ coerce_tests! :test_vebose_query_logs
1596
+ def test_vebose_query_logs_coerced
1597
+ original_test_vebose_query_logs
1598
+ end
1599
+ end
1600
+
1601
+
1602
+
1603
+
1604
+ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1605
+ # Workaround for randomly failing test.
1606
+ coerce_tests! :test_has_primary_key
1607
+ def test_has_primary_key_coerced
1608
+ @schema_migration.reset_column_information
1609
+ original_test_has_primary_key
1610
+ end
1611
+ end
1612
+
1613
+
1614
+
1615
+
1616
+ module ActiveRecord
1617
+ module ConnectionAdapters
1618
+ class ReaperTest < ActiveRecord::TestCase
1619
+ # Coerce can be removed if Rails version > 6.0.3
1620
+ coerce_tests! :test_connection_pool_starts_reaper_in_fork unless Process.respond_to?(:fork)
1621
+ end
1622
+ end
1623
+ end
1624
+
1625
+
1626
+
1627
+
1628
+ class FixturesTest < ActiveRecord::TestCase
1629
+ # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1630
+ coerce_tests! :test_binary_in_fixtures if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
1631
+ end
1632
+
1633
+
1634
+
1635
+
1636
+ class ReloadModelsTest < ActiveRecord::TestCase
1637
+ # Skip test on Windows. The number of arguements passed to `IO.popen` in
1638
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1639
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
1183
1640
  end