activerecord-sqlserver-adapter 6.0.2 → 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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -71
  3. data/README.md +13 -2
  4. data/VERSION +1 -1
  5. data/activerecord-sqlserver-adapter.gemspec +1 -1
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -9
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -2
  8. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  9. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -3
  11. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  12. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  13. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +7 -5
  14. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
  15. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  16. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  17. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  18. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +83 -66
  19. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
  20. data/lib/active_record/sqlserver_base.rb +9 -15
  21. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  22. data/lib/arel/visitors/sqlserver.rb +60 -28
  23. data/test/cases/adapter_test_sqlserver.rb +17 -15
  24. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  25. data/test/cases/coerced_tests.rb +531 -77
  26. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  27. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  28. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  29. data/test/cases/migration_test_sqlserver.rb +7 -0
  30. data/test/cases/order_test_sqlserver.rb +7 -0
  31. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  32. data/test/cases/rake_test_sqlserver.rb +3 -2
  33. data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
  34. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  35. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  36. data/test/schema/sqlserver_specific_schema.rb +7 -0
  37. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  38. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  39. data/test/support/sql_counter_sqlserver.rb +14 -12
  40. metadata +22 -8
@@ -53,7 +53,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
53
53
  assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
54
54
 
55
55
  # Test when database and owner included in table name.
56
- Topic.table_name = "#{ActiveRecord::Base.configurations["arunit"]['database']}.dbo.topics"
56
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
57
+ Topic.table_name = "#{db_config.database}.dbo.topics"
57
58
  assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
58
59
  ensure
59
60
  Topic.table_name = "topics"
@@ -64,8 +65,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
64
65
  arunit_connection = Topic.connection
65
66
  arunit2_connection = College.connection
66
67
 
67
- arunit_database = arunit_connection.pool.spec.config[:database]
68
- arunit2_database = arunit2_connection.pool.spec.config[:database]
68
+ arunit_database = arunit_connection.pool.db_config.database
69
+ arunit2_database = arunit2_connection.pool.db_config.database
69
70
 
70
71
  # Assert that connections use different default databases schemas.
71
72
  assert_not_equal arunit_database, arunit2_database
@@ -100,21 +101,23 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
100
101
 
101
102
  it "test bad connection" do
102
103
  assert_raise ActiveRecord::NoDatabaseError do
103
- config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
104
- ActiveRecord::Base.sqlserver_connection config
104
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
105
+ configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
106
+ ActiveRecord::Base.sqlserver_connection configuration
105
107
  end
106
108
  end
107
109
 
108
110
  it "test database exists returns false if database does not exist" do
109
- config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
110
- assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
111
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
112
+ configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
113
+ assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
111
114
  "expected database to not exist"
112
115
  end
113
116
 
114
117
  it "test database exists returns true when the database exists" do
115
- config = ActiveRecord::Base.configurations["arunit"]
116
- assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
117
- "expected database #{config[:database]} to exist"
118
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
119
+ assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
120
+ "expected database #{db_config.database} to exist"
118
121
  end
119
122
 
120
123
  describe "with different language" do
@@ -466,12 +469,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
466
469
  describe "block writes to a database" do
467
470
  def setup
468
471
  @conn = ActiveRecord::Base.connection
469
- @connection_handler = ActiveRecord::Base.connection_handler
470
472
  end
471
473
 
472
474
  def test_errors_when_an_insert_query_is_called_while_preventing_writes
473
475
  assert_raises(ActiveRecord::ReadOnlyError) do
474
- @connection_handler.while_preventing_writes do
476
+ ActiveRecord::Base.while_preventing_writes do
475
477
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
476
478
  end
477
479
  end
@@ -481,7 +483,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
481
483
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
482
484
 
483
485
  assert_raises(ActiveRecord::ReadOnlyError) do
484
- @connection_handler.while_preventing_writes do
486
+ ActiveRecord::Base.while_preventing_writes do
485
487
  @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
486
488
  end
487
489
  end
@@ -491,7 +493,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
491
493
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
492
494
 
493
495
  assert_raises(ActiveRecord::ReadOnlyError) do
494
- @connection_handler.while_preventing_writes do
496
+ ActiveRecord::Base.while_preventing_writes do
495
497
  @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
496
498
  end
497
499
  end
@@ -500,7 +502,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
500
502
  def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
501
503
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
502
504
 
503
- @connection_handler.while_preventing_writes do
505
+ ActiveRecord::Base.while_preventing_writes do
504
506
  assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
505
507
  end
506
508
  end
@@ -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
72
+
73
+ module ActiveRecord
74
+ class AdapterPreventWritesTest < ActiveRecord::TestCase
75
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
76
+ coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
77
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
78
+ Subscriber.send(:load_schema!)
79
+ original_test_errors_when_an_insert_query_is_called_while_preventing_writes
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
57
97
 
98
+ module ActiveRecord
99
+ class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
58
100
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
59
101
  coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
60
102
  def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
61
103
  Subscriber.send(:load_schema!)
62
104
  original_test_errors_when_an_insert_query_is_called_while_preventing_writes
63
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
 
@@ -1215,8 +1529,43 @@ end
1215
1529
  module ActiveRecord
1216
1530
  module ConnectionAdapters
1217
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
+
1218
1548
  private
1219
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
+
1220
1569
  # We need to give the full path for this to work.
1221
1570
  def schema_dump_path
1222
1571
  File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
@@ -1233,19 +1582,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1233
1582
  # Use LEN() vs length() function.
1234
1583
  coerce_tests! %r{order: always allows Arel}
1235
1584
  test "order: always allows Arel" do
1236
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1237
- 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)
1238
1586
 
1239
- assert_equal ids_depr, ids_disabled
1587
+ assert_not_empty titles
1240
1588
  end
1241
1589
 
1242
1590
  # Use LEN() vs length() function.
1243
1591
  coerce_tests! %r{pluck: always allows Arel}
1244
1592
  test "pluck: always allows Arel" do
1245
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1246
- 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)"))
1247
1595
 
1248
- assert_equal values_depr, values_disabled
1596
+ assert_equal excepted_values, values
1249
1597
  end
1250
1598
 
1251
1599
  # Use LEN() vs length() function.
@@ -1253,91 +1601,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1253
1601
  test "order: allows valid Array arguments" do
1254
1602
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1255
1603
 
1256
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1257
- 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)
1258
1605
 
1259
- assert_equal ids_expected, ids_depr
1260
- assert_equal ids_expected, ids_disabled
1606
+ assert_equal ids_expected, ids
1261
1607
  end
1262
1608
 
1263
1609
  test "order: allows string column names that are quoted" do
1264
1610
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1265
1611
 
1266
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[id]").pluck(:id) }
1267
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[id]").pluck(:id) }
1612
+ ids = Post.order("[id]").pluck(:id)
1268
1613
 
1269
- assert_equal ids_expected, ids_depr
1270
- assert_equal ids_expected, ids_disabled
1614
+ assert_equal ids_expected, ids
1271
1615
  end
1272
1616
 
1273
1617
  test "order: allows string column names that are quoted with table" do
1274
1618
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1275
1619
 
1276
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[posts].[id]").pluck(:id) }
1277
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[posts].[id]").pluck(:id) }
1620
+ ids = Post.order("[posts].[id]").pluck(:id)
1278
1621
 
1279
- assert_equal ids_expected, ids_depr
1280
- assert_equal ids_expected, ids_disabled
1622
+ assert_equal ids_expected, ids
1281
1623
  end
1282
1624
 
1283
1625
  test "order: allows string column names that are quoted with table and user" do
1284
1626
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1285
1627
 
1286
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[dbo].[posts].[id]").pluck(:id) }
1287
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[dbo].[posts].[id]").pluck(:id) }
1628
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1288
1629
 
1289
- assert_equal ids_expected, ids_depr
1290
- assert_equal ids_expected, ids_disabled
1630
+ assert_equal ids_expected, ids
1291
1631
  end
1292
1632
 
1293
1633
  test "order: allows string column names that are quoted with table, user and database" do
1294
1634
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1295
1635
 
1296
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
1297
- 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)
1298
1637
 
1299
- assert_equal ids_expected, ids_depr
1300
- assert_equal ids_expected, ids_disabled
1638
+ assert_equal ids_expected, ids
1301
1639
  end
1302
1640
 
1303
1641
  test "pluck: allows string column name that are quoted" do
1304
1642
  titles_expected = Post.pluck(Arel.sql("title"))
1305
1643
 
1306
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[title]") }
1307
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[title]") }
1644
+ titles = Post.pluck("[title]")
1308
1645
 
1309
- assert_equal titles_expected, titles_depr
1310
- assert_equal titles_expected, titles_disabled
1646
+ assert_equal titles_expected, titles
1311
1647
  end
1312
1648
 
1313
1649
  test "pluck: allows string column name that are quoted with table" do
1314
1650
  titles_expected = Post.pluck(Arel.sql("title"))
1315
1651
 
1316
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[posts].[title]") }
1317
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[posts].[title]") }
1652
+ titles = Post.pluck("[posts].[title]")
1318
1653
 
1319
- assert_equal titles_expected, titles_depr
1320
- assert_equal titles_expected, titles_disabled
1654
+ assert_equal titles_expected, titles
1321
1655
  end
1322
1656
 
1323
1657
  test "pluck: allows string column name that are quoted with table and user" do
1324
1658
  titles_expected = Post.pluck(Arel.sql("title"))
1325
1659
 
1326
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[dbo].[posts].[title]") }
1327
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[dbo].[posts].[title]") }
1660
+ titles = Post.pluck("[dbo].[posts].[title]")
1328
1661
 
1329
- assert_equal titles_expected, titles_depr
1330
- assert_equal titles_expected, titles_disabled
1662
+ assert_equal titles_expected, titles
1331
1663
  end
1332
1664
 
1333
1665
  test "pluck: allows string column name that are quoted with table, user and database" do
1334
1666
  titles_expected = Post.pluck(Arel.sql("title"))
1335
1667
 
1336
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
1337
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
1668
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1338
1669
 
1339
- assert_equal titles_expected, titles_depr
1340
- assert_equal titles_expected, titles_disabled
1670
+ assert_equal titles_expected, titles
1341
1671
  end
1342
1672
  end
1343
1673
 
@@ -1379,6 +1709,25 @@ class RelationMergingTest < ActiveRecord::TestCase
1379
1709
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1380
1710
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1381
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
1382
1731
  end
1383
1732
 
1384
1733
  module ActiveRecord
@@ -1466,6 +1815,8 @@ end
1466
1815
 
1467
1816
  require "models/citation"
1468
1817
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1818
+ fixtures :citations
1819
+
1469
1820
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1470
1821
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1471
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/).
@@ -1473,14 +1824,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1473
1824
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1474
1825
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1475
1826
  # adapter is 2,098.
1476
- coerce_tests! :test_eager_loading_too_may_ids
1477
- 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
1478
1829
  # Remove excess records.
1479
1830
  Citation.limit(32768).order(id: :desc).delete_all
1480
1831
 
1481
1832
  # Perform test
1482
1833
  citation_count = Citation.count
1483
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1834
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1484
1835
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1485
1836
  end
1486
1837
  end
@@ -1503,22 +1854,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1503
1854
  end
1504
1855
  end
1505
1856
 
1506
- module ActiveRecord
1507
- module ConnectionAdapters
1508
- class ReaperTest < ActiveRecord::TestCase
1509
- # Coerce can be removed if Rails version > 6.0.3
1510
- 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
1511
1901
  end
1512
1902
  end
1513
1903
  end
1514
1904
 
1515
- class FixturesTest < ActiveRecord::TestCase
1516
- # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1517
- 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
1518
1914
  end
1519
1915
 
1520
- class ReloadModelsTest < ActiveRecord::TestCase
1521
- # Skip test on Windows. The number of arguements passed to `IO.popen` in
1522
- # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1523
- 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/
1524
1978
  end