activerecord-sqlserver-adapter-odbc-extended 8.0.10

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +22 -0
  3. data/.github/workflows/ci.yml +32 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +69 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/Dockerfile.ci +14 -0
  9. data/Gemfile +26 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +104 -0
  12. data/RUNNING_UNIT_TESTS.md +38 -0
  13. data/Rakefile +45 -0
  14. data/VERSION +1 -0
  15. data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
  16. data/compose.ci.yaml +15 -0
  17. data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
  19. data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
  21. data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
  22. data/test/appveyor/dbsetup.ps1 +27 -0
  23. data/test/appveyor/dbsetup.sql +11 -0
  24. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  25. data/test/cases/adapter_test_sqlserver.rb +648 -0
  26. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  27. data/test/cases/change_column_null_test_sqlserver.rb +44 -0
  28. data/test/cases/coerced_tests.rb +2796 -0
  29. data/test/cases/column_test_sqlserver.rb +848 -0
  30. data/test/cases/connection_test_sqlserver.rb +138 -0
  31. data/test/cases/dbconsole.rb +19 -0
  32. data/test/cases/disconnected_test_sqlserver.rb +42 -0
  33. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  34. data/test/cases/enum_test_sqlserver.rb +49 -0
  35. data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
  36. data/test/cases/fetch_test_sqlserver.rb +88 -0
  37. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
  38. data/test/cases/helper_sqlserver.rb +61 -0
  39. data/test/cases/migration_test_sqlserver.rb +144 -0
  40. data/test/cases/order_test_sqlserver.rb +153 -0
  41. data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
  42. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  43. data/test/cases/rake_test_sqlserver.rb +198 -0
  44. data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
  45. data/test/cases/schema_test_sqlserver.rb +111 -0
  46. data/test/cases/trigger_test_sqlserver.rb +51 -0
  47. data/test/cases/utils_test_sqlserver.rb +129 -0
  48. data/test/cases/uuid_test_sqlserver.rb +54 -0
  49. data/test/cases/view_test_sqlserver.rb +58 -0
  50. data/test/config.yml +38 -0
  51. data/test/debug.rb +16 -0
  52. data/test/fixtures/1px.gif +0 -0
  53. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  54. data/test/migrations/create_clients_and_change_column_null.rb +25 -0
  55. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  56. data/test/models/sqlserver/alien.rb +5 -0
  57. data/test/models/sqlserver/booking.rb +5 -0
  58. data/test/models/sqlserver/composite_pk.rb +9 -0
  59. data/test/models/sqlserver/customers_view.rb +5 -0
  60. data/test/models/sqlserver/datatype.rb +5 -0
  61. data/test/models/sqlserver/datatype_migration.rb +10 -0
  62. data/test/models/sqlserver/dollar_table_name.rb +5 -0
  63. data/test/models/sqlserver/edge_schema.rb +13 -0
  64. data/test/models/sqlserver/fk_has_fk.rb +5 -0
  65. data/test/models/sqlserver/fk_has_pk.rb +5 -0
  66. data/test/models/sqlserver/natural_pk_data.rb +6 -0
  67. data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
  68. data/test/models/sqlserver/no_pk_data.rb +5 -0
  69. data/test/models/sqlserver/object_default.rb +5 -0
  70. data/test/models/sqlserver/quoted_table.rb +9 -0
  71. data/test/models/sqlserver/quoted_view_1.rb +5 -0
  72. data/test/models/sqlserver/quoted_view_2.rb +5 -0
  73. data/test/models/sqlserver/sst_memory.rb +5 -0
  74. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  75. data/test/models/sqlserver/string_default.rb +5 -0
  76. data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
  77. data/test/models/sqlserver/string_defaults_view.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/tinyint_pk.rb +5 -0
  80. data/test/models/sqlserver/trigger.rb +17 -0
  81. data/test/models/sqlserver/trigger_history.rb +5 -0
  82. data/test/models/sqlserver/upper.rb +5 -0
  83. data/test/models/sqlserver/uppered.rb +5 -0
  84. data/test/models/sqlserver/uuid.rb +5 -0
  85. data/test/schema/datatypes/2012.sql +56 -0
  86. data/test/schema/enable-in-memory-oltp.sql +81 -0
  87. data/test/schema/sqlserver_specific_schema.rb +363 -0
  88. data/test/support/coerceable_test_sqlserver.rb +55 -0
  89. data/test/support/connection_reflection.rb +32 -0
  90. data/test/support/core_ext/query_cache.rb +38 -0
  91. data/test/support/load_schema_sqlserver.rb +29 -0
  92. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  93. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  94. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  96. data/test/support/minitest_sqlserver.rb +3 -0
  97. data/test/support/paths_sqlserver.rb +50 -0
  98. data/test/support/query_assertions.rb +49 -0
  99. data/test/support/rake_helpers.rb +46 -0
  100. data/test/support/table_definition_sqlserver.rb +24 -0
  101. data/test/support/test_in_memory_oltp.rb +17 -0
  102. metadata +240 -0
@@ -0,0 +1,2796 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ require "models/event"
6
+ class UniquenessValidationTest < ActiveRecord::TestCase
7
+ # So sp_executesql swallows this exception. Run without prepared to see it.
8
+ coerce_tests! :test_validate_uniqueness_with_limit
9
+ def test_validate_uniqueness_with_limit_coerced
10
+ connection.unprepared_statement do
11
+ assert_raise(ActiveRecord::ValueTooLong) do
12
+ Event.create(title: "abcdefgh")
13
+ end
14
+ end
15
+ end
16
+
17
+ # So sp_executesql swallows this exception. Run without prepared to see it.
18
+ coerce_tests! :test_validate_uniqueness_with_limit_and_utf8
19
+ def test_validate_uniqueness_with_limit_and_utf8_coerced
20
+ connection.unprepared_statement do
21
+ assert_raise(ActiveRecord::ValueTooLong) do
22
+ Event.create(title: "一二三四五六七八")
23
+ end
24
+ end
25
+ end
26
+
27
+ # Same as original coerced test except that it handles default SQL Server case-insensitive collation.
28
+ coerce_tests! :test_validate_uniqueness_by_default_database_collation
29
+ def test_validate_uniqueness_by_default_database_collation_coerced
30
+ Topic.validates_uniqueness_of(:author_email_address)
31
+
32
+ topic1 = Topic.new(author_email_address: "david@loudthinking.com")
33
+ topic2 = Topic.new(author_email_address: "David@loudthinking.com")
34
+
35
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
36
+
37
+ assert_not topic1.valid?
38
+ assert_not topic1.save
39
+
40
+ # Case insensitive collation (SQL_Latin1_General_CP1_CI_AS) by default.
41
+ # Should not allow "David" if "david" exists.
42
+ assert_not topic2.valid?
43
+ assert_not topic2.save
44
+
45
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
46
+ assert_equal 1, Topic.where(author_email_address: "David@loudthinking.com").count
47
+ end
48
+ end
49
+
50
+ class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
51
+ # Need to explicitly set the WHERE clause to truthy.
52
+ coerce_tests! :test_partial_index
53
+ def test_partial_index_coerced
54
+ Topic.validates_uniqueness_of(:title)
55
+ @connection.add_index(:topics, :title, unique: true, where: "approved=1", name: :topics_index)
56
+
57
+ t = Topic.create!(title: "abc")
58
+ t.author_name = "John"
59
+ assert_queries_count(1) do
60
+ t.valid?
61
+ end
62
+ end
63
+ end
64
+
65
+ require "models/event"
66
+ module ActiveRecord
67
+ class AdapterTest < ActiveRecord::TestCase
68
+ # Legacy binds are not supported.
69
+ coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
70
+
71
+ # As far as I can tell, SQL Server does not support null bytes in strings.
72
+ coerce_tests! :test_update_prepared_statement
73
+
74
+ # So sp_executesql swallows this exception. Run without prepared to see it.
75
+ coerce_tests! :test_value_limit_violations_are_translated_to_specific_exception
76
+ def test_value_limit_violations_are_translated_to_specific_exception_coerced
77
+ connection.unprepared_statement do
78
+ error = assert_raises(ActiveRecord::ValueTooLong) do
79
+ Event.create(title: "abcdefgh")
80
+ end
81
+ assert_not_nil error.cause
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ module ActiveRecord
88
+ class AdapterPreventWritesTest < ActiveRecord::TestCase
89
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
90
+ coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
91
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
92
+ Subscriber.send(:load_schema!)
93
+ original_test_errors_when_an_insert_query_is_called_while_preventing_writes
94
+ end
95
+
96
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
97
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
98
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
99
+ Subscriber.send(:load_schema!)
100
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
101
+ end
102
+
103
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
104
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
105
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
106
+ Subscriber.send(:load_schema!)
107
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
108
+ end
109
+
110
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
111
+ coerce_tests! :test_doesnt_error_when_a_select_query_has_encoding_errors
112
+ def test_doesnt_error_when_a_select_query_has_encoding_errors_coerced
113
+ ActiveRecord::Base.while_preventing_writes do
114
+ # TinyTDS fail on encoding errors.
115
+ # But at least we can assert it fails in the client and not before when trying to match the query.
116
+ assert_raises ActiveRecord::StatementInvalid do
117
+ @connection.select_all("SELECT '\xC8'")
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ module ActiveRecord
125
+ class AdapterTestWithoutTransaction < ActiveRecord::TestCase
126
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
127
+ # constraints. So manually remove/add foreign keys in test.
128
+ coerce_tests! :test_truncate_tables
129
+ def test_truncate_tables_coerced
130
+ # Remove foreign key constraint to allow truncation.
131
+ @connection.remove_foreign_key :authors, :author_addresses
132
+
133
+ assert_operator Post.count, :>, 0
134
+ assert_operator Author.count, :>, 0
135
+ assert_operator AuthorAddress.count, :>, 0
136
+
137
+ @connection.truncate_tables("author_addresses", "authors", "posts")
138
+
139
+ assert_equal 0, Post.count
140
+ assert_equal 0, Author.count
141
+ assert_equal 0, AuthorAddress.count
142
+ ensure
143
+ reset_fixtures("posts", "authors", "author_addresses")
144
+
145
+ # Restore foreign key constraint.
146
+ @connection.add_foreign_key :authors, :author_addresses
147
+ end
148
+
149
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
150
+ # constraints. So manually remove/add foreign keys in test.
151
+ coerce_tests! :test_truncate_tables_with_query_cache
152
+ def test_truncate_tables_with_query_cache
153
+ # Remove foreign key constraint to allow truncation.
154
+ @connection.remove_foreign_key :authors, :author_addresses
155
+
156
+ @connection.enable_query_cache!
157
+
158
+ assert_operator Post.count, :>, 0
159
+ assert_operator Author.count, :>, 0
160
+ assert_operator AuthorAddress.count, :>, 0
161
+
162
+ @connection.truncate_tables("author_addresses", "authors", "posts")
163
+
164
+ assert_equal 0, Post.count
165
+ assert_equal 0, Author.count
166
+ assert_equal 0, AuthorAddress.count
167
+ ensure
168
+ reset_fixtures("posts", "authors", "author_addresses")
169
+ @connection.disable_query_cache!
170
+
171
+ # Restore foreign key constraint.
172
+ @connection.add_foreign_key :authors, :author_addresses
173
+ end
174
+ end
175
+ end
176
+
177
+ require "models/topic"
178
+ class AttributeMethodsTest < ActiveRecord::TestCase
179
+ # Use IFF for boolean statement in SELECT
180
+ coerce_tests! %r{typecast attribute from select to false}
181
+ def test_typecast_attribute_from_select_to_false_coerced
182
+ Topic.create(:title => "Budget")
183
+ topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
184
+ assert_not_predicate topic, :is_test?
185
+ end
186
+
187
+ # Use IFF for boolean statement in SELECT
188
+ coerce_tests! %r{typecast attribute from select to true}
189
+ def test_typecast_attribute_from_select_to_true_coerced
190
+ Topic.create(:title => "Budget")
191
+ topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
192
+ assert_predicate topic, :is_test?
193
+ end
194
+ end
195
+
196
+ class BasicsTest < ActiveRecord::TestCase
197
+ # Use square brackets as SQL Server escaped character
198
+ coerce_tests! :test_column_names_are_escaped
199
+ def test_column_names_are_escaped_coerced
200
+ conn = ActiveRecord::Base.lease_connection
201
+ assert_equal "[t]]]", conn.quote_column_name("t]")
202
+ end
203
+
204
+ # Just like PostgreSQLAdapter does.
205
+ coerce_tests! :test_respect_internal_encoding
206
+
207
+ # Caused in Rails v4.2.5 by adding `firm_id` column in this http://git.io/vBfMs
208
+ # commit. Trust Rails has this covered.
209
+ coerce_tests! :test_find_keeps_multiple_group_values
210
+
211
+ def test_update_date_time_attributes
212
+ Time.use_zone("Eastern Time (US & Canada)") do
213
+ topic = Topic.find(1)
214
+ time = Time.zone.parse("2017-07-17 10:56")
215
+ topic.update!(written_on: time)
216
+ assert_equal(time, topic.written_on)
217
+ end
218
+ end
219
+
220
+ def test_update_date_time_attributes_with_default_timezone_local
221
+ with_env_tz "America/New_York" do
222
+ with_timezone_config default: :local do
223
+ Time.use_zone("Eastern Time (US & Canada)") do
224
+ topic = Topic.find(1)
225
+ time = Time.zone.parse("2017-07-17 10:56")
226
+ topic.update!(written_on: time)
227
+ assert_equal(time, topic.written_on)
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ class BelongsToAssociationsTest < ActiveRecord::TestCase
235
+ # Since @client.firm is a single first/top, and we use FETCH the order clause is used.
236
+ coerce_tests! :test_belongs_to_does_not_use_order_by
237
+
238
+ # Square brackets around column name
239
+ coerce_tests! :test_belongs_to_with_primary_key_joins_on_correct_column
240
+ def test_belongs_to_with_primary_key_joins_on_correct_column_coerced
241
+ sql = Client.joins(:firm_with_primary_key).to_sql
242
+ assert_no_match(/\[firm_with_primary_keys_companies\]\.\[id\]/, sql)
243
+ assert_match(/\[firm_with_primary_keys_companies\]\.\[name\]/, sql)
244
+ end
245
+
246
+ # Asserted SQL to get one row different from original test.
247
+ coerce_tests! :test_belongs_to
248
+ def test_belongs_to_coerced
249
+ client = Client.find(3)
250
+ first_firm = companies(:first_firm)
251
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
252
+ assert_equal first_firm, client.firm
253
+ assert_equal first_firm.name, client.firm.name
254
+ end
255
+ end
256
+ end
257
+
258
+ module ActiveRecord
259
+ class BindParameterTest < ActiveRecord::TestCase
260
+ # Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
261
+ coerce_tests! :test_binds_are_logged
262
+ def test_binds_are_logged_coerced
263
+ sub = Arel::Nodes::BindParam.new(1)
264
+ binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
265
+ sql = "select * from topics where id = #{sub.to_sql}"
266
+
267
+ @connection.exec_query(sql, "SQL", binds)
268
+
269
+ logged_sql = "EXEC sp_executesql N'#{sql}', N'#{sub.to_sql} int', #{sub.to_sql} = 1"
270
+ message = @subscriber.calls.find { |args| args[4][:sql] == logged_sql }
271
+
272
+ assert_equal binds, message[4][:binds]
273
+ end
274
+
275
+ # SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
276
+ coerce_tests! :test_statement_cache
277
+ coerce_tests! :test_statement_cache_with_query_cache
278
+ coerce_tests! :test_statement_cache_with_find
279
+ coerce_tests! :test_statement_cache_with_find_by
280
+ coerce_tests! :test_statement_cache_with_in_clause
281
+ coerce_tests! :test_statement_cache_with_sql_string_literal
282
+
283
+ # Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
284
+ coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
285
+ def test_bind_params_to_sql_with_prepared_statements_coerced
286
+ assert_bind_params_to_sql_coerced(prepared: true)
287
+ end
288
+
289
+ def test_bind_params_to_sql_with_unprepared_statements_coerced
290
+ @connection.unprepared_statement do
291
+ assert_bind_params_to_sql_coerced(prepared: false)
292
+ end
293
+ end
294
+
295
+ private
296
+
297
+ def assert_bind_params_to_sql_coerced(prepared:)
298
+ table = Author.quoted_table_name
299
+ pk = "#{table}.#{Author.quoted_primary_key}"
300
+
301
+ # prepared_statements: true
302
+ #
303
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
304
+ #
305
+ # prepared_statements: false
306
+ #
307
+ # SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
308
+ #
309
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
310
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
311
+
312
+ authors = Author.where(id: [1, 2, 3, nil])
313
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
314
+ assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
315
+
316
+ # prepared_statements: true
317
+ #
318
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
319
+ #
320
+ # prepared_statements: false
321
+ #
322
+ # SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
323
+ #
324
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
325
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
326
+
327
+ authors = Author.where(id: [1, 2, 3, 9223372036854775808])
328
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
329
+ assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
330
+ end
331
+ end
332
+ end
333
+
334
+ module ActiveRecord
335
+ class InstrumentationTest < ActiveRecord::TestCase
336
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
337
+ coerce_tests! :test_payload_name_on_load
338
+ def test_payload_name_on_load_coerced
339
+ Book.send(:load_schema!)
340
+ original_test_payload_name_on_load
341
+ end
342
+
343
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
344
+ coerce_tests! :test_payload_row_count_on_select_all
345
+ def test_payload_row_count_on_select_all_coerced
346
+ connection.remove_index(:books, column: [:author_id, :name])
347
+
348
+ original_test_payload_row_count_on_select_all
349
+ ensure
350
+ Book.where(author_id: nil, name: 'row count book 1').delete_all
351
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
352
+ end
353
+
354
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
355
+ coerce_tests! :test_payload_row_count_on_pluck
356
+ def test_payload_row_count_on_pluck_coerced
357
+ connection.remove_index(:books, column: [:author_id, :name])
358
+
359
+ original_test_payload_row_count_on_pluck
360
+ ensure
361
+ Book.where(author_id: nil, name: 'row count book 2').delete_all
362
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
363
+ end
364
+
365
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
366
+ coerce_tests! :test_payload_row_count_on_raw_sql
367
+ def test_payload_row_count_on_raw_sql_coerced
368
+ connection.remove_index(:books, column: [:author_id, :name])
369
+
370
+ original_test_payload_row_count_on_raw_sql
371
+ ensure
372
+ Book.where(author_id: nil, name: 'row count book 3').delete_all
373
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
374
+ end
375
+ end
376
+ end
377
+
378
+ class CalculationsTest < ActiveRecord::TestCase
379
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
380
+ coerce_tests! :test_offset_is_kept
381
+ def test_offset_is_kept_coerced
382
+ Account.send(:load_schema!)
383
+ original_test_offset_is_kept
384
+ end
385
+
386
+ # The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
387
+ coerce_tests! :test_should_return_decimal_average_of_integer_field
388
+ def test_should_return_decimal_average_of_integer_field_coerced
389
+ value = Account.average(:id)
390
+ assert_equal 3, value
391
+ end
392
+
393
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
394
+ # Match SQL Server limit implementation.
395
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
396
+ def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
397
+ rails_core = companies(:rails_core)
398
+
399
+ sql = <<~SQL
400
+ SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
401
+ FROM accounts
402
+ WHERE firm_id = ?
403
+ GROUP BY firm_id
404
+ ORDER BY firm_id
405
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
406
+ SQL
407
+
408
+ account = Account.find_by_sql([sql, rails_core]).first
409
+
410
+ # id was not selected, so it should be nil
411
+ # (cannot select id because it wasn't used in the GROUP BY clause)
412
+ assert_nil account.id
413
+
414
+ # firm_id was explicitly selected, so it should be present
415
+ assert_equal(rails_core, account.firm)
416
+
417
+ # avg_credit_limit should be present as a virtual attribute
418
+ assert_equal(52.5, account.avg_credit_limit)
419
+ end
420
+
421
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
422
+ # Order column must be in the GROUP clause.
423
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
424
+ def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
425
+ rails_core = companies(:rails_core)
426
+
427
+ account = Account
428
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
429
+ .where(firm: rails_core)
430
+ .group(:firm_id)
431
+ .order(:firm_id)
432
+ .take!
433
+
434
+ # id was not selected, so it should be nil
435
+ # (cannot select id because it wasn't used in the GROUP BY clause)
436
+ assert_nil account.id
437
+
438
+ # firm_id was explicitly selected, so it should be present
439
+ assert_equal(rails_core, account.firm)
440
+
441
+ # avg_credit_limit should be present as a virtual attribute
442
+ assert_equal(52.5, account.avg_credit_limit)
443
+ end
444
+
445
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
446
+ # SELECT columns must be in the GROUP clause.
447
+ # Match SQL Server limit implementation.
448
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
449
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
450
+ rails_core = companies(:rails_core)
451
+
452
+ sql = <<~SQL
453
+ SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
454
+ FROM companies
455
+ INNER JOIN accounts ON companies.id = accounts.firm_id
456
+ WHERE companies.id = ?
457
+ GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description, companies.status
458
+ ORDER BY companies.id
459
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
460
+ SQL
461
+
462
+ firm = DependentFirm.find_by_sql([sql, rails_core]).first
463
+
464
+ # all the DependentFirm attributes should be present
465
+ assert_equal rails_core, firm
466
+ assert_equal rails_core.name, firm.name
467
+
468
+ # avg_credit_limit should be present as a virtual attribute
469
+ assert_equal(52.5, firm.avg_credit_limit)
470
+ end
471
+
472
+
473
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
474
+ # SELECT columns must be in the GROUP clause.
475
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
476
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
477
+ rails_core = companies(:rails_core)
478
+
479
+ firm = DependentFirm
480
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
481
+ .where(id: rails_core)
482
+ .joins(:account)
483
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
484
+ .take!
485
+
486
+ # all the DependentFirm attributes should be present
487
+ assert_equal rails_core, firm
488
+ assert_equal rails_core.name, firm.name
489
+
490
+ # avg_credit_limit should be present as a virtual attribute
491
+ assert_equal(52.5, firm.avg_credit_limit)
492
+ end
493
+
494
+ # Match SQL Server limit implementation
495
+ coerce_tests! :test_limit_is_kept
496
+ def test_limit_is_kept_coerced
497
+ queries = capture_sql { Account.limit(1).count }
498
+ assert_equal 1, queries.length
499
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
500
+ end
501
+
502
+ # Match SQL Server limit implementation
503
+ coerce_tests! :test_limit_with_offset_is_kept
504
+ def test_limit_with_offset_is_kept_coerced
505
+ queries = capture_sql { Account.limit(1).offset(1).count }
506
+ assert_equal 1, queries.length
507
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
508
+ end
509
+
510
+ # SQL Server needs an alias for the calculated column
511
+ coerce_tests! :test_distinct_count_all_with_custom_select_and_order
512
+ def test_distinct_count_all_with_custom_select_and_order_coerced
513
+ accounts = Account.distinct.select("credit_limit % 10 AS the_limit").order(Arel.sql("credit_limit % 10"))
514
+ assert_queries_count(1) { assert_equal 3, accounts.count(:all) }
515
+ assert_queries_count(1) { assert_equal 3, accounts.load.size }
516
+ end
517
+
518
+ # Leave it up to users to format selects/functions so HAVING works correctly.
519
+ coerce_tests! :test_having_with_strong_parameters
520
+
521
+ # SELECT columns must be in the GROUP clause. Since since `ids` only selects the primary key you cannot perform this query in SQL Server.
522
+ coerce_tests! :test_ids_with_includes_and_non_primary_key_order
523
+
524
+ # To limit the results in SQL Server we use `FETCH NEXT @0 ROWS ONLY` instead of `LIMIT @0`. To use `FETCH NEXT` an order must be provided.
525
+ coerce_tests! :test_no_order_by_when_counting_all
526
+ end
527
+
528
+ module ActiveRecord
529
+ class Migration
530
+ class ChangeSchemaTest < ActiveRecord::TestCase
531
+ # Integer.default is a number and not a string
532
+ coerce_tests! :test_create_table_with_defaults
533
+ def test_create_table_with_defaults_coerce
534
+ connection.create_table :testings do |t|
535
+ t.column :one, :string, default: "hello"
536
+ t.column :two, :boolean, default: true
537
+ t.column :three, :boolean, default: false
538
+ t.column :four, :integer, default: 1
539
+ t.column :five, :text, default: "hello"
540
+ end
541
+
542
+ columns = connection.columns(:testings)
543
+ one = columns.detect { |c| c.name == "one" }
544
+ two = columns.detect { |c| c.name == "two" }
545
+ three = columns.detect { |c| c.name == "three" }
546
+ four = columns.detect { |c| c.name == "four" }
547
+ five = columns.detect { |c| c.name == "five" }
548
+
549
+ assert_equal "hello", one.default
550
+ assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
551
+ assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
552
+ assert_equal 1, four.default
553
+ assert_equal "hello", five.default
554
+ end
555
+
556
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
557
+ coerce_tests! :test_add_column_with_postgresql_datetime_type
558
+ def test_add_column_with_postgresql_datetime_type_coerced
559
+ connection.create_table :testings do |t|
560
+ t.column :foo, :datetime
561
+ end
562
+
563
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
564
+
565
+ assert_equal :datetime, column.type
566
+ assert_equal "datetime2(6)", column.sql_type
567
+ end
568
+
569
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
570
+ coerce_tests! :test_change_column_with_timestamp_type
571
+ def test_change_column_with_timestamp_type_coerced
572
+ connection.create_table :testings do |t|
573
+ t.column :foo, :datetime, null: false
574
+ end
575
+
576
+ connection.change_column :testings, :foo, :timestamp
577
+
578
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
579
+
580
+ assert_equal :datetime, column.type
581
+ assert_equal "datetime2(6)", column.sql_type
582
+ end
583
+
584
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
585
+ coerce_tests! :test_add_column_with_timestamp_type
586
+ def test_add_column_with_timestamp_type_coerced
587
+ connection.create_table :testings do |t|
588
+ t.column :foo, :timestamp
589
+ end
590
+
591
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
592
+
593
+ assert_equal :datetime, column.type
594
+ assert_equal "datetime2(6)", column.sql_type
595
+ end
596
+ end
597
+ end
598
+ end
599
+
600
+ module ActiveRecord
601
+ class Migration
602
+ class ColumnAttributesTest < ActiveRecord::TestCase
603
+ # We have a default 4000 varying character limit.
604
+ coerce_tests! :test_add_column_without_limit
605
+ def test_add_column_without_limit_coerced
606
+ add_column :test_models, :description, :string, limit: nil
607
+ TestModel.reset_column_information
608
+ _(TestModel.columns_hash["description"].limit).must_equal 4000
609
+ end
610
+ end
611
+ end
612
+ end
613
+
614
+ module ActiveRecord
615
+ class Migration
616
+ class ColumnsTest < ActiveRecord::TestCase
617
+ # Our defaults are real 70000 integers vs '70000' strings.
618
+ coerce_tests! :test_rename_column_preserves_default_value_not_null
619
+ def test_rename_column_preserves_default_value_not_null_coerced
620
+ add_column "test_models", "salary", :integer, :default => 70000
621
+ default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
622
+ assert_equal 70000, default_before
623
+ rename_column "test_models", "salary", "annual_salary"
624
+ TestModel.reset_column_information
625
+ assert TestModel.column_names.include?("annual_salary")
626
+ default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
627
+ assert_equal 70000, default_after
628
+ end
629
+
630
+ # Dropping the column removes the single index.
631
+ coerce_tests! :test_remove_column_with_multi_column_index
632
+ def test_remove_column_with_multi_column_index_coerced
633
+ add_column "test_models", :hat_size, :integer
634
+ add_column "test_models", :hat_style, :string, :limit => 100
635
+ add_index "test_models", ["hat_style", "hat_size"], :unique => true
636
+ assert_equal 1, connection.indexes("test_models").size
637
+ remove_column("test_models", "hat_size")
638
+ assert_equal [], connection.indexes("test_models").map(&:name)
639
+ end
640
+
641
+ # Choose `StatementInvalid` vs `ActiveRecordError`.
642
+ coerce_tests! :test_rename_nonexistent_column
643
+ def test_rename_nonexistent_column_coerced
644
+ exception = ActiveRecord::StatementInvalid
645
+ assert_raise(exception) do
646
+ rename_column "test_models", "nonexistent", "should_fail"
647
+ end
648
+ end
649
+ end
650
+ end
651
+ end
652
+
653
+ class MigrationTest < ActiveRecord::TestCase
654
+ # For some reason our tests set Rails.@_env which breaks test env switching.
655
+ coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
656
+ coerce_tests! :test_internal_metadata_stores_environment
657
+
658
+ # Same as original but using binary type instead of blob
659
+ coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
660
+ def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
661
+ migration_a = Class.new(ActiveRecord::Migration::Current) {
662
+ def version; 100 end
663
+ def migrate(x)
664
+ add_column "people", "last_name", :binary
665
+ end
666
+ }.new
667
+
668
+ migration_b = Class.new(ActiveRecord::Migration::Current) {
669
+ def version; 101 end
670
+ def migrate(x)
671
+ add_column "people", "last_name", :binary, if_not_exists: true
672
+ end
673
+ }.new
674
+
675
+ ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, @internal_metadata, 100).migrate
676
+ assert_column Person, :last_name, "migration_a should have created the last_name column on people"
677
+
678
+ assert_nothing_raised do
679
+ ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, @internal_metadata, 101).migrate
680
+ end
681
+ ensure
682
+ Person.reset_column_information
683
+ if Person.column_names.include?("last_name")
684
+ Person.lease_connection.remove_column("people", "last_name")
685
+ end
686
+ end
687
+ end
688
+
689
+ module ActiveRecord
690
+ class Migration
691
+ class CompatibilityTest < ActiveRecord::TestCase
692
+ # Error message depends on the database adapter.
693
+ coerce_tests! :test_create_table_on_7_0
694
+ def test_create_table_on_7_0_coerced
695
+ long_table_name = "a" * (connection.table_name_length + 1)
696
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
697
+ @@long_table_name = long_table_name
698
+ def version; 100 end
699
+ def migrate(x)
700
+ create_table @@long_table_name
701
+ end
702
+ }.new
703
+
704
+ error = assert_raises(StandardError) do
705
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
706
+ end
707
+ assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
708
+ ensure
709
+ connection.drop_table(long_table_name) rescue nil
710
+ end
711
+
712
+ # SQL Server truncates long table names when renaming (https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-ver16).
713
+ coerce_tests! :test_rename_table_on_7_0
714
+ def test_rename_table_on_7_0_coerced
715
+ long_table_name = "a" * (connection.table_name_length + 1)
716
+ connection.create_table(:more_testings)
717
+
718
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
719
+ @@long_table_name = long_table_name
720
+ def version; 100 end
721
+ def migrate(x)
722
+ rename_table :more_testings, @@long_table_name
723
+ end
724
+ }.new
725
+
726
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
727
+ assert connection.table_exists?(long_table_name[0...-1])
728
+ assert_not connection.table_exists?(:more_testings)
729
+ assert connection.table_exists?(long_table_name[0...-1])
730
+ ensure
731
+ connection.drop_table(:more_testings) rescue nil
732
+ connection.drop_table(long_table_name[0...-1]) rescue nil
733
+ end
734
+
735
+ # SQL Server has a different maximum index name length.
736
+ coerce_tests! :test_add_index_errors_on_too_long_name_7_0
737
+ def test_add_index_errors_on_too_long_name_7_0_coerced
738
+ long_index_name = 'a' * (connection.index_name_length + 1)
739
+
740
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
741
+ @@long_index_name = long_index_name
742
+ def migrate(x)
743
+ add_column :testings, :very_long_column_name_to_test_with, :string
744
+ add_index :testings, [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
745
+ end
746
+ }.new
747
+
748
+ error = assert_raises(StandardError) do
749
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
750
+ end
751
+ assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
752
+ end
753
+
754
+ # SQL Server has a different maximum index name length.
755
+ coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
756
+ def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
757
+ long_index_name = 'a' * (connection.index_name_length + 1)
758
+
759
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
760
+ @@long_index_name = long_index_name
761
+ def migrate(x)
762
+ create_table :more_testings do |t|
763
+ t.integer :foo
764
+ t.integer :bar
765
+ t.integer :very_long_column_name_to_test_with
766
+ t.index [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
767
+ end
768
+ end
769
+ }.new
770
+
771
+ error = assert_raises(StandardError) do
772
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
773
+ end
774
+ assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
775
+ ensure
776
+ connection.drop_table :more_testings rescue nil
777
+ end
778
+ end
779
+ end
780
+ end
781
+
782
+ class CoreTest < ActiveRecord::TestCase
783
+ # I think fixtures are using the wrong time zone and the `:first`
784
+ # `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
785
+ # getting local EST time for me and set to "09:28:00.0000000".
786
+ coerce_tests! :test_pretty_print_persisted
787
+ end
788
+
789
+ module ActiveRecord
790
+ module ConnectionAdapters
791
+ # Just like PostgreSQLAdapter does.
792
+ TypeLookupTest.coerce_all_tests! if defined?(TypeLookupTest)
793
+
794
+ # All sorts of errors due to how we test. Even setting ENV['RAILS_ENV'] to
795
+ # a value of 'default_env' will still show tests failing. Just ignoring all
796
+ # of them since we have no monkey in this circus.
797
+ MergeAndResolveDefaultUrlConfigTest.coerce_all_tests! if defined?(MergeAndResolveDefaultUrlConfigTest)
798
+ ConnectionHandlerTest.coerce_all_tests! if defined?(ConnectionHandlerTest)
799
+ end
800
+ end
801
+
802
+ module ActiveRecord
803
+ # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
804
+ module DatabaseTasksSetupper
805
+ undef_method :setup
806
+ def setup
807
+ @sqlserver_tasks =
808
+ Class.new do
809
+ def create; end
810
+
811
+ def drop; end
812
+
813
+ def purge; end
814
+
815
+ def charset; end
816
+
817
+ def collation; end
818
+
819
+ def structure_dump(*); end
820
+
821
+ def structure_load(*); end
822
+ end.new
823
+
824
+ $stdout, @original_stdout = StringIO.new, $stdout
825
+ $stderr, @original_stderr = StringIO.new, $stderr
826
+ end
827
+
828
+ undef_method :with_stubbed_new
829
+ def with_stubbed_new
830
+ ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
831
+ yield
832
+ end
833
+ end
834
+ end
835
+
836
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
837
+ # Coerce PostgreSQL/SQLite/MySQL tests.
838
+ coerce_all_tests!
839
+
840
+ def test_sqlserver_create
841
+ with_stubbed_new do
842
+ assert_called(eval("@sqlserver_tasks"), :create) do
843
+ ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
844
+ end
845
+ end
846
+ end
847
+ end
848
+
849
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
850
+ # Coerce PostgreSQL/SQLite/MySQL tests.
851
+ coerce_all_tests!
852
+
853
+ def test_sqlserver_drop
854
+ with_stubbed_new do
855
+ assert_called(eval("@sqlserver_tasks"), :drop) do
856
+ ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
857
+ end
858
+ end
859
+ end
860
+ end
861
+
862
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
863
+ # Coerce PostgreSQL/SQLite/MySQL tests.
864
+ coerce_all_tests!
865
+
866
+ def test_sqlserver_purge
867
+ with_stubbed_new do
868
+ assert_called(eval("@sqlserver_tasks"), :purge) do
869
+ ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
870
+ end
871
+ end
872
+ end
873
+ end
874
+
875
+ class DatabaseTasksCharsetTest < ActiveRecord::TestCase
876
+ # Coerce PostgreSQL/SQLite/MySQL tests.
877
+ coerce_all_tests!
878
+
879
+ def test_sqlserver_charset
880
+ with_stubbed_new do
881
+ assert_called(eval("@sqlserver_tasks"), :charset) do
882
+ ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
883
+ end
884
+ end
885
+ end
886
+ end
887
+
888
+ class DatabaseTasksCollationTest < ActiveRecord::TestCase
889
+ # Coerce PostgreSQL/SQLite/MySQL tests.
890
+ coerce_all_tests!
891
+
892
+ def test_sqlserver_collation
893
+ with_stubbed_new do
894
+ assert_called(eval("@sqlserver_tasks"), :collation) do
895
+ ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
896
+ end
897
+ end
898
+ end
899
+ end
900
+
901
+ class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
902
+ # Coerce PostgreSQL/SQLite/MySQL tests.
903
+ coerce_all_tests!
904
+
905
+ def test_sqlserver_structure_dump
906
+ with_stubbed_new do
907
+ assert_called_with(
908
+ eval("@sqlserver_tasks"), :structure_dump,
909
+ ["awesome-file.sql", nil]
910
+ ) do
911
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
912
+ end
913
+ end
914
+ end
915
+ end
916
+
917
+ class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
918
+ # Coerce PostgreSQL/SQLite/MySQL tests.
919
+ coerce_all_tests!
920
+
921
+ def test_sqlserver_structure_load
922
+ with_stubbed_new do
923
+ assert_called_with(
924
+ eval("@sqlserver_tasks"),
925
+ :structure_load,
926
+ ["awesome-file.sql", nil]
927
+ ) do
928
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
929
+ end
930
+ end
931
+ end
932
+ end
933
+
934
+ class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
935
+ # We extend `local_database?` so that common VM IPs can be used.
936
+ coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
937
+ end
938
+
939
+ class DatabaseTasksDropAllTest < ActiveRecord::TestCase
940
+ # We extend `local_database?` so that common VM IPs can be used.
941
+ coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
942
+ end
943
+ end
944
+
945
+ class DefaultScopingTest < ActiveRecord::TestCase
946
+ # We are not doing order duplicate removal anymore.
947
+ coerce_tests! :test_order_in_default_scope_should_not_prevail
948
+ end
949
+
950
+ class EagerAssociationTest < ActiveRecord::TestCase
951
+ # Use LEN() instead of LENGTH() function.
952
+ coerce_tests! :test_count_with_include
953
+ def test_count_with_include_coerced
954
+ assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
955
+ end
956
+
957
+ # The raw SQL in the scope uses `limit 1`.
958
+ coerce_tests! %r{including association based on sql condition and no database column}
959
+ end
960
+
961
+ require "models/topic"
962
+ require "models/customer"
963
+ require "models/non_primary_key"
964
+ class FinderTest < ActiveRecord::TestCase
965
+ fixtures :customers, :topics, :authors
966
+
967
+ # We have implicit ordering, via FETCH.
968
+ coerce_tests! %r{doesn't have implicit ordering},
969
+ :test_find_doesnt_have_implicit_ordering
970
+
971
+ # Assert SQL Server limit implementation
972
+ coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
973
+ def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
974
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
975
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 2/) { Topic.first(2).entries }
976
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 5/) { Topic.last(5).entries }
977
+ end
978
+
979
+ # This fails only when run in the full test suite task. Just taking it out of the mix.
980
+ coerce_tests! :test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
981
+
982
+ # Can not use array condition due to not finding right type and hence fractional second quoting.
983
+ coerce_tests! :test_condition_utc_time_interpolation_with_default_timezone_local
984
+ def test_condition_utc_time_interpolation_with_default_timezone_local_coerced
985
+ with_env_tz "America/New_York" do
986
+ with_timezone_config default: :local do
987
+ topic = Topic.first
988
+ assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
989
+ end
990
+ end
991
+ end
992
+
993
+ # Can not use array condition due to not finding right type and hence fractional second quoting.
994
+ coerce_tests! :test_condition_local_time_interpolation_with_default_timezone_utc
995
+ def test_condition_local_time_interpolation_with_default_timezone_utc_coerced
996
+ with_env_tz "America/New_York" do
997
+ with_timezone_config default: :utc do
998
+ topic = Topic.first
999
+ assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
1000
+ end
1001
+ end
1002
+ end
1003
+
1004
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1005
+ coerce_tests! :test_include_on_unloaded_relation_with_match
1006
+ def test_include_on_unloaded_relation_with_match_coerced
1007
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1008
+ assert_equal true, Customer.where(name: "David").include?(customers(:david))
1009
+ end
1010
+ end
1011
+
1012
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1013
+ coerce_tests! :test_include_on_unloaded_relation_without_match
1014
+ def test_include_on_unloaded_relation_without_match_coerced
1015
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1016
+ assert_equal false, Customer.where(name: "David").include?(customers(:mary))
1017
+ end
1018
+ end
1019
+
1020
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1021
+ coerce_tests! :test_member_on_unloaded_relation_with_match
1022
+ def test_member_on_unloaded_relation_with_match_coerced
1023
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1024
+ assert_equal true, Customer.where(name: "David").member?(customers(:david))
1025
+ end
1026
+ end
1027
+
1028
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1029
+ coerce_tests! :test_member_on_unloaded_relation_without_match
1030
+ def test_member_on_unloaded_relation_without_match_coerced
1031
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1032
+ assert_equal false, Customer.where(name: "David").member?(customers(:mary))
1033
+ end
1034
+ end
1035
+
1036
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1037
+ coerce_tests! :test_implicit_order_column_is_configurable
1038
+ def test_implicit_order_column_is_configurable_coerced
1039
+ old_implicit_order_column = Topic.implicit_order_column
1040
+ Topic.implicit_order_column = "title"
1041
+
1042
+ assert_equal topics(:fifth), Topic.first
1043
+ assert_equal topics(:third), Topic.last
1044
+
1045
+ c = Topic.lease_connection
1046
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
1047
+ Topic.last
1048
+ }
1049
+ ensure
1050
+ Topic.implicit_order_column = old_implicit_order_column
1051
+ end
1052
+
1053
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1054
+ coerce_tests! :test_implicit_order_set_to_primary_key
1055
+ def test_implicit_order_set_to_primary_key_coerced
1056
+ old_implicit_order_column = Topic.implicit_order_column
1057
+ Topic.implicit_order_column = "id"
1058
+
1059
+ c = Topic.lease_connection
1060
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
1061
+ Topic.last
1062
+ }
1063
+ ensure
1064
+ Topic.implicit_order_column = old_implicit_order_column
1065
+ end
1066
+
1067
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1068
+ coerce_tests! :test_implicit_order_for_model_without_primary_key
1069
+ def test_implicit_order_for_model_without_primary_key_coerced
1070
+ old_implicit_order_column = NonPrimaryKey.implicit_order_column
1071
+ NonPrimaryKey.implicit_order_column = "created_at"
1072
+
1073
+ c = NonPrimaryKey.lease_connection
1074
+
1075
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
1076
+ NonPrimaryKey.last
1077
+ }
1078
+ ensure
1079
+ NonPrimaryKey.implicit_order_column = old_implicit_order_column
1080
+ end
1081
+
1082
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1083
+ coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
1084
+ def test_member_on_unloaded_relation_with_composite_primary_key_coerced
1085
+ assert_queries_match(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1086
+ book = cpk_books(:cpk_great_author_first_book)
1087
+ assert Cpk::Book.where(title: "The first book").member?(book)
1088
+ end
1089
+ end
1090
+
1091
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1092
+ coerce_tests! :test_implicit_order_column_prepends_query_constraints
1093
+ def test_implicit_order_column_prepends_query_constraints_coerced
1094
+ c = ClothingItem.lease_connection
1095
+ ClothingItem.implicit_order_column = "description"
1096
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1097
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1098
+ quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1099
+
1100
+ assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1101
+ assert_kind_of ClothingItem, ClothingItem.first
1102
+ end
1103
+ ensure
1104
+ ClothingItem.implicit_order_column = nil
1105
+ end
1106
+
1107
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1108
+ coerce_tests! %r{#last for a model with composite query constraints}
1109
+ test "#last for a model with composite query constraints coerced" do
1110
+ c = ClothingItem.lease_connection
1111
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1112
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1113
+
1114
+ assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1115
+ assert_kind_of ClothingItem, ClothingItem.last
1116
+ end
1117
+ end
1118
+
1119
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1120
+ coerce_tests! %r{#first for a model with composite query constraints}
1121
+ test "#first for a model with composite query constraints coerced" do
1122
+ c = ClothingItem.lease_connection
1123
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1124
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1125
+
1126
+ assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1127
+ assert_kind_of ClothingItem, ClothingItem.first
1128
+ end
1129
+ end
1130
+
1131
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1132
+ coerce_tests! :test_implicit_order_column_reorders_query_constraints
1133
+ def test_implicit_order_column_reorders_query_constraints_coerced
1134
+ c = ClothingItem.lease_connection
1135
+ ClothingItem.implicit_order_column = "color"
1136
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1137
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1138
+
1139
+ assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1140
+ assert_kind_of ClothingItem, ClothingItem.first
1141
+ end
1142
+ ensure
1143
+ ClothingItem.implicit_order_column = nil
1144
+ end
1145
+
1146
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1147
+ coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
1148
+ def test_include_on_unloaded_relation_with_composite_primary_key_coerced
1149
+ assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1150
+ book = cpk_books(:cpk_great_author_first_book)
1151
+ assert Cpk::Book.where(title: "The first book").include?(book)
1152
+ end
1153
+ end
1154
+
1155
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1156
+ coerce_tests! :test_nth_to_last_with_order_uses_limit
1157
+ def test_nth_to_last_with_order_uses_limit_coerced
1158
+ c = Topic.lease_connection
1159
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1160
+ Topic.second_to_last
1161
+ end
1162
+
1163
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1164
+ Topic.order(:updated_at).second_to_last
1165
+ end
1166
+ end
1167
+
1168
+ # SQL Server is unable to use aliased SELECT in the HAVING clause.
1169
+ coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
1170
+ end
1171
+
1172
+ module ActiveRecord
1173
+ class Migration
1174
+ class ForeignKeyTest < ActiveRecord::TestCase
1175
+ # SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
1176
+ coerce_tests! :test_add_on_delete_restrict_foreign_key
1177
+ def test_add_on_delete_restrict_foreign_key_coerced
1178
+ assert_raises ArgumentError do
1179
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict
1180
+ end
1181
+ assert_raises ArgumentError do
1182
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :restrict
1183
+ end
1184
+ end
1185
+
1186
+ # SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
1187
+ coerce_tests! :test_remove_foreign_key_with_restrict_action
1188
+
1189
+ # Error message depends on the database adapter.
1190
+ coerce_tests! :test_add_foreign_key_with_if_not_exists_not_set
1191
+ def test_add_foreign_key_with_if_not_exists_not_set_coerced
1192
+ @connection.add_foreign_key :astronauts, :rockets
1193
+ assert_equal 1, @connection.foreign_keys("astronauts").size
1194
+
1195
+ error = assert_raises do
1196
+ @connection.add_foreign_key :astronauts, :rockets
1197
+ end
1198
+
1199
+ assert_match(/TinyTds::Error: There is already an object named '.*' in the database/, error.message)
1200
+ end
1201
+ end
1202
+ end
1203
+ end
1204
+
1205
+ class HasOneAssociationsTest < ActiveRecord::TestCase
1206
+ # We use OFFSET/FETCH vs TOP. So we always have an order.
1207
+ coerce_tests! :test_has_one_does_not_use_order_by
1208
+
1209
+ # Asserted SQL to get one row different from original test.
1210
+ coerce_tests! :test_has_one
1211
+ def test_has_one_coerced
1212
+ firm = companies(:first_firm)
1213
+ first_account = Account.find(1)
1214
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1215
+ assert_equal first_account, firm.account
1216
+ assert_equal first_account.credit_limit, firm.account.credit_limit
1217
+ end
1218
+ end
1219
+ end
1220
+
1221
+ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1222
+ # Asserted SQL to get one row different from original test.
1223
+ coerce_tests! :test_has_one_through_executes_limited_query
1224
+ def test_has_one_through_executes_limited_query_coerced
1225
+ boring_club = clubs(:boring_club)
1226
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1227
+ assert_equal boring_club, @member.general_club
1228
+ end
1229
+ end
1230
+ end
1231
+
1232
+ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1233
+ # Uses || operator in SQL. Just trust core gets value out of this test.
1234
+ coerce_tests! :test_does_not_override_select
1235
+ end
1236
+
1237
+ require "models/developer"
1238
+ require "models/computer"
1239
+ class NestedRelationScopingTest < ActiveRecord::TestCase
1240
+ # Assert SQL Server limit implementation
1241
+ coerce_tests! :test_merge_options
1242
+ def test_merge_options_coerced
1243
+ Developer.where("salary = 80000").scoping do
1244
+ Developer.limit(10).scoping do
1245
+ devs = Developer.all
1246
+ sql = devs.to_sql
1247
+ assert_match "(salary = 80000)", sql
1248
+ assert_match "FETCH NEXT 10 ROWS ONLY", sql
1249
+ end
1250
+ end
1251
+ end
1252
+ end
1253
+
1254
+ require "models/topic"
1255
+ class PersistenceTest < ActiveRecord::TestCase
1256
+ # Rails test required updating a identity column.
1257
+ coerce_tests! :test_update_columns_changing_id
1258
+
1259
+ # Rails test required updating a identity column.
1260
+ coerce_tests! :test_update
1261
+ def test_update_coerced
1262
+ topic = Topic.find(1)
1263
+ assert_not_predicate topic, :approved?
1264
+ assert_equal "The First Topic", topic.title
1265
+
1266
+ topic.update("approved" => true, "title" => "The First Topic Updated")
1267
+ topic.reload
1268
+ assert_predicate topic, :approved?
1269
+ assert_equal "The First Topic Updated", topic.title
1270
+
1271
+ topic.update(approved: false, title: "The First Topic")
1272
+ topic.reload
1273
+ assert_not_predicate topic, :approved?
1274
+ assert_equal "The First Topic", topic.title
1275
+ end
1276
+
1277
+ # In SQL Server it's not possible to set the primary key column using a trigger and to get it then to return.
1278
+ coerce_tests! :test_model_with_no_auto_populated_fields_still_returns_primary_key_after_insert
1279
+ end
1280
+
1281
+ require "models/author"
1282
+ class UpdateAllTest < ActiveRecord::TestCase
1283
+ # Rails test required updating a identity column.
1284
+ coerce_tests! :test_update_all_doesnt_ignore_order
1285
+ def test_update_all_doesnt_ignore_order_coerced
1286
+ david, mary = authors(:david), authors(:mary)
1287
+ _(david.id).must_equal 1
1288
+ _(mary.id).must_equal 2
1289
+ _(david.name).wont_equal mary.name
1290
+ assert_queries_match(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
1291
+ Author.where("[id] > 1").order(:id).update_all(name: "Test")
1292
+ end
1293
+ _(david.reload.name).must_equal "David"
1294
+ _(mary.reload.name).must_equal "Test"
1295
+ end
1296
+
1297
+ # SELECT columns must be in the GROUP clause.
1298
+ coerce_tests! :test_update_all_with_group_by
1299
+ def test_update_all_with_group_by_coerced
1300
+ minimum_comments_count = 2
1301
+
1302
+ Post.most_commented(minimum_comments_count).update_all(title: "ig")
1303
+ posts = Post.select(:id, :title).group(:title).most_commented(minimum_comments_count).all.to_a
1304
+
1305
+ assert_operator posts.length, :>, 0
1306
+ assert posts.all? { |post| post.comments.length >= minimum_comments_count }
1307
+ assert posts.all? { |post| "ig" == post.title }
1308
+
1309
+ post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
1310
+ assert_not_equal "ig", post.title
1311
+ end
1312
+ end
1313
+
1314
+ class DeleteAllTest < ActiveRecord::TestCase
1315
+ # SELECT columns must be in the GROUP clause.
1316
+ coerce_tests! :test_delete_all_with_group_by_and_having
1317
+ def test_delete_all_with_group_by_and_having_coerced
1318
+ minimum_comments_count = 2
1319
+ posts_to_be_deleted = Post.select(:id).most_commented(minimum_comments_count).all.to_a
1320
+ assert_operator posts_to_be_deleted.length, :>, 0
1321
+
1322
+ assert_difference("Post.count", -posts_to_be_deleted.length) do
1323
+ Post.most_commented(minimum_comments_count).delete_all
1324
+ end
1325
+
1326
+ posts_to_be_deleted.each do |deleted_post|
1327
+ assert_raise(ActiveRecord::RecordNotFound) { deleted_post.reload }
1328
+ end
1329
+ end
1330
+ end
1331
+
1332
+ require "models/topic"
1333
+ module ActiveRecord
1334
+ class PredicateBuilderTest < ActiveRecord::TestCase
1335
+ # Same as original test except string has `N` prefix to indicate unicode string.
1336
+ coerce_tests! :test_registering_new_handlers
1337
+ def test_registering_new_handlers_coerced
1338
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Topic.where(title: /rails/).to_sql
1339
+ end
1340
+
1341
+ # Same as original test except string has `N` prefix to indicate unicode string.
1342
+ coerce_tests! :test_registering_new_handlers_for_association
1343
+ def test_registering_new_handlers_for_association_coerced
1344
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
1345
+ end
1346
+
1347
+ # Same as original test except string has `N` prefix to indicate unicode string.
1348
+ coerce_tests! :test_registering_new_handlers_for_joins
1349
+ def test_registering_new_handlers_for_joins_coerced
1350
+ Reply.belongs_to :regexp_topic, -> { where(title: /rails/) }, class_name: "Topic", foreign_key: "parent_id"
1351
+
1352
+ assert_match %r{#{Regexp.escape(quote_table_name("regexp_topic.title"))} ~ N'rails'}i, Reply.joins(:regexp_topic).references(Arel.sql("regexp_topic")).to_sql
1353
+ end
1354
+
1355
+ private
1356
+
1357
+ def topic_title
1358
+ Topic.lease_connection.quote_table_name("topics.title")
1359
+ end
1360
+ end
1361
+ end
1362
+
1363
+ require "models/task"
1364
+ class QueryCacheTest < ActiveRecord::TestCase
1365
+ # SQL Server adapter not in list of supported adapters in original test.
1366
+ coerce_tests! :test_cache_does_not_wrap_results_in_arrays
1367
+ def test_cache_does_not_wrap_results_in_arrays_coerced
1368
+ Task.cache do
1369
+ assert_equal 2, Task.lease_connection.select_value("SELECT count(*) AS count_all FROM tasks")
1370
+ end
1371
+ end
1372
+ end
1373
+
1374
+ require "models/post"
1375
+ class RelationTest < ActiveRecord::TestCase
1376
+ # Use LEN() instead of LENGTH() function.
1377
+ coerce_tests! :test_reverse_order_with_function
1378
+ def test_reverse_order_with_function_coerced
1379
+ topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
1380
+ assert_equal topics(:second).title, topics.first.title
1381
+ end
1382
+
1383
+ # Use LEN() instead of LENGTH() function.
1384
+ coerce_tests! :test_reverse_order_with_function_other_predicates
1385
+ def test_reverse_order_with_function_other_predicates_coerced
1386
+ topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
1387
+ assert_equal topics(:second).title, topics.first.title
1388
+ topics = Topic.order(Arel.sql("LEN(author_name), id, LEN(title)")).reverse_order
1389
+ assert_equal topics(:fifth).title, topics.first.title
1390
+ end
1391
+
1392
+ # We have implicit ordering, via FETCH.
1393
+ coerce_tests! %r{doesn't have implicit ordering}
1394
+
1395
+ # We have implicit ordering, via FETCH.
1396
+ coerce_tests! :test_reorder_with_take
1397
+ def test_reorder_with_take_coerced
1398
+ sql_log = capture_sql do
1399
+ assert Post.order(:title).reorder(nil).take
1400
+ end
1401
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1402
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1403
+ end
1404
+
1405
+ # We have implicit ordering, via FETCH.
1406
+ coerce_tests! :test_reorder_with_first
1407
+ def test_reorder_with_first_coerced
1408
+ post = nil
1409
+ sql_log = capture_sql do
1410
+ post = Post.order(:title).reorder(nil).first
1411
+ end
1412
+ assert_equal posts(:welcome), post
1413
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1414
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1415
+ end
1416
+
1417
+ # We are not doing order duplicate removal anymore.
1418
+ coerce_tests! :test_order_using_scoping
1419
+
1420
+ # We are not doing order duplicate removal anymore.
1421
+ coerce_tests! :test_default_scope_order_with_scope_order
1422
+
1423
+ # Order column must be in the GROUP clause.
1424
+ coerce_tests! :test_multiple_where_and_having_clauses
1425
+ def test_multiple_where_and_having_clauses_coerced
1426
+ post = Post.first
1427
+ having_then_where = Post.having(id: post.id).where(title: post.title)
1428
+ .having(id: post.id).where(title: post.title).group(:id).select(:id)
1429
+
1430
+ assert_equal [post], having_then_where
1431
+ end
1432
+
1433
+ # Order column must be in the GROUP clause.
1434
+ coerce_tests! :test_having_with_binds_for_both_where_and_having
1435
+ def test_having_with_binds_for_both_where_and_having
1436
+ post = Post.first
1437
+ having_then_where = Post.having(id: post.id).where(title: post.title).group(:id).select(:id)
1438
+ where_then_having = Post.where(title: post.title).having(id: post.id).group(:id).select(:id)
1439
+
1440
+ assert_equal [post], having_then_where
1441
+ assert_equal [post], where_then_having
1442
+ end
1443
+
1444
+ # Find any limit via our expression.
1445
+ coerce_tests! %r{relations don't load all records in #inspect}
1446
+ def test_relations_dont_load_all_records_in_inspect_coerced
1447
+ assert_queries_match(/NEXT @0 ROWS.*@0 = \d+/) do
1448
+ Post.all.inspect
1449
+ end
1450
+ end
1451
+
1452
+ # Find any limit via our expression.
1453
+ coerce_tests! %r{relations don't load all records in #pretty_print}
1454
+ def test_relations_dont_load_all_records_in_pretty_print_coerced
1455
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
1456
+ PP.pp Post.all, StringIO.new # avoid outputting.
1457
+ end
1458
+ end
1459
+
1460
+ # Order column must be in the GROUP clause.
1461
+ coerce_tests! :test_empty_complex_chained_relations
1462
+ def test_empty_complex_chained_relations_coerced
1463
+ posts = Post.select("comments_count").where("id is not null").group("author_id", "id").where("legacy_comments_count > 0")
1464
+
1465
+ assert_queries_count(1) { assert_equal false, posts.empty? }
1466
+ assert_not_predicate posts, :loaded?
1467
+
1468
+ no_posts = posts.where(title: "")
1469
+ assert_queries_count(1) { assert_equal true, no_posts.empty? }
1470
+ assert_not_predicate no_posts, :loaded?
1471
+ end
1472
+
1473
+ # Can't apply offset without ORDER
1474
+ coerce_tests! %r{using a custom table affects the wheres}
1475
+ test "using a custom table affects the wheres coerced" do
1476
+ post = posts(:welcome)
1477
+
1478
+ assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
1479
+ end
1480
+
1481
+ # Can't apply offset without ORDER
1482
+ coerce_tests! %r{using a custom table with joins affects the joins}
1483
+ test "using a custom table with joins affects the joins coerced" do
1484
+ post = posts(:welcome)
1485
+
1486
+ assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
1487
+ end
1488
+
1489
+ # Use LEN() instead of LENGTH() function.
1490
+ coerce_tests! :test_reverse_arel_assoc_order_with_function
1491
+ def test_reverse_arel_assoc_order_with_function_coerced
1492
+ topics = Topic.order(Arel.sql("LEN(title)") => :asc).reverse_order
1493
+ assert_equal topics(:second).title, topics.first.title
1494
+ end
1495
+ end
1496
+
1497
+ module ActiveRecord
1498
+ class RelationTest < ActiveRecord::TestCase
1499
+ # Skipping this test. SQL Server doesn't support optimizer hint as comments
1500
+ coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
1501
+
1502
+ coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1503
+ def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1504
+ escaped_table = Post.lease_connection.quote_table_name("posts")
1505
+ expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1506
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1507
+ assert_equal expected, query
1508
+ end
1509
+ end
1510
+ end
1511
+
1512
+ require "models/post"
1513
+ class SanitizeTest < ActiveRecord::TestCase
1514
+ # Use nvarchar string (N'') in assert
1515
+ coerce_tests! :test_sanitize_sql_like_example_use_case
1516
+ def test_sanitize_sql_like_example_use_case_coerced
1517
+ searchable_post = Class.new(Post) do
1518
+ def self.search_as_method(term)
1519
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
1520
+ end
1521
+
1522
+ scope :search_as_scope, ->(term) {
1523
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
1524
+ }
1525
+ end
1526
+
1527
+ assert_queries_match(/LIKE @0/) do
1528
+ searchable_post.search_as_method("20% _reduction_!").to_a
1529
+ end
1530
+
1531
+ assert_queries_match(/LIKE @0/) do
1532
+ searchable_post.search_as_scope("20% _reduction_!").to_a
1533
+ end
1534
+ end
1535
+
1536
+ # Use nvarchar string (N'') in assert
1537
+ coerce_tests! :test_named_bind_with_literal_colons
1538
+ def test_named_bind_with_literal_colons_coerced
1539
+ assert_equal "TO_TIMESTAMP(N'2017/08/02 10:59:00', 'YYYY/MM/DD HH12:MI:SS')", bind("TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "2017/08/02 10:59:00")
1540
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
1541
+ end
1542
+ end
1543
+
1544
+ class SchemaDumperTest < ActiveRecord::TestCase
1545
+ # Use nvarchar string (N'') in assert
1546
+ coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1547
+ def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1548
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
1549
+ versions.shuffle.each do |v|
1550
+ @schema_migration.create_version(v)
1551
+ end
1552
+
1553
+ schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
1554
+ expected = <<~STR
1555
+ INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
1556
+ (N'20100301010101'),
1557
+ (N'20100201010101'),
1558
+ (N'20100101010101');
1559
+ STR
1560
+ assert_equal expected.strip, schema_info
1561
+ ensure
1562
+ @schema_migration.delete_all_versions
1563
+ end
1564
+
1565
+ # We have precision to 38.
1566
+ coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
1567
+ def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
1568
+ output = standard_dump
1569
+ assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 38}, output
1570
+ end
1571
+
1572
+ # This is a poorly written test and really does not catch the bottom'ness it is meant to. Ours throw it off.
1573
+ coerce_tests! :test_foreign_keys_are_dumped_at_the_bottom_to_circumvent_dependency_issues
1574
+
1575
+ # Fall through false positive with no filter.
1576
+ coerce_tests! :test_schema_dumps_partial_indices
1577
+ def test_schema_dumps_partial_indices_coerced
1578
+ index_definition = standard_dump.split(/\n/).grep(/t.index.*company_partial_index/).first.strip
1579
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "([rating]>(10))"', index_definition
1580
+ end
1581
+
1582
+ # We do not quote the 2.78 string default.
1583
+ coerce_tests! :test_schema_dump_includes_decimal_options
1584
+ def test_schema_dump_includes_decimal_options_coerced
1585
+ output = dump_all_table_schema([/^[^n]/])
1586
+ assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
1587
+ end
1588
+
1589
+ # Tests are not about a specific adapter.
1590
+ coerce_tests! :test_do_not_dump_foreign_keys_when_bypassed_by_config
1591
+
1592
+ # SQL Server formats the check constraint expression differently.
1593
+ coerce_tests! :test_schema_dumps_check_constraints
1594
+ def test_schema_dumps_check_constraints_coerced
1595
+ constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
1596
+ assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
1597
+ end
1598
+ end
1599
+
1600
+ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
1601
+ # These date formats do not match ours. We got these covered in our dumper tests.
1602
+ coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
1603
+
1604
+ # SQL Server uses different method to generate a UUID than Rails test uses. Reimplemented the
1605
+ # test in 'SchemaDumperDefaultsCoerceTest'.
1606
+ coerce_tests! :test_schema_dump_with_text_column
1607
+ end
1608
+
1609
+ class SchemaDumperDefaultsCoerceTest < ActiveRecord::TestCase
1610
+ include SchemaDumpingHelper
1611
+
1612
+ setup do
1613
+ @connection = ActiveRecord::Base.lease_connection
1614
+ @connection.create_table :dump_defaults, force: true do |t|
1615
+ t.string :string_with_default, default: "Hello!"
1616
+ t.date :date_with_default, default: "2014-06-05"
1617
+ t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
1618
+ t.time :time_with_default, default: "07:17:04"
1619
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1620
+
1621
+ t.text :text_with_default, default: "John' Doe"
1622
+ t.text :uuid, default: -> { "newid()" }
1623
+ end
1624
+ end
1625
+
1626
+ def test_schema_dump_with_text_column_coerced
1627
+ output = dump_table_schema("dump_defaults")
1628
+
1629
+ assert_match %r{t\.text\s+"text_with_default",.*?default: "John' Doe"}, output
1630
+ assert_match %r{t\.text\s+"uuid",.*?default: -> \{ "newid\(\)" \}}, output
1631
+ end
1632
+ end
1633
+
1634
+ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
1635
+ # We trust Rails on this since we do not want to install mysql.
1636
+ coerce_tests! %r{inspect on Model class does not raise}
1637
+ end
1638
+
1639
+ require "models/topic"
1640
+ class TransactionTest < ActiveRecord::TestCase
1641
+ # SQL Server does not have query for release_savepoint.
1642
+ coerce_tests! :test_releasing_named_savepoints
1643
+ def test_releasing_named_savepoints_coerced
1644
+ Topic.transaction do
1645
+ Topic.lease_connection.materialize_transactions
1646
+
1647
+ Topic.lease_connection.create_savepoint("another")
1648
+ Topic.lease_connection.release_savepoint("another")
1649
+
1650
+ # We do not have a notion of releasing, so this does nothing and doesn't raise an error.
1651
+ assert_nothing_raised do
1652
+ Topic.lease_connection.release_savepoint("another")
1653
+ end
1654
+ end
1655
+ end
1656
+
1657
+ # SQL Server does not have query for release_savepoint.
1658
+ coerce_tests! :test_nested_transactions_after_disable_lazy_transactions
1659
+ def test_nested_transactions_after_disable_lazy_transactions_coerced
1660
+ Topic.lease_connection.disable_lazy_transactions!
1661
+
1662
+ actual_queries = capture_sql(include_schema: true) do
1663
+ # RealTransaction (begin..commit)
1664
+ Topic.transaction(requires_new: true) do
1665
+ # ResetParentTransaction (no queries)
1666
+ Topic.transaction(requires_new: true) do
1667
+ Topic.delete_all
1668
+ # SavepointTransaction (savepoint..release)
1669
+ Topic.transaction(requires_new: true) do
1670
+ # ResetParentTransaction (no queries)
1671
+ Topic.transaction(requires_new: true) do
1672
+ # no-op
1673
+ end
1674
+ end
1675
+ end
1676
+ Topic.delete_all
1677
+ end
1678
+ end
1679
+
1680
+ expected_queries = [
1681
+ /BEGIN/i,
1682
+ /DELETE/i,
1683
+ /^SAVE TRANSACTION/i,
1684
+ /DELETE/i,
1685
+ /COMMIT/i,
1686
+ ]
1687
+
1688
+ assert_equal expected_queries.size, actual_queries.size
1689
+ expected_queries.zip(actual_queries) do |expected, actual|
1690
+ assert_match expected, actual
1691
+ end
1692
+ end
1693
+
1694
+ # SQL Server does not have query for release_savepoint.
1695
+ coerce_tests! :test_nested_transactions_skip_excess_savepoints
1696
+ def test_nested_transactions_skip_excess_savepoints_coerced
1697
+ actual_queries = capture_sql(include_schema: true) do
1698
+ # RealTransaction (begin..commit)
1699
+ Topic.transaction(requires_new: true) do
1700
+ # ResetParentTransaction (no queries)
1701
+ Topic.transaction(requires_new: true) do
1702
+ Topic.delete_all
1703
+ # SavepointTransaction (savepoint..release)
1704
+ Topic.transaction(requires_new: true) do
1705
+ # ResetParentTransaction (no queries)
1706
+ Topic.transaction(requires_new: true) do
1707
+ Topic.delete_all
1708
+ end
1709
+ end
1710
+ end
1711
+ Topic.delete_all
1712
+ end
1713
+ end
1714
+
1715
+ expected_queries = [
1716
+ /BEGIN/i,
1717
+ /DELETE/i,
1718
+ /^SAVE TRANSACTION/i,
1719
+ /DELETE/i,
1720
+ /DELETE/i,
1721
+ /COMMIT/i,
1722
+ ]
1723
+
1724
+ assert_equal expected_queries.size, actual_queries.size
1725
+ expected_queries.zip(actual_queries) do |expected, actual|
1726
+ assert_match expected, actual
1727
+ end
1728
+ end
1729
+ end
1730
+
1731
+ require "models/tag"
1732
+ class TransactionIsolationTest < ActiveRecord::TestCase
1733
+ # SQL Server will lock the table for counts even when both
1734
+ # connections are `READ COMMITTED`. So we bypass with `READPAST`.
1735
+ coerce_tests! %r{read committed}
1736
+ test "read committed coerced" do
1737
+ Tag.transaction(isolation: :read_committed) do
1738
+ assert_equal 0, Tag.count
1739
+ Tag2.transaction do
1740
+ Tag2.create
1741
+ assert_equal 0, Tag.lock("WITH(READPAST)").count
1742
+ end
1743
+ end
1744
+ assert_equal 1, Tag.count
1745
+ end
1746
+
1747
+ # I really need some help understanding this one.
1748
+ coerce_tests! %r{repeatable read}
1749
+ end
1750
+
1751
+ require "models/book"
1752
+ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
1753
+ # We have a few view tables. use includes vs equality.
1754
+ coerce_tests! :test_views
1755
+ def test_views_coerced
1756
+ assert_includes @connection.views, Ebook.table_name
1757
+ end
1758
+
1759
+ # We do better than ActiveRecord and find the views PK.
1760
+ coerce_tests! :test_does_not_assume_id_column_as_primary_key
1761
+ def test_does_not_assume_id_column_as_primary_key_coerced
1762
+ model = Class.new(ActiveRecord::Base) { self.table_name = "ebooks" }
1763
+ assert_equal "id", model.primary_key
1764
+ end
1765
+ end
1766
+
1767
+ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
1768
+ # We have a few view tables. use includes vs equality.
1769
+ coerce_tests! :test_views
1770
+ def test_views_coerced
1771
+ assert_includes @connection.views, Paperback.table_name
1772
+ end
1773
+ end
1774
+
1775
+ require "models/author"
1776
+ class YamlSerializationTest < ActiveRecord::TestCase
1777
+ coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1778
+ def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
1779
+ author = Author.select("authors.*, 5 as posts_count").first
1780
+ dumped_author = YAML.dump(author)
1781
+ dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
1782
+ assert_equal 5, author.posts_count
1783
+ assert_equal 5, dumped.posts_count
1784
+ end
1785
+ end
1786
+
1787
+ class DateTimePrecisionTest < ActiveRecord::TestCase
1788
+ # Original test had `7` which we support vs `8` which we use.
1789
+ coerce_tests! :test_invalid_datetime_precision_raises_error
1790
+ def test_invalid_datetime_precision_raises_error_coerced
1791
+ assert_raises ActiveRecord::ActiveRecordError do
1792
+ @connection.create_table(:foos, force: true) do |t|
1793
+ t.timestamps precision: 8
1794
+ end
1795
+ end
1796
+ end
1797
+
1798
+ # datetime is rounded to increments of .000, .003, or .007 seconds
1799
+ coerce_tests! :test_datetime_precision_is_truncated_on_assignment
1800
+ def test_datetime_precision_is_truncated_on_assignment_coerced
1801
+ @connection.create_table(:foos, force: true)
1802
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
1803
+ @connection.add_column :foos, :updated_at, :datetime, precision: 6
1804
+
1805
+ time = ::Time.now.change(nsec: 123456789)
1806
+ foo = Foo.new(created_at: time, updated_at: time)
1807
+
1808
+ assert_equal 0, foo.created_at.nsec
1809
+ assert_equal 123457000, foo.updated_at.nsec
1810
+
1811
+ foo.save!
1812
+ foo.reload
1813
+
1814
+ assert_equal 0, foo.created_at.nsec
1815
+ assert_equal 123457000, foo.updated_at.nsec
1816
+ end
1817
+ end
1818
+
1819
+ class TimePrecisionTest < ActiveRecord::TestCase
1820
+ # datetime is rounded to increments of .000, .003, or .007 seconds
1821
+ coerce_tests! :test_time_precision_is_truncated_on_assignment
1822
+ def test_time_precision_is_truncated_on_assignment_coerced
1823
+ @connection.create_table(:foos, force: true)
1824
+ @connection.add_column :foos, :start, :time, precision: 0
1825
+ @connection.add_column :foos, :finish, :time, precision: 6
1826
+
1827
+ time = ::Time.now.change(nsec: 123456789)
1828
+ foo = Foo.new(start: time, finish: time)
1829
+
1830
+ assert_equal 0, foo.start.nsec
1831
+ assert_equal 123457000, foo.finish.nsec
1832
+
1833
+ foo.save!
1834
+ foo.reload
1835
+
1836
+ assert_equal 0, foo.start.nsec
1837
+ assert_equal 123457000, foo.finish.nsec
1838
+ end
1839
+
1840
+ # SQL Server uses default precision for time.
1841
+ coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
1842
+
1843
+ # SQL Server accepts precision of 7 for time.
1844
+ coerce_tests! :test_invalid_time_precision_raises_error
1845
+ end
1846
+
1847
+ class DefaultNumbersTest < ActiveRecord::TestCase
1848
+ # We do better with native types and do not return strings for everything.
1849
+ coerce_tests! :test_default_positive_integer
1850
+ def test_default_positive_integer_coerced
1851
+ record = DefaultNumber.new
1852
+ assert_equal 7, record.positive_integer
1853
+ assert_equal 7, record.positive_integer_before_type_cast
1854
+ end
1855
+
1856
+ # We do better with native types and do not return strings for everything.
1857
+ coerce_tests! :test_default_negative_integer
1858
+ def test_default_negative_integer_coerced
1859
+ record = DefaultNumber.new
1860
+ assert_equal (-5), record.negative_integer
1861
+ assert_equal (-5), record.negative_integer_before_type_cast
1862
+ end
1863
+
1864
+ # We do better with native types and do not return strings for everything.
1865
+ coerce_tests! :test_default_decimal_number
1866
+ def test_default_decimal_number_coerced
1867
+ record = DefaultNumber.new
1868
+ assert_equal BigDecimal("2.78"), record.decimal_number
1869
+ assert_equal 2.78, record.decimal_number_before_type_cast
1870
+ end
1871
+ end
1872
+
1873
+ module ActiveRecord
1874
+ class CollectionCacheKeyTest < ActiveRecord::TestCase
1875
+ # Will trust rails has this sorted since you cant offset without a limit.
1876
+ coerce_tests! %r{with offset which return 0 rows}
1877
+ end
1878
+ end
1879
+
1880
+ module ActiveRecord
1881
+ class CacheKeyTest < ActiveRecord::TestCase
1882
+ # Like Mysql2 and PostgreSQL, SQL Server doesn't return a string value for updated_at. In the Rails tests
1883
+ # the tests are skipped if adapter is Mysql2 or PostgreSQL.
1884
+ coerce_tests! %r{cache_version is the same when it comes from the DB or from the user}
1885
+ coerce_tests! %r{cache_version does NOT call updated_at when value is from the database}
1886
+ coerce_tests! %r{cache_version does not truncate zeros when timestamp ends in zeros}
1887
+ end
1888
+ end
1889
+
1890
+ require "models/book"
1891
+ module ActiveRecord
1892
+ class StatementCacheTest < ActiveRecord::TestCase
1893
+ # Getting random failures.
1894
+ coerce_tests! :test_find_does_not_use_statement_cache_if_table_name_is_changed
1895
+
1896
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1897
+ coerce_tests! :test_statement_cache_values_differ
1898
+ def test_statement_cache_values_differ_coerced
1899
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1900
+
1901
+ original_test_statement_cache_values_differ
1902
+ ensure
1903
+ Book.where(author_id: nil, name: 'my book').delete_all
1904
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1905
+ end
1906
+ end
1907
+ end
1908
+
1909
+ module ActiveRecord
1910
+ module ConnectionAdapters
1911
+ class SchemaCacheTest < ActiveRecord::TestCase
1912
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1913
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1914
+
1915
+ # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1916
+ coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1917
+
1918
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1919
+ unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1920
+ def test_marshal_dump_and_load_with_gzip_coerced
1921
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1922
+ end
1923
+ def test_marshal_dump_and_load_via_disk_coerced
1924
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1925
+ end
1926
+ end
1927
+
1928
+ private
1929
+
1930
+ def with_marshable_time_defaults
1931
+ # Detect problems
1932
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1933
+ column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1934
+ current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1935
+ end
1936
+
1937
+ # Correct problems
1938
+ if current_default.present?
1939
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1940
+ end
1941
+
1942
+ # Run original test
1943
+ yield
1944
+ ensure
1945
+ # Revert changes
1946
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
1947
+ end
1948
+
1949
+ # We need to give the full path for this to work.
1950
+ undef_method :schema_dump_path
1951
+ def schema_dump_path
1952
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1953
+ end
1954
+ end
1955
+ end
1956
+ end
1957
+
1958
+ require "models/post"
1959
+ require "models/comment"
1960
+ class UnsafeRawSqlTest < ActiveRecord::TestCase
1961
+ fixtures :posts
1962
+
1963
+ # Use LEN() instead of LENGTH() function.
1964
+ coerce_tests! %r{order: always allows Arel}
1965
+ test "order: always allows Arel" do
1966
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1967
+
1968
+ assert_not_empty titles
1969
+ end
1970
+
1971
+ # Use LEN() instead of LENGTH() function.
1972
+ coerce_tests! %r{pluck: always allows Arel}
1973
+ test "pluck: always allows Arel" do
1974
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1975
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1976
+
1977
+ assert_equal excepted_values, values
1978
+ end
1979
+
1980
+ # Use LEN() instead of LENGTH() function.
1981
+ coerce_tests! %r{order: allows valid Array arguments}
1982
+ test "order: allows valid Array arguments" do
1983
+ ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1984
+
1985
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1986
+
1987
+ assert_equal ids_expected, ids
1988
+ end
1989
+
1990
+ # Use LEN() instead of LENGTH() function.
1991
+ coerce_tests! %r{order: allows nested functions}
1992
+ test "order: allows nested functions" do
1993
+ ids_expected = Post.order(Arel.sql("author_id, len(trim(title))")).pluck(:id)
1994
+
1995
+ # $DEBUG = true
1996
+ ids = Post.order("author_id, len(trim(title))").pluck(:id)
1997
+
1998
+ assert_equal ids_expected, ids
1999
+ end
2000
+
2001
+ # Use LEN() instead of LENGTH() function.
2002
+ coerce_tests! %r{pluck: allows nested functions}
2003
+ test "pluck: allows nested functions" do
2004
+ title_lengths_expected = Post.pluck(Arel.sql("len(trim(title))"))
2005
+
2006
+ title_lengths = Post.pluck("len(trim(title))")
2007
+
2008
+ assert_equal title_lengths_expected, title_lengths
2009
+ end
2010
+
2011
+ test "order: allows string column names that are quoted" do
2012
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
2013
+
2014
+ ids = Post.order("[id]").pluck(:id)
2015
+
2016
+ assert_equal ids_expected, ids
2017
+ end
2018
+
2019
+ test "order: allows string column names that are quoted with table" do
2020
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
2021
+
2022
+ ids = Post.order("[posts].[id]").pluck(:id)
2023
+
2024
+ assert_equal ids_expected, ids
2025
+ end
2026
+
2027
+ test "order: allows string column names that are quoted with table and user" do
2028
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
2029
+
2030
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
2031
+
2032
+ assert_equal ids_expected, ids
2033
+ end
2034
+
2035
+ test "order: allows string column names that are quoted with table, user and database" do
2036
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
2037
+
2038
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
2039
+
2040
+ assert_equal ids_expected, ids
2041
+ end
2042
+
2043
+ test "pluck: allows string column name that are quoted" do
2044
+ titles_expected = Post.pluck(Arel.sql("title"))
2045
+
2046
+ titles = Post.pluck("[title]")
2047
+
2048
+ assert_equal titles_expected, titles
2049
+ end
2050
+
2051
+ test "pluck: allows string column name that are quoted with table" do
2052
+ titles_expected = Post.pluck(Arel.sql("title"))
2053
+
2054
+ titles = Post.pluck("[posts].[title]")
2055
+
2056
+ assert_equal titles_expected, titles
2057
+ end
2058
+
2059
+ test "pluck: allows string column name that are quoted with table and user" do
2060
+ titles_expected = Post.pluck(Arel.sql("title"))
2061
+
2062
+ titles = Post.pluck("[dbo].[posts].[title]")
2063
+
2064
+ assert_equal titles_expected, titles
2065
+ end
2066
+
2067
+ test "pluck: allows string column name that are quoted with table, user and database" do
2068
+ titles_expected = Post.pluck(Arel.sql("title"))
2069
+
2070
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
2071
+
2072
+ assert_equal titles_expected, titles
2073
+ end
2074
+
2075
+ # Collation name should not be quoted. Hardcoded values for different adapters.
2076
+ coerce_tests! %r{order: allows valid arguments with COLLATE}
2077
+ test "order: allows valid arguments with COLLATE" do
2078
+ collation_name = "Latin1_General_CS_AS_WS"
2079
+
2080
+ ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2081
+
2082
+ ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2083
+
2084
+ assert_equal ids_expected, ids
2085
+ end
2086
+ end
2087
+
2088
+ class ReservedWordTest < ActiveRecord::TestCase
2089
+ coerce_tests! :test_change_columns
2090
+ def test_change_columns_coerced
2091
+ assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
2092
+ assert_nothing_raised { @connection.change_column("group", "order", :text) }
2093
+ assert_nothing_raised { @connection.change_column_null("group", "order", true) }
2094
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
2095
+ end
2096
+ end
2097
+
2098
+ class OptimisticLockingTest < ActiveRecord::TestCase
2099
+ # We do not allow updating identities, but we can test using a non-identity key
2100
+ coerce_tests! :test_update_with_dirty_primary_key
2101
+ def test_update_with_dirty_primary_key_coerced
2102
+ assert_raises(ActiveRecord::RecordNotUnique) do
2103
+ record = StringKeyObject.find("record1")
2104
+ record.id = "record2"
2105
+ record.save!
2106
+ end
2107
+
2108
+ record = StringKeyObject.find("record1")
2109
+ record.id = "record42"
2110
+ record.save!
2111
+
2112
+ assert StringKeyObject.find("record42")
2113
+ assert_raises(ActiveRecord::RecordNotFound) do
2114
+ StringKeyObject.find("record1")
2115
+ end
2116
+ end
2117
+ end
2118
+
2119
+ class RelationMergingTest < ActiveRecord::TestCase
2120
+ # Use nvarchar string (N'') in assert
2121
+ coerce_tests! :test_merging_with_order_with_binds
2122
+ def test_merging_with_order_with_binds_coerced
2123
+ relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
2124
+ assert_equal ["title LIKE N'%suffix'"], relation.order_values
2125
+ end
2126
+
2127
+ # Same as original but change first regexp to match sp_executesql binding syntax
2128
+ coerce_tests! :test_merge_doesnt_duplicate_same_clauses
2129
+ def test_merge_doesnt_duplicate_same_clauses_coerced
2130
+ david, mary, bob = authors(:david, :mary, :bob)
2131
+
2132
+ non_mary_and_bob = Author.where.not(id: [mary, bob])
2133
+
2134
+ author_id = Author.lease_connection.quote_table_name("authors.id")
2135
+ assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
2136
+ assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
2137
+ end
2138
+
2139
+ only_david = Author.where("#{author_id} IN (?)", david)
2140
+
2141
+ assert_queries_match(/WHERE \(#{Regexp.escape(author_id)} IN \(@\d\)\)/) do
2142
+ assert_equal [david], only_david.merge(only_david)
2143
+ end
2144
+ end
2145
+ end
2146
+
2147
+ module ActiveRecord
2148
+ class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
2149
+ # SQL Server does not allow truncation of tables that are referenced by foreign key
2150
+ # constraints. As this test truncates all tables we would need to remove all foreign
2151
+ # key constraints and then restore them afterwards to get this test to pass.
2152
+ coerce_tests! :test_truncate_tables
2153
+ end
2154
+ end
2155
+
2156
+ require "models/book"
2157
+ class EnumTest < ActiveRecord::TestCase
2158
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2159
+ coerce_tests! %r{enums are distinct per class}
2160
+ test "enums are distinct per class coerced" do
2161
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2162
+
2163
+ send(:'original_enums are distinct per class')
2164
+ ensure
2165
+ Book.where(author_id: nil, name: nil).delete_all
2166
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2167
+ end
2168
+
2169
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2170
+ coerce_tests! %r{creating new objects with enum scopes}
2171
+ test "creating new objects with enum scopes coerced" do
2172
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2173
+
2174
+ send(:'original_creating new objects with enum scopes')
2175
+ ensure
2176
+ Book.where(author_id: nil, name: nil).delete_all
2177
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2178
+ end
2179
+
2180
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2181
+ coerce_tests! %r{enums are inheritable}
2182
+ test "enums are inheritable coerced" do
2183
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2184
+
2185
+ send(:'original_enums are inheritable')
2186
+ ensure
2187
+ Book.where(author_id: nil, name: nil).delete_all
2188
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2189
+ end
2190
+
2191
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2192
+ coerce_tests! %r{serializable\? with large number label}
2193
+ test "serializable? with large number label coerced" do
2194
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2195
+
2196
+ send(:'original_serializable\? with large number label')
2197
+ ensure
2198
+ Book.where(author_id: nil, name: nil).delete_all
2199
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2200
+ end
2201
+ end
2202
+
2203
+ require "models/task"
2204
+ class QueryCacheExpiryTest < ActiveRecord::TestCase
2205
+ # SQL Server does not support skipping or upserting duplicates.
2206
+ coerce_tests! :test_insert_all
2207
+ def test_insert_all_coerced
2208
+ assert_raises(ArgumentError, /does not support skipping duplicates/) do
2209
+ Task.cache { Task.insert({ starting: Time.now }) }
2210
+ end
2211
+
2212
+ assert_raises(ArgumentError, /does not support upsert/) do
2213
+ Task.cache { Task.upsert({ starting: Time.now }) }
2214
+ end
2215
+
2216
+ assert_raises(ArgumentError, /does not support upsert/) do
2217
+ Task.cache { Task.upsert_all([{ starting: Time.now }]) }
2218
+ end
2219
+
2220
+ Task.cache do
2221
+ assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
2222
+ Task.insert_all!([ starting: Time.now ])
2223
+ end
2224
+
2225
+ assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
2226
+ Task.insert!({ starting: Time.now })
2227
+ end
2228
+ end
2229
+ end
2230
+ end
2231
+
2232
+ require "models/citation"
2233
+ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
2234
+ fixtures :citations
2235
+
2236
+ # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
2237
+ # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
2238
+ # (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
2239
+ # However, you cannot change the compatibility level during a test. The purpose of the test is to ensure that an
2240
+ # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
2241
+ # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
2242
+ # adapter is 2,098.
2243
+ coerce_tests! :test_eager_loading_too_many_ids
2244
+ def test_eager_loading_too_many_ids_coerced
2245
+ # Remove excess records.
2246
+ Citation.limit(32768).order(id: :desc).delete_all
2247
+
2248
+ # Perform test
2249
+ citation_count = Citation.count
2250
+ assert_queries_match(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
2251
+ assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
2252
+ end
2253
+ end
2254
+ end
2255
+
2256
+ class LogSubscriberTest < ActiveRecord::TestCase
2257
+ # Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
2258
+ coerce_tests! :test_verbose_query_logs
2259
+ def test_verbose_query_logs_coerced
2260
+ original_test_verbose_query_logs
2261
+ end
2262
+ end
2263
+
2264
+ class ReloadModelsTest < ActiveRecord::TestCase
2265
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
2266
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
2267
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2268
+ end
2269
+
2270
+ class MarshalSerializationTest < ActiveRecord::TestCase
2271
+ private
2272
+
2273
+ undef_method :marshal_fixture_path
2274
+ def marshal_fixture_path(file_name)
2275
+ File.expand_path(
2276
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.lease_connection.adapter_name}/#{file_name}.dump",
2277
+ ARTest::SQLServer.test_root_sqlserver
2278
+ )
2279
+ end
2280
+ end
2281
+
2282
+ class NestedThroughAssociationsTest < ActiveRecord::TestCase
2283
+ # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
2284
+ # "A column has been specified more than once in the order by list"
2285
+ # Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
2286
+ coerce_tests! :test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins, :test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
2287
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
2288
+ # preload table schemas
2289
+ Author.joins(:category_post_comments).first
2290
+
2291
+ assert_includes_and_joins_equal(
2292
+ Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
2293
+ [authors(:david), authors(:mary)], :category_post_comments
2294
+ )
2295
+ end
2296
+
2297
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
2298
+ # preload table schemas
2299
+ Category.joins(:post_comments).first
2300
+
2301
+ assert_includes_and_joins_equal(
2302
+ Category.where("comments.id" => comments(:more_greetings).id).order(:id),
2303
+ [categories(:general), categories(:technology)], :post_comments
2304
+ )
2305
+ end
2306
+ end
2307
+
2308
+ class PreloaderTest < ActiveRecord::TestCase
2309
+ # Need to handle query parameters in SQL regex.
2310
+ coerce_tests! :test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
2311
+ def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute_coerced
2312
+ order = cpk_orders(:cpk_groceries_order_2)
2313
+ _shop_id, order_id = order.id
2314
+ order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
2315
+
2316
+ assert_not_empty order_agreements
2317
+ assert_equal order_agreements.sort, order.order_agreements.sort
2318
+
2319
+ loaded_order = nil
2320
+ sql = capture_sql do
2321
+ loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
2322
+ end
2323
+
2324
+ assert_equal 2, sql.size
2325
+ preload_sql = sql.last
2326
+
2327
+ c = Cpk::OrderAgreement.lease_connection
2328
+ order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
2329
+ order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
2330
+ expectation = /SELECT.*WHERE.* #{order_id_constraint}/
2331
+
2332
+ assert_match(expectation, preload_sql)
2333
+ assert_equal order_agreements.sort, loaded_order.order_agreements.sort
2334
+ end
2335
+
2336
+ # Need to handle query parameters in SQL regex.
2337
+ coerce_tests! :test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
2338
+ def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute_coerced
2339
+ order_agreement = cpk_order_agreements(:order_agreement_three)
2340
+ order = cpk_orders(:cpk_groceries_order_2)
2341
+ assert_equal order, order_agreement.order
2342
+
2343
+ loaded_order_agreement = nil
2344
+ sql = capture_sql do
2345
+ loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
2346
+ end
2347
+
2348
+ assert_equal 2, sql.size
2349
+ preload_sql = sql.last
2350
+
2351
+ c = Cpk::Order.lease_connection
2352
+ order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
2353
+ order_constraint = /#{order_id} = @0.*@0 = \d+$/
2354
+ expectation = /SELECT.*WHERE.* #{order_constraint}/
2355
+
2356
+ assert_match(expectation, preload_sql)
2357
+ assert_equal order, loaded_order_agreement.order
2358
+ end
2359
+ end
2360
+
2361
+ class MigratorTest < ActiveRecord::TestCase
2362
+ # Test fails on Windows AppVeyor CI for unknown reason.
2363
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2364
+ end
2365
+
2366
+ class MultiDbMigratorTest < ActiveRecord::TestCase
2367
+ # Test fails on Windows AppVeyor CI for unknown reason.
2368
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2369
+ end
2370
+
2371
+ require "models/book"
2372
+ class FieldOrderedValuesTest < ActiveRecord::TestCase
2373
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2374
+ coerce_tests! :test_in_order_of_with_enums_values
2375
+ def test_in_order_of_with_enums_values_coerced
2376
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2377
+
2378
+ original_test_in_order_of_with_enums_values
2379
+ ensure
2380
+ Book.where(author_id: nil, name: nil).delete_all
2381
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2382
+ end
2383
+
2384
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2385
+ coerce_tests! :test_in_order_of_with_string_column
2386
+ def test_in_order_of_with_string_column_coerced
2387
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2388
+
2389
+ original_test_in_order_of_with_string_column
2390
+ ensure
2391
+ Book.where(author_id: nil, name: nil).delete_all
2392
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2393
+ end
2394
+
2395
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2396
+ coerce_tests! :test_in_order_of_with_enums_keys
2397
+ def test_in_order_of_with_enums_keys_coerced
2398
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2399
+
2400
+ original_test_in_order_of_with_enums_keys
2401
+ ensure
2402
+ Book.where(author_id: nil, name: nil).delete_all
2403
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2404
+ end
2405
+
2406
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2407
+ coerce_tests! :test_in_order_of_with_nil
2408
+ def test_in_order_of_with_nil_coerced
2409
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2410
+
2411
+ original_test_in_order_of_with_nil
2412
+ ensure
2413
+ Book.where(author_id: nil, name: nil).delete_all
2414
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2415
+ end
2416
+ end
2417
+
2418
+ require "models/dashboard"
2419
+ class QueryLogsTest < ActiveRecord::TestCase
2420
+ # SQL requires double single-quotes.
2421
+ coerce_tests! :test_sql_commenter_format
2422
+ def test_sql_commenter_format_coerced
2423
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2424
+ ActiveRecord::QueryLogs.tags = [:application]
2425
+
2426
+ assert_queries_match(%r{/\*application=''active_record''\*/}) do
2427
+ Dashboard.first
2428
+ end
2429
+ end
2430
+
2431
+ # SQL requires double single-quotes.
2432
+ coerce_tests! :test_sqlcommenter_format_value
2433
+ def test_sqlcommenter_format_value_coerced
2434
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2435
+
2436
+ ActiveRecord::QueryLogs.tags = [
2437
+ :application,
2438
+ { tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
2439
+ ]
2440
+
2441
+ assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2442
+ Dashboard.first
2443
+ end
2444
+ end
2445
+
2446
+ # SQL requires double single-quotes.
2447
+ coerce_tests! :test_sqlcommenter_format_value_string_coercible
2448
+ def test_sqlcommenter_format_value_string_coercible_coerced
2449
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2450
+
2451
+ ActiveRecord::QueryLogs.tags = [
2452
+ :application,
2453
+ { custom_proc: -> { 1234 } },
2454
+ ]
2455
+
2456
+ assert_queries_match(%r{custom_proc=''1234''\*/}) do
2457
+ Dashboard.first
2458
+ end
2459
+ end
2460
+
2461
+ # SQL requires double single-quotes.
2462
+ coerce_tests! :test_sqlcommenter_format_allows_string_keys
2463
+ def test_sqlcommenter_format_allows_string_keys_coerced
2464
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2465
+
2466
+ ActiveRecord::QueryLogs.tags = [
2467
+ :application,
2468
+ {
2469
+ "string" => "value",
2470
+ tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7",
2471
+ custom_proc: -> { "Joe's Shack" }
2472
+ },
2473
+ ]
2474
+
2475
+ assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2476
+ Dashboard.first
2477
+ end
2478
+ end
2479
+
2480
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2481
+ coerce_tests! :test_invalid_encoding_query
2482
+ def test_invalid_encoding_query_coerced
2483
+ ActiveRecord::QueryLogs.tags = [ :application ]
2484
+ assert_raises ActiveRecord::StatementInvalid do
2485
+ ActiveRecord::Base.lease_connection.execute "select 1 as '\xFF'"
2486
+ end
2487
+ end
2488
+ end
2489
+
2490
+ class InsertAllTest < ActiveRecord::TestCase
2491
+ # Same as original but using INSERTED.name as UPPER argument
2492
+ coerce_tests! :test_insert_all_returns_requested_sql_fields
2493
+ def test_insert_all_returns_requested_sql_fields_coerced
2494
+ skip unless supports_insert_returning?
2495
+
2496
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2497
+ assert_equal %w[ REWORK ], result.pluck("name")
2498
+ end
2499
+
2500
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2501
+ coerce_tests! :test_insert_with_type_casting_and_serialize_is_consistent
2502
+ def test_insert_with_type_casting_and_serialize_is_consistent_coerced
2503
+ connection.remove_index(:books, column: [:author_id, :name])
2504
+
2505
+ original_test_insert_with_type_casting_and_serialize_is_consistent
2506
+ ensure
2507
+ Book.where(author_id: nil, name: '["Array"]').delete_all
2508
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2509
+ end
2510
+ end
2511
+
2512
+ module ActiveRecord
2513
+ class Migration
2514
+ class InvalidOptionsTest < ActiveRecord::TestCase
2515
+ # Include the additional SQL Server migration options.
2516
+ undef_method :invalid_add_column_option_exception_message
2517
+ def invalid_add_column_option_exception_message(key)
2518
+ default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2519
+ default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2520
+
2521
+ "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2522
+ end
2523
+ end
2524
+ end
2525
+ end
2526
+
2527
+ # SQL Server does not support upsert. Removed dependency on `insert_all` that uses upsert.
2528
+ class ActiveRecord::Encryption::ConcurrencyTest < ActiveRecord::EncryptionTestCase
2529
+ undef_method :thread_encrypting_and_decrypting
2530
+ def thread_encrypting_and_decrypting(thread_label)
2531
+ posts = 100.times.collect { |index| EncryptedPost.create! title: "Article #{index} (#{thread_label})", body: "Body #{index} (#{thread_label})" }
2532
+
2533
+ Thread.new do
2534
+ posts.each.with_index do |article, index|
2535
+ assert_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2536
+ article.decrypt
2537
+ assert_not_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2538
+ end
2539
+ end
2540
+ end
2541
+ end
2542
+
2543
+ # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2544
+ # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2545
+ class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2546
+ private
2547
+ undef_method :serializer
2548
+ def serializer
2549
+ @serializer ||= ::MessagePack::Factory.new.tap do |factory|
2550
+ ActiveRecord::MessagePack::Extensions.install(factory)
2551
+ ActiveSupport::MessagePack::Extensions.install(factory)
2552
+ ActiveSupport::MessagePack::Extensions.install_unregistered_type_fallback(factory)
2553
+ end
2554
+ end
2555
+ end
2556
+
2557
+ class StoreTest < ActiveRecord::TestCase
2558
+ # Set the attribute as JSON type for the `StoreTest#saved changes tracking for accessors with json column` test.
2559
+ Admin::User.attribute :json_options, ActiveRecord::Type::SQLServer::Json.new
2560
+ end
2561
+
2562
+ class TestDatabasesTest < ActiveRecord::TestCase
2563
+ # Tests are not about a specific adapter.
2564
+ coerce_all_tests!
2565
+ end
2566
+
2567
+ module ActiveRecord
2568
+ module ConnectionAdapters
2569
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2570
+ # Tests are not about a specific adapter.
2571
+ coerce_all_tests!
2572
+ end
2573
+ end
2574
+ end
2575
+
2576
+ module ActiveRecord
2577
+ module ConnectionAdapters
2578
+ class ConnectionSwappingNestedTest < ActiveRecord::TestCase
2579
+ # Tests are not about a specific adapter.
2580
+ coerce_all_tests!
2581
+ end
2582
+ end
2583
+ end
2584
+
2585
+ module ActiveRecord
2586
+ module ConnectionAdapters
2587
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
2588
+ # Tests are not about a specific adapter.
2589
+ coerce_tests! :test_switching_connections_via_handler
2590
+ end
2591
+ end
2592
+ end
2593
+
2594
+ module ActiveRecord
2595
+ module ConnectionAdapters
2596
+ class ConnectionHandlersMultiPoolConfigTest < ActiveRecord::TestCase
2597
+ # Tests are not about a specific adapter.
2598
+ coerce_all_tests!
2599
+ end
2600
+ end
2601
+ end
2602
+
2603
+ module ActiveRecord
2604
+ class Migration
2605
+ class CheckConstraintTest < ActiveRecord::TestCase
2606
+ # SQL Server formats the check constraint expression differently.
2607
+ coerce_tests! :test_check_constraints
2608
+ def test_check_constraints_coerced
2609
+ check_constraints = @connection.check_constraints("products")
2610
+ assert_equal 1, check_constraints.size
2611
+
2612
+ constraint = check_constraints.first
2613
+ assert_equal "products", constraint.table_name
2614
+ assert_equal "products_price_check", constraint.name
2615
+ assert_equal "[price]>[discounted_price]", constraint.expression
2616
+ end
2617
+
2618
+ # SQL Server formats the check constraint expression differently.
2619
+ coerce_tests! :test_add_check_constraint
2620
+ def test_add_check_constraint_coerced
2621
+ @connection.add_check_constraint :trades, "quantity > 0"
2622
+
2623
+ check_constraints = @connection.check_constraints("trades")
2624
+ assert_equal 1, check_constraints.size
2625
+
2626
+ constraint = check_constraints.first
2627
+ assert_equal "trades", constraint.table_name
2628
+ assert_equal "chk_rails_2189e9f96c", constraint.name
2629
+ assert_equal "[quantity]>(0)", constraint.expression
2630
+ end
2631
+
2632
+ # SQL Server formats the check constraint expression differently.
2633
+ coerce_tests! :test_remove_check_constraint
2634
+ def test_remove_check_constraint_coerced
2635
+ @connection.add_check_constraint :trades, "price > 0", name: "price_check"
2636
+ @connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
2637
+
2638
+ assert_equal 2, @connection.check_constraints("trades").size
2639
+ @connection.remove_check_constraint :trades, name: "quantity_check"
2640
+ assert_equal 1, @connection.check_constraints("trades").size
2641
+
2642
+ constraint = @connection.check_constraints("trades").first
2643
+ assert_equal "trades", constraint.table_name
2644
+ assert_equal "price_check", constraint.name
2645
+ assert_equal "[price]>(0)", constraint.expression
2646
+
2647
+ @connection.remove_check_constraint :trades, name: :price_check # name as a symbol
2648
+ assert_empty @connection.check_constraints("trades")
2649
+ end
2650
+ end
2651
+ end
2652
+ end
2653
+
2654
+ module ActiveRecord
2655
+ module ConnectionAdapters
2656
+ class PoolConfig
2657
+ class ResolverTest < ActiveRecord::TestCase
2658
+ # SQL Server was not included in the list of available adapters in the error message.
2659
+ coerce_tests! :test_url_invalid_adapter
2660
+ def test_url_invalid_adapter_coerced
2661
+ error = assert_raises(AdapterNotFound) do
2662
+ Base.connection_handler.establish_connection "ridiculous://foo?encoding=utf8"
2663
+ end
2664
+
2665
+ assert_match "Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.", error.message
2666
+ end
2667
+ end
2668
+ end
2669
+ end
2670
+ end
2671
+
2672
+ module ActiveRecord
2673
+ module ConnectionAdapters
2674
+ class RegistrationIsolatedTest < ActiveRecord::TestCase
2675
+ # SQL Server was not included in the list of available adapters in the error message.
2676
+ coerce_tests! %r{resolve raises if the adapter is using the pre 7.2 adapter registration API}
2677
+ def resolve_raises_if_the_adapter_is_using_the_pre_7_2_adapter_registration_API
2678
+ exception = assert_raises(ActiveRecord::AdapterNotFound) do
2679
+ ActiveRecord::ConnectionAdapters.resolve("fake_legacy")
2680
+ end
2681
+
2682
+ assert_equal(
2683
+ "Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.",
2684
+ exception.message
2685
+ )
2686
+ ensure
2687
+ ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).delete("fake_legacy")
2688
+ end
2689
+ end
2690
+ end
2691
+ end
2692
+
2693
+
2694
+ module ActiveRecord
2695
+ class TableMetadataTest < ActiveSupport::TestCase
2696
+ # Adapter returns an object that is subclass of what is expected in the original test.
2697
+ coerce_tests! %r{#associated_table creates the right type caster for joined table with different association name}
2698
+ def associated_table_creates_the_right_type_caster_for_joined_table_with_different_association_name_coerced
2699
+ base_table_metadata = TableMetadata.new(AuditRequiredDeveloper, Arel::Table.new("developers"))
2700
+
2701
+ associated_table_metadata = base_table_metadata.associated_table("audit_logs")
2702
+
2703
+ assert associated_table_metadata.arel_table.type_for_attribute(:message).is_a?(ActiveRecord::Type::String)
2704
+ end
2705
+ end
2706
+ end
2707
+
2708
+ module ActiveRecord
2709
+ module TypeCaster
2710
+ class ConnectionTest < ActiveSupport::TestCase
2711
+ # Adapter returns an object that is subclass of what is expected in the original test.
2712
+ coerce_tests! %r{#type_for_attribute is not aware of custom types}
2713
+ def type_for_attribute_is_not_aware_of_custom_types_coerced
2714
+ type_caster = Connection.new(AttributedDeveloper, "developers")
2715
+
2716
+ type = type_caster.type_for_attribute(:name)
2717
+
2718
+ assert_not_equal DeveloperName, type.class
2719
+ assert type.is_a?(ActiveRecord::Type::String)
2720
+ end
2721
+ end
2722
+ end
2723
+ end
2724
+
2725
+ require "models/car"
2726
+ class ExplainTest < ActiveRecord::TestCase
2727
+ # Expected query slightly different from because of 'sp_executesql' and query parameters.
2728
+ coerce_tests! :test_relation_explain_with_first
2729
+ def test_relation_explain_with_first_coerced
2730
+ expected_query = capture_sql {
2731
+ Car.all.first
2732
+ }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2733
+ message = Car.all.explain.first
2734
+ assert_match(/^EXPLAIN/, message)
2735
+ assert_match(expected_query, message)
2736
+ end
2737
+
2738
+ # Expected query slightly different from because of 'sp_executesql' and query parameters.
2739
+ coerce_tests! :test_relation_explain_with_last
2740
+ def test_relation_explain_with_last_coerced
2741
+ expected_query = capture_sql {
2742
+ Car.all.last
2743
+ }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2744
+ expected_query = expected_query
2745
+ message = Car.all.explain.last
2746
+
2747
+ assert_match(/^EXPLAIN/, message)
2748
+ assert_match(expected_query, message)
2749
+ end
2750
+ end
2751
+
2752
+ module ActiveRecord
2753
+ module Assertions
2754
+ class QueryAssertionsTest < ActiveSupport::TestCase
2755
+ # Query slightly different in original test.
2756
+ coerce_tests! :test_assert_queries_match
2757
+ def test_assert_queries_match_coerced
2758
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 1) { Post.first }
2759
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i) { Post.first }
2760
+
2761
+ error = assert_raises(Minitest::Assertion) {
2762
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 2) { Post.first }
2763
+ }
2764
+ assert_match(/1 instead of 2 queries/, error.message)
2765
+
2766
+ error = assert_raises(Minitest::Assertion) {
2767
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 0) { Post.first }
2768
+ }
2769
+ assert_match(/1 instead of 0 queries/, error.message)
2770
+ end
2771
+ end
2772
+ end
2773
+ end
2774
+
2775
+ module ActiveRecord
2776
+ class WithTest < ActiveRecord::TestCase
2777
+ # SQL contains just 'WITH' instead of 'WITH RECURSIVE' as expected by the original test.
2778
+ coerce_tests! :test_with_recursive
2779
+ def test_with_recursive_coerced
2780
+ top_companies = Company.where(firm_id: nil).to_a
2781
+ child_companies = Company.where(firm_id: top_companies).to_a
2782
+ top_companies_and_children = (top_companies.map(&:id) + child_companies.map(&:id)).sort
2783
+
2784
+ relation = Company.with_recursive(
2785
+ top_companies_and_children: [
2786
+ Company.where(firm_id: nil),
2787
+ Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id"),
2788
+ ]
2789
+ ).from("top_companies_and_children AS companies")
2790
+
2791
+ assert_equal top_companies_and_children, relation.order(:id).pluck(:id)
2792
+ assert_match "WITH ", relation.to_sql
2793
+ end
2794
+ end
2795
+ end
2796
+