activerecord-sqlserver-adapter 5.2.1 → 6.0.0.rc1

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