activerecord-sqlserver-adapter 6.0.3 → 6.1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -3
  3. data/CHANGELOG.md +18 -77
  4. data/Gemfile +1 -2
  5. data/README.md +13 -2
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/appveyor.yml +7 -5
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +0 -2
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -13
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -4
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +0 -2
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +0 -2
  14. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
  16. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -3
  17. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  18. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  19. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +7 -5
  20. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
  21. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  22. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  23. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +83 -66
  25. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
  26. data/lib/active_record/sqlserver_base.rb +9 -15
  27. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  28. data/lib/arel/visitors/sqlserver.rb +60 -28
  29. data/test/cases/adapter_test_sqlserver.rb +17 -15
  30. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  31. data/test/cases/coerced_tests.rb +470 -95
  32. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  33. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  34. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  35. data/test/cases/migration_test_sqlserver.rb +7 -0
  36. data/test/cases/order_test_sqlserver.rb +7 -0
  37. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  38. data/test/cases/rake_test_sqlserver.rb +3 -2
  39. data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
  40. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  41. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  42. data/test/schema/sqlserver_specific_schema.rb +7 -0
  43. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  45. data/test/support/sql_counter_sqlserver.rb +14 -12
  46. metadata +23 -9
@@ -24,22 +24,35 @@ class UniquenessValidationTest < ActiveRecord::TestCase
24
24
  end
25
25
  end
26
26
 
27
- # Skip the test if database is case-insensitive.
28
- coerce_tests! :test_validate_case_sensitive_uniqueness_by_default
29
- def test_validate_case_sensitive_uniqueness_by_default_coerced
30
- database_collation = connection.select_one("SELECT collation_name FROM sys.databases WHERE name = 'activerecord_unittest'").values.first
31
- skip if database_collation.include?("_CI_")
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)
32
31
 
33
- original_test_validate_case_sensitive_uniqueness_by_default_coerced
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
34
47
  end
35
48
  end
36
49
 
37
50
  require "models/event"
38
51
  module ActiveRecord
39
52
  class AdapterTest < ActiveRecord::TestCase
40
- # I really don`t think we can support legacy binds.
41
- coerce_tests! :test_select_all_with_legacy_binds
42
- coerce_tests! :test_insert_update_delete_with_legacy_binds
53
+ # Legacy binds are not supported.
54
+ coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
55
+ coerce_tests! :test_select_all_insert_update_delete_with_legacy_binds
43
56
 
44
57
  # As far as I can tell, SQL Server does not support null bytes in strings.
45
58
  coerce_tests! :test_update_prepared_statement
@@ -54,13 +67,63 @@ module ActiveRecord
54
67
  assert_not_nil error.cause
55
68
  end
56
69
  end
70
+ end
71
+ end
57
72
 
73
+ module ActiveRecord
74
+ class AdapterPreventWritesTest < ActiveRecord::TestCase
58
75
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
59
76
  coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
60
77
  def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
61
78
  Subscriber.send(:load_schema!)
62
79
  original_test_errors_when_an_insert_query_is_called_while_preventing_writes
63
80
  end
81
+
82
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
83
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
84
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
85
+ Subscriber.send(:load_schema!)
86
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
87
+ end
88
+
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_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
91
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
92
+ Subscriber.send(:load_schema!)
93
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
94
+ end
95
+ end
96
+ end
97
+
98
+ module ActiveRecord
99
+ class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
100
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
101
+ coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
102
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
103
+ Subscriber.send(:load_schema!)
104
+ original_test_errors_when_an_insert_query_is_called_while_preventing_writes
105
+ end
106
+
107
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
108
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
109
+ def test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes_coerced
110
+ Subscriber.send(:load_schema!)
111
+ original_test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
112
+ end
113
+
114
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
115
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
116
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
117
+ Subscriber.send(:load_schema!)
118
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
119
+ end
120
+
121
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
122
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
123
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
124
+ Subscriber.send(:load_schema!)
125
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
126
+ end
64
127
  end
65
128
  end
66
129
 
@@ -176,7 +239,7 @@ class BasicsTest < ActiveRecord::TestCase
176
239
  # SQL Server does not have query for release_savepoint
177
240
  coerce_tests! %r{an empty transaction does not raise if preventing writes}
178
241
  test "an empty transaction does not raise if preventing writes coerced" do
179
- ActiveRecord::Base.connection_handler.while_preventing_writes do
242
+ ActiveRecord::Base.while_preventing_writes do
180
243
  assert_queries(1, ignore_none: true) do
181
244
  Bird.transaction do
182
245
  ActiveRecord::Base.connection.materialize_transactions
@@ -234,6 +297,55 @@ module ActiveRecord
234
297
  coerce_tests! :test_statement_cache_with_find_by
235
298
  coerce_tests! :test_statement_cache_with_in_clause
236
299
  coerce_tests! :test_statement_cache_with_sql_string_literal
300
+
301
+ # Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
302
+ coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
303
+ def test_bind_params_to_sql_with_prepared_statements_coerced
304
+ assert_bind_params_to_sql_coerced(prepared: true)
305
+ end
306
+
307
+ def test_bind_params_to_sql_with_unprepared_statements_coerced
308
+ @connection.unprepared_statement do
309
+ assert_bind_params_to_sql_coerced(prepared: false)
310
+ end
311
+ end
312
+
313
+ private
314
+
315
+ def assert_bind_params_to_sql_coerced(prepared:)
316
+ table = Author.quoted_table_name
317
+ pk = "#{table}.#{Author.quoted_primary_key}"
318
+
319
+ # prepared_statements: true
320
+ #
321
+ # 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
322
+ #
323
+ # prepared_statements: false
324
+ #
325
+ # SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
326
+ #
327
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
328
+ 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"
329
+
330
+ authors = Author.where(id: [1, 2, 3, nil])
331
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
332
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
333
+
334
+ # prepared_statements: true
335
+ #
336
+ # 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
337
+ #
338
+ # prepared_statements: false
339
+ #
340
+ # SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
341
+ #
342
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
343
+ 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"
344
+
345
+ authors = Author.where(id: [1, 2, 3, 9223372036854775808])
346
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
347
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
348
+ end
237
349
  end
238
350
  end
239
351
 
@@ -256,11 +368,112 @@ class CalculationsTest < ActiveRecord::TestCase
256
368
  original_test_offset_is_kept
257
369
  end
258
370
 
259
- # Are decimal, not integer.
371
+ # The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
260
372
  coerce_tests! :test_should_return_decimal_average_of_integer_field
261
373
  def test_should_return_decimal_average_of_integer_field_coerced
262
374
  value = Account.average(:id)
263
- assert_equal BigDecimal("3.0").to_s, BigDecimal(value).to_s
375
+ assert_equal 3, value
376
+ end
377
+
378
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
379
+ # Match SQL Server limit implementation.
380
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
381
+ def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
382
+ rails_core = companies(:rails_core)
383
+
384
+ sql = <<~SQL
385
+ SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
386
+ FROM accounts
387
+ WHERE firm_id = ?
388
+ GROUP BY firm_id
389
+ ORDER BY firm_id
390
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
391
+ SQL
392
+
393
+ account = Account.find_by_sql([sql, rails_core]).first
394
+
395
+ # id was not selected, so it should be nil
396
+ # (cannot select id because it wasn't used in the GROUP BY clause)
397
+ assert_nil account.id
398
+
399
+ # firm_id was explicitly selected, so it should be present
400
+ assert_equal(rails_core, account.firm)
401
+
402
+ # avg_credit_limit should be present as a virtual attribute
403
+ assert_equal(52.5, account.avg_credit_limit)
404
+ end
405
+
406
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
407
+ # Order column must be in the GROUP clause.
408
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
409
+ def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
410
+ rails_core = companies(:rails_core)
411
+
412
+ account = Account
413
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
414
+ .where(firm: rails_core)
415
+ .group(:firm_id)
416
+ .order(:firm_id)
417
+ .take!
418
+
419
+ # id was not selected, so it should be nil
420
+ # (cannot select id because it wasn't used in the GROUP BY clause)
421
+ assert_nil account.id
422
+
423
+ # firm_id was explicitly selected, so it should be present
424
+ assert_equal(rails_core, account.firm)
425
+
426
+ # avg_credit_limit should be present as a virtual attribute
427
+ assert_equal(52.5, account.avg_credit_limit)
428
+ end
429
+
430
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
431
+ # SELECT columns must be in the GROUP clause.
432
+ # Match SQL Server limit implementation.
433
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
434
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
435
+ rails_core = companies(:rails_core)
436
+
437
+ sql = <<~SQL
438
+ SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
439
+ FROM companies
440
+ INNER JOIN accounts ON companies.id = accounts.firm_id
441
+ WHERE companies.id = ?
442
+ GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description
443
+ ORDER BY companies.id
444
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
445
+ SQL
446
+
447
+ firm = DependentFirm.find_by_sql([sql, rails_core]).first
448
+
449
+ # all the DependentFirm attributes should be present
450
+ assert_equal rails_core, firm
451
+ assert_equal rails_core.name, firm.name
452
+
453
+ # avg_credit_limit should be present as a virtual attribute
454
+ assert_equal(52.5, firm.avg_credit_limit)
455
+ end
456
+
457
+
458
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
459
+ # SELECT columns must be in the GROUP clause.
460
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
461
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
462
+ rails_core = companies(:rails_core)
463
+
464
+ firm = DependentFirm
465
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
466
+ .where(id: rails_core)
467
+ .joins(:account)
468
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description)
469
+ .take!
470
+
471
+ # all the DependentFirm attributes should be present
472
+ assert_equal rails_core, firm
473
+ assert_equal rails_core.name, firm.name
474
+
475
+ # avg_credit_limit should be present as a virtual attribute
476
+ assert_equal(52.5, firm.avg_credit_limit)
264
477
  end
265
478
 
266
479
  # Match SQL Server limit implementation
@@ -329,14 +542,18 @@ module ActiveRecord
329
542
  coerce_tests! :test_quote_ar_object
330
543
  def test_quote_ar_object_coerced
331
544
  value = DatetimePrimaryKey.new(id: @time)
332
- assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
545
+ assert_deprecated do
546
+ assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
547
+ end
333
548
  end
334
549
 
335
550
  # Use our date format.
336
551
  coerce_tests! :test_type_cast_ar_object
337
552
  def test_type_cast_ar_object_coerced
338
553
  value = DatetimePrimaryKey.new(id: @time)
339
- assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
554
+ assert_deprecated do
555
+ assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
556
+ end
340
557
  end
341
558
  end
342
559
  end
@@ -551,11 +768,6 @@ module ActiveRecord
551
768
  end
552
769
  end
553
770
 
554
- class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
555
- # Skip this test with /tmp/my_schema_cache.yml path on Windows.
556
- coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
557
- end
558
-
559
771
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
560
772
  # We extend `local_database?` so that common VM IPs can be used.
561
773
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
@@ -620,7 +832,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
620
832
  end
621
833
 
622
834
  require "models/topic"
835
+ require "models/customer"
836
+ require "models/non_primary_key"
623
837
  class FinderTest < ActiveRecord::TestCase
838
+ fixtures :customers, :topics, :authors
839
+
624
840
  # We have implicit ordering, via FETCH.
625
841
  coerce_tests! %r{doesn't have implicit ordering},
626
842
  :test_find_doesnt_have_implicit_ordering
@@ -665,6 +881,87 @@ class FinderTest < ActiveRecord::TestCase
665
881
  end
666
882
  end
667
883
  end
884
+
885
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
886
+ coerce_tests! :test_include_on_unloaded_relation_with_match
887
+ def test_include_on_unloaded_relation_with_match_coerced
888
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
889
+ assert_equal true, Customer.where(name: "David").include?(customers(:david))
890
+ end
891
+ end
892
+
893
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
894
+ coerce_tests! :test_include_on_unloaded_relation_without_match
895
+ def test_include_on_unloaded_relation_without_match_coerced
896
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
897
+ assert_equal false, Customer.where(name: "David").include?(customers(:mary))
898
+ end
899
+ end
900
+
901
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
902
+ coerce_tests! :test_member_on_unloaded_relation_with_match
903
+ def test_member_on_unloaded_relation_with_match_coerced
904
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
905
+ assert_equal true, Customer.where(name: "David").member?(customers(:david))
906
+ end
907
+ end
908
+
909
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
910
+ coerce_tests! :test_member_on_unloaded_relation_without_match
911
+ def test_member_on_unloaded_relation_without_match_coerced
912
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
913
+ assert_equal false, Customer.where(name: "David").member?(customers(:mary))
914
+ end
915
+ end
916
+
917
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
918
+ coerce_tests! :test_implicit_order_column_is_configurable
919
+ def test_implicit_order_column_is_configurable_coerced
920
+ old_implicit_order_column = Topic.implicit_order_column
921
+ Topic.implicit_order_column = "title"
922
+
923
+ assert_equal topics(:fifth), Topic.first
924
+ assert_equal topics(:third), Topic.last
925
+
926
+ c = Topic.connection
927
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
928
+ Topic.last
929
+ }
930
+ ensure
931
+ Topic.implicit_order_column = old_implicit_order_column
932
+ end
933
+
934
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
935
+ coerce_tests! :test_implicit_order_set_to_primary_key
936
+ def test_implicit_order_set_to_primary_key_coerced
937
+ old_implicit_order_column = Topic.implicit_order_column
938
+ Topic.implicit_order_column = "id"
939
+
940
+ c = Topic.connection
941
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
942
+ Topic.last
943
+ }
944
+ ensure
945
+ Topic.implicit_order_column = old_implicit_order_column
946
+ end
947
+
948
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
949
+ coerce_tests! :test_implicit_order_for_model_without_primary_key
950
+ def test_implicit_order_for_model_without_primary_key_coerced
951
+ old_implicit_order_column = NonPrimaryKey.implicit_order_column
952
+ NonPrimaryKey.implicit_order_column = "created_at"
953
+
954
+ c = NonPrimaryKey.connection
955
+
956
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
957
+ NonPrimaryKey.last
958
+ }
959
+ ensure
960
+ NonPrimaryKey.implicit_order_column = old_implicit_order_column
961
+ end
962
+
963
+ # SQL Server is unable to use aliased SELECT in the HAVING clause.
964
+ coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
668
965
  end
669
966
 
670
967
  module ActiveRecord
@@ -896,7 +1193,14 @@ class RelationTest < ActiveRecord::TestCase
896
1193
  coerce_tests! :test_reorder_with_first
897
1194
  def test_reorder_with_first_coerced
898
1195
  sql_log = capture_sql do
899
- assert Post.order(:title).reorder(nil).first
1196
+ message = <<~MSG.squish
1197
+ `.reorder(nil)` with `.first` / `.first!` no longer
1198
+ takes non-deterministic result in Rails 6.2.
1199
+ To continue taking non-deterministic result, use `.take` / `.take!` instead.
1200
+ MSG
1201
+ assert_deprecated(message) do
1202
+ assert Post.order(:title).reorder(nil).first
1203
+ end
900
1204
  end
901
1205
  assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
902
1206
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
@@ -961,6 +1265,16 @@ module ActiveRecord
961
1265
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
962
1266
  assert_equal expected, query
963
1267
  end
1268
+
1269
+ # Original Rails test fails on Windows CI because the dump file was not being binary read.
1270
+ coerce_tests! :test_marshal_load_legacy_relation
1271
+ def test_marshal_load_legacy_relation_coerced
1272
+ path = File.expand_path(
1273
+ "support/marshal_compatibility_fixtures/legacy_relation.dump",
1274
+ ARTest::SQLServer.root_activerecord_test
1275
+ )
1276
+ assert_equal 11, Marshal.load(File.binread(path)).size
1277
+ end
964
1278
  end
965
1279
  end
966
1280
 
@@ -1088,8 +1402,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
1088
1402
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1089
1403
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
1090
1404
  author = Author.select("authors.*, 5 as posts_count").first
1091
- dumped_author = YAML.dump(author)
1092
- dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
1405
+ dumped = YAML.load(YAML.dump(author))
1093
1406
  assert_equal 5, author.posts_count
1094
1407
  assert_equal 5, dumped.posts_count
1095
1408
  end
@@ -1208,7 +1521,6 @@ module ActiveRecord
1208
1521
 
1209
1522
  original_test_statement_cache_values_differ
1210
1523
  ensure
1211
- Book.where(author_id: nil, name: 'my book').delete_all
1212
1524
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1213
1525
  end
1214
1526
  end
@@ -1217,8 +1529,43 @@ end
1217
1529
  module ActiveRecord
1218
1530
  module ConnectionAdapters
1219
1531
  class SchemaCacheTest < ActiveRecord::TestCase
1532
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1533
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1534
+
1535
+ # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1536
+ coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1537
+
1538
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1539
+ unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1540
+ def test_marshal_dump_and_load_with_gzip_coerced
1541
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1542
+ end
1543
+ def test_marshal_dump_and_load_via_disk_coerced
1544
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1545
+ end
1546
+ end
1547
+
1220
1548
  private
1221
1549
 
1550
+ def with_marshable_time_defaults
1551
+ # Detect problems
1552
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1553
+ column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1554
+ current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1555
+ end
1556
+
1557
+ # Correct problems
1558
+ if current_default.present?
1559
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1560
+ end
1561
+
1562
+ # Run original test
1563
+ yield
1564
+ ensure
1565
+ # Revert changes
1566
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
1567
+ end
1568
+
1222
1569
  # We need to give the full path for this to work.
1223
1570
  def schema_dump_path
1224
1571
  File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
@@ -1235,19 +1582,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1235
1582
  # Use LEN() vs length() function.
1236
1583
  coerce_tests! %r{order: always allows Arel}
1237
1584
  test "order: always allows Arel" do
1238
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1239
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
1585
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1240
1586
 
1241
- assert_equal ids_depr, ids_disabled
1587
+ assert_not_empty titles
1242
1588
  end
1243
1589
 
1244
1590
  # Use LEN() vs length() function.
1245
1591
  coerce_tests! %r{pluck: always allows Arel}
1246
1592
  test "pluck: always allows Arel" do
1247
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1248
- values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1593
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1594
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1249
1595
 
1250
- assert_equal values_depr, values_disabled
1596
+ assert_equal excepted_values, values
1251
1597
  end
1252
1598
 
1253
1599
  # Use LEN() vs length() function.
@@ -1255,91 +1601,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1255
1601
  test "order: allows valid Array arguments" do
1256
1602
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1257
1603
 
1258
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1259
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "len(title)"]).pluck(:id) }
1604
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1260
1605
 
1261
- assert_equal ids_expected, ids_depr
1262
- assert_equal ids_expected, ids_disabled
1606
+ assert_equal ids_expected, ids
1263
1607
  end
1264
1608
 
1265
1609
  test "order: allows string column names that are quoted" do
1266
1610
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1267
1611
 
1268
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[id]").pluck(:id) }
1269
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[id]").pluck(:id) }
1612
+ ids = Post.order("[id]").pluck(:id)
1270
1613
 
1271
- assert_equal ids_expected, ids_depr
1272
- assert_equal ids_expected, ids_disabled
1614
+ assert_equal ids_expected, ids
1273
1615
  end
1274
1616
 
1275
1617
  test "order: allows string column names that are quoted with table" do
1276
1618
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1277
1619
 
1278
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[posts].[id]").pluck(:id) }
1279
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[posts].[id]").pluck(:id) }
1620
+ ids = Post.order("[posts].[id]").pluck(:id)
1280
1621
 
1281
- assert_equal ids_expected, ids_depr
1282
- assert_equal ids_expected, ids_disabled
1622
+ assert_equal ids_expected, ids
1283
1623
  end
1284
1624
 
1285
1625
  test "order: allows string column names that are quoted with table and user" do
1286
1626
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1287
1627
 
1288
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[dbo].[posts].[id]").pluck(:id) }
1289
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[dbo].[posts].[id]").pluck(:id) }
1628
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1290
1629
 
1291
- assert_equal ids_expected, ids_depr
1292
- assert_equal ids_expected, ids_disabled
1630
+ assert_equal ids_expected, ids
1293
1631
  end
1294
1632
 
1295
1633
  test "order: allows string column names that are quoted with table, user and database" do
1296
1634
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1297
1635
 
1298
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
1299
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
1636
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1300
1637
 
1301
- assert_equal ids_expected, ids_depr
1302
- assert_equal ids_expected, ids_disabled
1638
+ assert_equal ids_expected, ids
1303
1639
  end
1304
1640
 
1305
1641
  test "pluck: allows string column name that are quoted" do
1306
1642
  titles_expected = Post.pluck(Arel.sql("title"))
1307
1643
 
1308
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[title]") }
1309
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[title]") }
1644
+ titles = Post.pluck("[title]")
1310
1645
 
1311
- assert_equal titles_expected, titles_depr
1312
- assert_equal titles_expected, titles_disabled
1646
+ assert_equal titles_expected, titles
1313
1647
  end
1314
1648
 
1315
1649
  test "pluck: allows string column name that are quoted with table" do
1316
1650
  titles_expected = Post.pluck(Arel.sql("title"))
1317
1651
 
1318
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[posts].[title]") }
1319
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[posts].[title]") }
1652
+ titles = Post.pluck("[posts].[title]")
1320
1653
 
1321
- assert_equal titles_expected, titles_depr
1322
- assert_equal titles_expected, titles_disabled
1654
+ assert_equal titles_expected, titles
1323
1655
  end
1324
1656
 
1325
1657
  test "pluck: allows string column name that are quoted with table and user" do
1326
1658
  titles_expected = Post.pluck(Arel.sql("title"))
1327
1659
 
1328
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[dbo].[posts].[title]") }
1329
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[dbo].[posts].[title]") }
1660
+ titles = Post.pluck("[dbo].[posts].[title]")
1330
1661
 
1331
- assert_equal titles_expected, titles_depr
1332
- assert_equal titles_expected, titles_disabled
1662
+ assert_equal titles_expected, titles
1333
1663
  end
1334
1664
 
1335
1665
  test "pluck: allows string column name that are quoted with table, user and database" do
1336
1666
  titles_expected = Post.pluck(Arel.sql("title"))
1337
1667
 
1338
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
1339
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
1668
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1340
1669
 
1341
- assert_equal titles_expected, titles_depr
1342
- assert_equal titles_expected, titles_disabled
1670
+ assert_equal titles_expected, titles
1343
1671
  end
1344
1672
  end
1345
1673
 
@@ -1381,6 +1709,25 @@ class RelationMergingTest < ActiveRecord::TestCase
1381
1709
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1382
1710
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1383
1711
  end
1712
+
1713
+ # Same as original but change first regexp to match sp_executesql binding syntax
1714
+ coerce_tests! :test_merge_doesnt_duplicate_same_clauses
1715
+ def test_merge_doesnt_duplicate_same_clauses_coerced
1716
+ david, mary, bob = authors(:david, :mary, :bob)
1717
+
1718
+ non_mary_and_bob = Author.where.not(id: [mary, bob])
1719
+
1720
+ author_id = Author.connection.quote_table_name("authors.id")
1721
+ assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
1722
+ assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
1723
+ end
1724
+
1725
+ only_david = Author.where("#{author_id} IN (?)", david)
1726
+
1727
+ assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
1728
+ assert_equal [david], only_david.merge(only_david)
1729
+ end
1730
+ end
1384
1731
  end
1385
1732
 
1386
1733
  module ActiveRecord
@@ -1401,7 +1748,6 @@ class EnumTest < ActiveRecord::TestCase
1401
1748
 
1402
1749
  send(:'original_enums are distinct per class')
1403
1750
  ensure
1404
- Book.where(author_id: nil, name: nil).delete_all
1405
1751
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1406
1752
  end
1407
1753
 
@@ -1412,7 +1758,6 @@ class EnumTest < ActiveRecord::TestCase
1412
1758
 
1413
1759
  send(:'original_creating new objects with enum scopes')
1414
1760
  ensure
1415
- Book.where(author_id: nil, name: nil).delete_all
1416
1761
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1417
1762
  end
1418
1763
 
@@ -1423,7 +1768,6 @@ class EnumTest < ActiveRecord::TestCase
1423
1768
 
1424
1769
  send(:'original_enums are inheritable')
1425
1770
  ensure
1426
- Book.where(author_id: nil, name: nil).delete_all
1427
1771
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1428
1772
  end
1429
1773
 
@@ -1434,7 +1778,6 @@ class EnumTest < ActiveRecord::TestCase
1434
1778
 
1435
1779
  send(:'original_declare multiple enums at a time')
1436
1780
  ensure
1437
- Book.where(author_id: nil, name: nil).delete_all
1438
1781
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1439
1782
  end
1440
1783
  end
@@ -1472,6 +1815,8 @@ end
1472
1815
 
1473
1816
  require "models/citation"
1474
1817
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1818
+ fixtures :citations
1819
+
1475
1820
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1476
1821
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1477
1822
  # (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
@@ -1479,14 +1824,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1479
1824
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1480
1825
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1481
1826
  # adapter is 2,098.
1482
- coerce_tests! :test_eager_loading_too_may_ids
1483
- def test_eager_loading_too_may_ids_coerced
1827
+ coerce_tests! :test_eager_loading_too_many_ids
1828
+ def test_eager_loading_too_many_ids_coerced
1484
1829
  # Remove excess records.
1485
1830
  Citation.limit(32768).order(id: :desc).delete_all
1486
1831
 
1487
1832
  # Perform test
1488
1833
  citation_count = Citation.count
1489
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1834
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1490
1835
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1491
1836
  end
1492
1837
  end
@@ -1509,22 +1854,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1509
1854
  end
1510
1855
  end
1511
1856
 
1512
- module ActiveRecord
1513
- module ConnectionAdapters
1514
- class ReaperTest < ActiveRecord::TestCase
1515
- # Coerce can be removed if Rails version > 6.0.3
1516
- coerce_tests! :test_connection_pool_starts_reaper_in_fork unless Process.respond_to?(:fork)
1517
- end
1518
- end
1519
- end
1520
-
1521
- class FixturesTest < ActiveRecord::TestCase
1522
- # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1523
- coerce_tests! :test_binary_in_fixtures if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1524
- end
1525
-
1526
1857
  class ReloadModelsTest < ActiveRecord::TestCase
1527
- # Skip test on Windows. The number of arguements passed to `IO.popen` in
1858
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
1528
1859
  # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1529
1860
  coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1530
1861
  end
@@ -1549,22 +1880,17 @@ class AnnotateTest < ActiveRecord::TestCase
1549
1880
  def test_annotate_is_sanitized_coerced
1550
1881
  quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1551
1882
 
1552
- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/}i) do
1883
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1553
1884
  posts = Post.select(:id).annotate("*/foo/*")
1554
1885
  assert posts.first
1555
1886
  end
1556
1887
 
1557
- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \*\* //foo// \*\* \*/}i) do
1888
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1558
1889
  posts = Post.select(:id).annotate("**//foo//**")
1559
1890
  assert posts.first
1560
1891
  end
1561
1892
 
1562
- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* \* //foo// \* \* \*/}i) do
1563
- posts = Post.select(:id).annotate("* *//foo//* *")
1564
- assert posts.first
1565
- end
1566
-
1567
- assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/ /\* \* /bar \*/}i) do
1893
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
1568
1894
  posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
1569
1895
  assert posts.first
1570
1896
  end
@@ -1576,6 +1902,17 @@ class AnnotateTest < ActiveRecord::TestCase
1576
1902
  end
1577
1903
  end
1578
1904
 
1905
+ class MarshalSerializationTest < ActiveRecord::TestCase
1906
+ private
1907
+
1908
+ def marshal_fixture_path(file_name)
1909
+ File.expand_path(
1910
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
1911
+ ARTest::SQLServer.test_root_sqlserver
1912
+ )
1913
+ end
1914
+ end
1915
+
1579
1916
  class NestedThroughAssociationsTest < ActiveRecord::TestCase
1580
1917
  # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
1581
1918
  # "A column has been specified more than once in the order by list"
@@ -1601,3 +1938,41 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1601
1938
  )
1602
1939
  end
1603
1940
  end
1941
+
1942
+ class BasePreventWritesTest < ActiveRecord::TestCase
1943
+ # SQL Server does not have query for release_savepoint
1944
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
1945
+ test "an empty transaction does not raise if preventing writes coerced" do
1946
+ ActiveRecord::Base.while_preventing_writes do
1947
+ assert_queries(1, ignore_none: true) do
1948
+ Bird.transaction do
1949
+ ActiveRecord::Base.connection.materialize_transactions
1950
+ end
1951
+ end
1952
+ end
1953
+ end
1954
+
1955
+ class BasePreventWritesLegacyTest < ActiveRecord::TestCase
1956
+ # SQL Server does not have query for release_savepoint
1957
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
1958
+ test "an empty transaction does not raise if preventing writes coerced" do
1959
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
1960
+ assert_queries(1, ignore_none: true) do
1961
+ Bird.transaction do
1962
+ ActiveRecord::Base.connection.materialize_transactions
1963
+ end
1964
+ end
1965
+ end
1966
+ end
1967
+ end
1968
+ end
1969
+
1970
+ class MigratorTest < ActiveRecord::TestCase
1971
+ # Test fails on Windows AppVeyor CI for unknown reason.
1972
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1973
+ end
1974
+
1975
+ class MultiDbMigratorTest < ActiveRecord::TestCase
1976
+ # Test fails on Windows AppVeyor CI for unknown reason.
1977
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1978
+ end