activerecord-sqlserver-adapter 6.0.0.rc2 → 6.1.0.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/CHANGELOG.md +20 -41
  4. data/README.md +32 -3
  5. data/RUNNING_UNIT_TESTS.md +1 -1
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -9
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -2
  10. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +28 -16
  12. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
  13. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  14. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +31 -8
  16. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
  17. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  18. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  19. data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  22. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -68
  23. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
  24. data/lib/active_record/sqlserver_base.rb +9 -15
  25. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  26. data/lib/arel/visitors/sqlserver.rb +111 -39
  27. data/test/cases/adapter_test_sqlserver.rb +48 -14
  28. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  29. data/test/cases/coerced_tests.rb +598 -78
  30. data/test/cases/column_test_sqlserver.rb +5 -2
  31. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  32. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  33. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  34. data/test/cases/lateral_test_sqlserver.rb +35 -0
  35. data/test/cases/migration_test_sqlserver.rb +51 -0
  36. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  37. data/test/cases/order_test_sqlserver.rb +7 -0
  38. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  39. data/test/cases/rake_test_sqlserver.rb +3 -2
  40. data/test/cases/schema_dumper_test_sqlserver.rb +20 -3
  41. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  42. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  43. data/test/schema/sqlserver_specific_schema.rb +17 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  45. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  46. data/test/support/sql_counter_sqlserver.rb +14 -12
  47. metadata +32 -13
  48. data/.travis.yml +0 -23
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "migrations/create_clients_and_change_column_collation"
5
+
6
+ class ChangeColumnCollationTestSqlServer < ActiveRecord::TestCase
7
+ before do
8
+ @old_verbose = ActiveRecord::Migration.verbose
9
+ ActiveRecord::Migration.verbose = false
10
+ CreateClientsAndChangeColumnCollation.new.up
11
+ end
12
+
13
+ after do
14
+ CreateClientsAndChangeColumnCollation.new.down
15
+ ActiveRecord::Migration.verbose = @old_verbose
16
+ end
17
+
18
+ def find_column(table, name)
19
+ table.find { |column| column.name == name }
20
+ end
21
+
22
+ let(:clients_table) { connection.columns("clients") }
23
+ let(:name_column) { find_column(clients_table, "name") }
24
+ let(:code_column) { find_column(clients_table, "code") }
25
+
26
+ it "change column collation to other than default" do
27
+ _(name_column.collation).must_equal "SQL_Latin1_General_CP1_CS_AS"
28
+ end
29
+
30
+ it "change column collation to default" do
31
+ _(code_column.collation).must_equal "SQL_Latin1_General_CP1_CI_AS"
32
+ end
33
+ end
@@ -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
@@ -396,39 +613,6 @@ module ActiveRecord
396
613
  end
397
614
 
398
615
  class MigrationTest < ActiveRecord::TestCase
399
- # We do not have do the DecimalWithoutScale type.
400
- coerce_tests! :test_add_table_with_decimals
401
- def test_add_table_with_decimals_coerced
402
- Person.connection.drop_table :big_numbers rescue nil
403
- assert !BigNumber.table_exists?
404
- GiveMeBigNumbers.up
405
- BigNumber.reset_column_information
406
- assert BigNumber.create(
407
- :bank_balance => 1586.43,
408
- :big_bank_balance => BigDecimal("1000234000567.95"),
409
- :world_population => 6000000000,
410
- :my_house_population => 3,
411
- :value_of_e => BigDecimal("2.7182818284590452353602875")
412
- )
413
- b = BigNumber.first
414
- assert_not_nil b
415
- assert_not_nil b.bank_balance
416
- assert_not_nil b.big_bank_balance
417
- assert_not_nil b.world_population
418
- assert_not_nil b.my_house_population
419
- assert_not_nil b.value_of_e
420
- assert_kind_of BigDecimal, b.world_population
421
- assert_equal "6000000000.0", b.world_population.to_s
422
- assert_kind_of Integer, b.my_house_population
423
- assert_equal 3, b.my_house_population
424
- assert_kind_of BigDecimal, b.bank_balance
425
- assert_equal BigDecimal("1586.43"), b.bank_balance
426
- assert_kind_of BigDecimal, b.big_bank_balance
427
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
428
- GiveMeBigNumbers.down
429
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
430
- end
431
-
432
616
  # For some reason our tests set Rails.@_env which breaks test env switching.
433
617
  coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
434
618
  coerce_tests! :test_internal_metadata_stores_environment
@@ -584,11 +768,6 @@ module ActiveRecord
584
768
  end
585
769
  end
586
770
 
587
- class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
588
- # Skip this test with /tmp/my_schema_cache.yml path on Windows.
589
- coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
590
- end
591
-
592
771
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
593
772
  # We extend `local_database?` so that common VM IPs can be used.
594
773
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
@@ -653,7 +832,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
653
832
  end
654
833
 
655
834
  require "models/topic"
835
+ require "models/customer"
836
+ require "models/non_primary_key"
656
837
  class FinderTest < ActiveRecord::TestCase
838
+ fixtures :customers, :topics, :authors
839
+
657
840
  # We have implicit ordering, via FETCH.
658
841
  coerce_tests! %r{doesn't have implicit ordering},
659
842
  :test_find_doesnt_have_implicit_ordering
@@ -698,6 +881,87 @@ class FinderTest < ActiveRecord::TestCase
698
881
  end
699
882
  end
700
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
701
965
  end
702
966
 
703
967
  module ActiveRecord
@@ -929,7 +1193,14 @@ class RelationTest < ActiveRecord::TestCase
929
1193
  coerce_tests! :test_reorder_with_first
930
1194
  def test_reorder_with_first_coerced
931
1195
  sql_log = capture_sql do
932
- 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
933
1204
  end
934
1205
  assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
935
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}"
@@ -982,6 +1253,31 @@ class RelationTest < ActiveRecord::TestCase
982
1253
  end
983
1254
  end
984
1255
 
1256
+ module ActiveRecord
1257
+ class RelationTest < ActiveRecord::TestCase
1258
+ # Skipping this test. SQL Server doesn't support optimizer hint as comments
1259
+ coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
1260
+
1261
+ coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1262
+ def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1263
+ escaped_table = Post.connection.quote_table_name("posts")
1264
+ expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1265
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1266
+ assert_equal expected, query
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
1278
+ end
1279
+ end
1280
+
985
1281
  require "models/post"
986
1282
  class SanitizeTest < ActiveRecord::TestCase
987
1283
  # Use nvarchar string (N'') in assert
@@ -1233,8 +1529,43 @@ end
1233
1529
  module ActiveRecord
1234
1530
  module ConnectionAdapters
1235
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
+
1236
1548
  private
1237
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
+
1238
1569
  # We need to give the full path for this to work.
1239
1570
  def schema_dump_path
1240
1571
  File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
@@ -1243,23 +1574,26 @@ module ActiveRecord
1243
1574
  end
1244
1575
  end
1245
1576
 
1577
+ require "models/post"
1578
+ require "models/comment"
1246
1579
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1580
+ fixtures :posts
1581
+
1247
1582
  # Use LEN() vs length() function.
1248
1583
  coerce_tests! %r{order: always allows Arel}
1249
1584
  test "order: always allows Arel" do
1250
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1251
- 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)
1252
1586
 
1253
- assert_equal ids_depr, ids_disabled
1587
+ assert_not_empty titles
1254
1588
  end
1255
1589
 
1256
1590
  # Use LEN() vs length() function.
1257
1591
  coerce_tests! %r{pluck: always allows Arel}
1258
1592
  test "pluck: always allows Arel" do
1259
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1260
- 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)"))
1261
1595
 
1262
- assert_equal values_depr, values_disabled
1596
+ assert_equal excepted_values, values
1263
1597
  end
1264
1598
 
1265
1599
  # Use LEN() vs length() function.
@@ -1267,11 +1601,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1267
1601
  test "order: allows valid Array arguments" do
1268
1602
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1269
1603
 
1270
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1271
- 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)
1605
+
1606
+ assert_equal ids_expected, ids
1607
+ end
1608
+
1609
+ test "order: allows string column names that are quoted" do
1610
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1611
+
1612
+ ids = Post.order("[id]").pluck(:id)
1272
1613
 
1273
- assert_equal ids_expected, ids_depr
1274
- assert_equal ids_expected, ids_disabled
1614
+ assert_equal ids_expected, ids
1615
+ end
1616
+
1617
+ test "order: allows string column names that are quoted with table" do
1618
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1619
+
1620
+ ids = Post.order("[posts].[id]").pluck(:id)
1621
+
1622
+ assert_equal ids_expected, ids
1623
+ end
1624
+
1625
+ test "order: allows string column names that are quoted with table and user" do
1626
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1627
+
1628
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1629
+
1630
+ assert_equal ids_expected, ids
1631
+ end
1632
+
1633
+ test "order: allows string column names that are quoted with table, user and database" do
1634
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1635
+
1636
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1637
+
1638
+ assert_equal ids_expected, ids
1639
+ end
1640
+
1641
+ test "pluck: allows string column name that are quoted" do
1642
+ titles_expected = Post.pluck(Arel.sql("title"))
1643
+
1644
+ titles = Post.pluck("[title]")
1645
+
1646
+ assert_equal titles_expected, titles
1647
+ end
1648
+
1649
+ test "pluck: allows string column name that are quoted with table" do
1650
+ titles_expected = Post.pluck(Arel.sql("title"))
1651
+
1652
+ titles = Post.pluck("[posts].[title]")
1653
+
1654
+ assert_equal titles_expected, titles
1655
+ end
1656
+
1657
+ test "pluck: allows string column name that are quoted with table and user" do
1658
+ titles_expected = Post.pluck(Arel.sql("title"))
1659
+
1660
+ titles = Post.pluck("[dbo].[posts].[title]")
1661
+
1662
+ assert_equal titles_expected, titles
1663
+ end
1664
+
1665
+ test "pluck: allows string column name that are quoted with table, user and database" do
1666
+ titles_expected = Post.pluck(Arel.sql("title"))
1667
+
1668
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1669
+
1670
+ assert_equal titles_expected, titles
1275
1671
  end
1276
1672
  end
1277
1673
 
@@ -1313,6 +1709,25 @@ class RelationMergingTest < ActiveRecord::TestCase
1313
1709
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1314
1710
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1315
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
1316
1731
  end
1317
1732
 
1318
1733
  module ActiveRecord
@@ -1400,6 +1815,8 @@ end
1400
1815
 
1401
1816
  require "models/citation"
1402
1817
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1818
+ fixtures :citations
1819
+
1403
1820
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1404
1821
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1405
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/).
@@ -1407,14 +1824,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1407
1824
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1408
1825
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1409
1826
  # adapter is 2,098.
1410
- coerce_tests! :test_eager_loading_too_may_ids
1411
- 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
1412
1829
  # Remove excess records.
1413
1830
  Citation.limit(32768).order(id: :desc).delete_all
1414
1831
 
1415
1832
  # Perform test
1416
1833
  citation_count = Citation.count
1417
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1834
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1418
1835
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1419
1836
  end
1420
1837
  end
@@ -1437,22 +1854,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1437
1854
  end
1438
1855
  end
1439
1856
 
1440
- module ActiveRecord
1441
- module ConnectionAdapters
1442
- class ReaperTest < ActiveRecord::TestCase
1443
- # Coerce can be removed if Rails version > 6.0.3
1444
- coerce_tests! :test_connection_pool_starts_reaper_in_fork unless Process.respond_to?(:fork)
1857
+ class ReloadModelsTest < ActiveRecord::TestCase
1858
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
1859
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1860
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1861
+ end
1862
+
1863
+ require "models/post"
1864
+ class AnnotateTest < ActiveRecord::TestCase
1865
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1866
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1867
+ coerce_tests! :test_annotate_wraps_content_in_an_inline_comment
1868
+ def test_annotate_wraps_content_in_an_inline_comment_coerced
1869
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1870
+
1871
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1872
+ posts = Post.select(:id).annotate("foo")
1873
+ assert posts.first
1874
+ end
1875
+ end
1876
+
1877
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1878
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1879
+ coerce_tests! :test_annotate_is_sanitized
1880
+ def test_annotate_is_sanitized_coerced
1881
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1882
+
1883
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1884
+ posts = Post.select(:id).annotate("*/foo/*")
1885
+ assert posts.first
1886
+ end
1887
+
1888
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1889
+ posts = Post.select(:id).annotate("**//foo//**")
1890
+ assert posts.first
1891
+ end
1892
+
1893
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
1894
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
1895
+ assert posts.first
1896
+ end
1897
+
1898
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
1899
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
1900
+ assert posts.first
1445
1901
  end
1446
1902
  end
1447
1903
  end
1448
1904
 
1449
- class FixturesTest < ActiveRecord::TestCase
1450
- # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1451
- coerce_tests! :test_binary_in_fixtures if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
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
1452
1914
  end
1453
1915
 
1454
- class ReloadModelsTest < ActiveRecord::TestCase
1455
- # Skip test on Windows. The number of arguements passed to `IO.popen` in
1456
- # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1457
- coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1916
+ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1917
+ # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
1918
+ # "A column has been specified more than once in the order by list"
1919
+ # Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
1920
+ 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
1921
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
1922
+ # preload table schemas
1923
+ Author.joins(:category_post_comments).first
1924
+
1925
+ assert_includes_and_joins_equal(
1926
+ Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
1927
+ [authors(:david), authors(:mary)], :category_post_comments
1928
+ )
1929
+ end
1930
+
1931
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
1932
+ # preload table schemas
1933
+ Category.joins(:post_comments).first
1934
+
1935
+ assert_includes_and_joins_equal(
1936
+ Category.where("comments.id" => comments(:more_greetings).id).order(:id),
1937
+ [categories(:general), categories(:technology)], :post_comments
1938
+ )
1939
+ end
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/
1458
1978
  end