activerecord-sqlserver-adapter 6.0.2 → 6.1.2.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/CHANGELOG.md +33 -56
  3. data/README.md +28 -11
  4. data/VERSION +1 -1
  5. data/activerecord-sqlserver-adapter.gemspec +1 -1
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  12. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
  13. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -3
  14. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  15. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  16. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +8 -6
  17. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  18. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  19. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  20. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  22. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -70
  23. data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
  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 +74 -29
  27. data/test/cases/adapter_test_sqlserver.rb +27 -17
  28. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  29. data/test/cases/coerced_tests.rb +544 -77
  30. data/test/cases/column_test_sqlserver.rb +4 -0
  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/fetch_test_sqlserver.rb +18 -0
  34. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  35. data/test/cases/migration_test_sqlserver.rb +7 -0
  36. data/test/cases/order_test_sqlserver.rb +7 -0
  37. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  38. data/test/cases/rake_test_sqlserver.rb +38 -2
  39. data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
  40. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  41. data/test/models/sqlserver/composite_pk.rb +9 -0
  42. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  43. data/test/schema/sqlserver_specific_schema.rb +25 -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 +23 -8
  48. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
@@ -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,31 @@ 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"
121
+ end
122
+
123
+ it "test primary key violation" do
124
+ Post.create!(id: 0, title: 'Setup', body: 'Create post with primary key of zero')
125
+
126
+ assert_raise ActiveRecord::RecordNotUnique do
127
+ Post.create!(id: 0, title: 'Test', body: 'Try to create another post with primary key of zero')
128
+ end
118
129
  end
119
130
 
120
131
  describe "with different language" do
@@ -374,7 +385,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
374
385
  assert !SSTestCustomersView.columns.blank?
375
386
  assert_equal columns.size, SSTestCustomersView.columns.size
376
387
  columns.each do |colname|
377
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
388
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
378
389
  SSTestCustomersView.columns_hash[colname],
379
390
  "Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
380
391
  end
@@ -401,7 +412,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
401
412
  assert !SSTestStringDefaultsView.columns.blank?
402
413
  assert_equal columns.size, SSTestStringDefaultsView.columns.size
403
414
  columns.each do |colname|
404
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
415
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
405
416
  SSTestStringDefaultsView.columns_hash[colname],
406
417
  "Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
407
418
  end
@@ -466,12 +477,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
466
477
  describe "block writes to a database" do
467
478
  def setup
468
479
  @conn = ActiveRecord::Base.connection
469
- @connection_handler = ActiveRecord::Base.connection_handler
470
480
  end
471
481
 
472
482
  def test_errors_when_an_insert_query_is_called_while_preventing_writes
473
483
  assert_raises(ActiveRecord::ReadOnlyError) do
474
- @connection_handler.while_preventing_writes do
484
+ ActiveRecord::Base.while_preventing_writes do
475
485
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
476
486
  end
477
487
  end
@@ -481,7 +491,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
481
491
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
482
492
 
483
493
  assert_raises(ActiveRecord::ReadOnlyError) do
484
- @connection_handler.while_preventing_writes do
494
+ ActiveRecord::Base.while_preventing_writes do
485
495
  @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
486
496
  end
487
497
  end
@@ -491,7 +501,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
491
501
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
492
502
 
493
503
  assert_raises(ActiveRecord::ReadOnlyError) do
494
- @connection_handler.while_preventing_writes do
504
+ ActiveRecord::Base.while_preventing_writes do
495
505
  @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
496
506
  end
497
507
  end
@@ -500,7 +510,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
500
510
  def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
501
511
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
502
512
 
503
- @connection_handler.while_preventing_writes do
513
+ ActiveRecord::Base.while_preventing_writes do
504
514
  assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
505
515
  end
506
516
  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
 
@@ -1207,6 +1521,7 @@ module ActiveRecord
1207
1521
 
1208
1522
  original_test_statement_cache_values_differ
1209
1523
  ensure
1524
+ Book.where(author_id: nil, name: 'my book').delete_all
1210
1525
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1211
1526
  end
1212
1527
  end
@@ -1215,8 +1530,43 @@ end
1215
1530
  module ActiveRecord
1216
1531
  module ConnectionAdapters
1217
1532
  class SchemaCacheTest < ActiveRecord::TestCase
1533
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1534
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1535
+
1536
+ # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1537
+ coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1538
+
1539
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1540
+ unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1541
+ def test_marshal_dump_and_load_with_gzip_coerced
1542
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1543
+ end
1544
+ def test_marshal_dump_and_load_via_disk_coerced
1545
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1546
+ end
1547
+ end
1548
+
1218
1549
  private
1219
1550
 
1551
+ def with_marshable_time_defaults
1552
+ # Detect problems
1553
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1554
+ column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1555
+ current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1556
+ end
1557
+
1558
+ # Correct problems
1559
+ if current_default.present?
1560
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1561
+ end
1562
+
1563
+ # Run original test
1564
+ yield
1565
+ ensure
1566
+ # Revert changes
1567
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
1568
+ end
1569
+
1220
1570
  # We need to give the full path for this to work.
1221
1571
  def schema_dump_path
1222
1572
  File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
@@ -1233,19 +1583,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1233
1583
  # Use LEN() vs length() function.
1234
1584
  coerce_tests! %r{order: always allows Arel}
1235
1585
  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) }
1586
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1238
1587
 
1239
- assert_equal ids_depr, ids_disabled
1588
+ assert_not_empty titles
1240
1589
  end
1241
1590
 
1242
1591
  # Use LEN() vs length() function.
1243
1592
  coerce_tests! %r{pluck: always allows Arel}
1244
1593
  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)")) }
1594
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1595
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1247
1596
 
1248
- assert_equal values_depr, values_disabled
1597
+ assert_equal excepted_values, values
1249
1598
  end
1250
1599
 
1251
1600
  # Use LEN() vs length() function.
@@ -1253,91 +1602,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1253
1602
  test "order: allows valid Array arguments" do
1254
1603
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1255
1604
 
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) }
1605
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1258
1606
 
1259
- assert_equal ids_expected, ids_depr
1260
- assert_equal ids_expected, ids_disabled
1607
+ assert_equal ids_expected, ids
1261
1608
  end
1262
1609
 
1263
1610
  test "order: allows string column names that are quoted" do
1264
1611
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1265
1612
 
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) }
1613
+ ids = Post.order("[id]").pluck(:id)
1268
1614
 
1269
- assert_equal ids_expected, ids_depr
1270
- assert_equal ids_expected, ids_disabled
1615
+ assert_equal ids_expected, ids
1271
1616
  end
1272
1617
 
1273
1618
  test "order: allows string column names that are quoted with table" do
1274
1619
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1275
1620
 
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) }
1621
+ ids = Post.order("[posts].[id]").pluck(:id)
1278
1622
 
1279
- assert_equal ids_expected, ids_depr
1280
- assert_equal ids_expected, ids_disabled
1623
+ assert_equal ids_expected, ids
1281
1624
  end
1282
1625
 
1283
1626
  test "order: allows string column names that are quoted with table and user" do
1284
1627
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1285
1628
 
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) }
1629
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1288
1630
 
1289
- assert_equal ids_expected, ids_depr
1290
- assert_equal ids_expected, ids_disabled
1631
+ assert_equal ids_expected, ids
1291
1632
  end
1292
1633
 
1293
1634
  test "order: allows string column names that are quoted with table, user and database" do
1294
1635
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1295
1636
 
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) }
1637
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1298
1638
 
1299
- assert_equal ids_expected, ids_depr
1300
- assert_equal ids_expected, ids_disabled
1639
+ assert_equal ids_expected, ids
1301
1640
  end
1302
1641
 
1303
1642
  test "pluck: allows string column name that are quoted" do
1304
1643
  titles_expected = Post.pluck(Arel.sql("title"))
1305
1644
 
1306
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[title]") }
1307
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[title]") }
1645
+ titles = Post.pluck("[title]")
1308
1646
 
1309
- assert_equal titles_expected, titles_depr
1310
- assert_equal titles_expected, titles_disabled
1647
+ assert_equal titles_expected, titles
1311
1648
  end
1312
1649
 
1313
1650
  test "pluck: allows string column name that are quoted with table" do
1314
1651
  titles_expected = Post.pluck(Arel.sql("title"))
1315
1652
 
1316
- titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[posts].[title]") }
1317
- titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[posts].[title]") }
1653
+ titles = Post.pluck("[posts].[title]")
1318
1654
 
1319
- assert_equal titles_expected, titles_depr
1320
- assert_equal titles_expected, titles_disabled
1655
+ assert_equal titles_expected, titles
1321
1656
  end
1322
1657
 
1323
1658
  test "pluck: allows string column name that are quoted with table and user" do
1324
1659
  titles_expected = Post.pluck(Arel.sql("title"))
1325
1660
 
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]") }
1661
+ titles = Post.pluck("[dbo].[posts].[title]")
1328
1662
 
1329
- assert_equal titles_expected, titles_depr
1330
- assert_equal titles_expected, titles_disabled
1663
+ assert_equal titles_expected, titles
1331
1664
  end
1332
1665
 
1333
1666
  test "pluck: allows string column name that are quoted with table, user and database" do
1334
1667
  titles_expected = Post.pluck(Arel.sql("title"))
1335
1668
 
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]") }
1669
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1338
1670
 
1339
- assert_equal titles_expected, titles_depr
1340
- assert_equal titles_expected, titles_disabled
1671
+ assert_equal titles_expected, titles
1341
1672
  end
1342
1673
  end
1343
1674
 
@@ -1379,6 +1710,25 @@ class RelationMergingTest < ActiveRecord::TestCase
1379
1710
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1380
1711
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1381
1712
  end
1713
+
1714
+ # Same as original but change first regexp to match sp_executesql binding syntax
1715
+ coerce_tests! :test_merge_doesnt_duplicate_same_clauses
1716
+ def test_merge_doesnt_duplicate_same_clauses_coerced
1717
+ david, mary, bob = authors(:david, :mary, :bob)
1718
+
1719
+ non_mary_and_bob = Author.where.not(id: [mary, bob])
1720
+
1721
+ author_id = Author.connection.quote_table_name("authors.id")
1722
+ assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
1723
+ assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
1724
+ end
1725
+
1726
+ only_david = Author.where("#{author_id} IN (?)", david)
1727
+
1728
+ assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
1729
+ assert_equal [david], only_david.merge(only_david)
1730
+ end
1731
+ end
1382
1732
  end
1383
1733
 
1384
1734
  module ActiveRecord
@@ -1399,6 +1749,7 @@ class EnumTest < ActiveRecord::TestCase
1399
1749
 
1400
1750
  send(:'original_enums are distinct per class')
1401
1751
  ensure
1752
+ Book.where(author_id: nil, name: nil).delete_all
1402
1753
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1403
1754
  end
1404
1755
 
@@ -1409,6 +1760,7 @@ class EnumTest < ActiveRecord::TestCase
1409
1760
 
1410
1761
  send(:'original_creating new objects with enum scopes')
1411
1762
  ensure
1763
+ Book.where(author_id: nil, name: nil).delete_all
1412
1764
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1413
1765
  end
1414
1766
 
@@ -1419,6 +1771,7 @@ class EnumTest < ActiveRecord::TestCase
1419
1771
 
1420
1772
  send(:'original_enums are inheritable')
1421
1773
  ensure
1774
+ Book.where(author_id: nil, name: nil).delete_all
1422
1775
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1423
1776
  end
1424
1777
 
@@ -1429,6 +1782,7 @@ class EnumTest < ActiveRecord::TestCase
1429
1782
 
1430
1783
  send(:'original_declare multiple enums at a time')
1431
1784
  ensure
1785
+ Book.where(author_id: nil, name: nil).delete_all
1432
1786
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1433
1787
  end
1434
1788
  end
@@ -1466,6 +1820,8 @@ end
1466
1820
 
1467
1821
  require "models/citation"
1468
1822
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1823
+ fixtures :citations
1824
+
1469
1825
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1470
1826
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1471
1827
  # (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 +1829,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1473
1829
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1474
1830
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1475
1831
  # adapter is 2,098.
1476
- coerce_tests! :test_eager_loading_too_may_ids
1477
- def test_eager_loading_too_may_ids_coerced
1832
+ coerce_tests! :test_eager_loading_too_many_ids
1833
+ def test_eager_loading_too_many_ids_coerced
1478
1834
  # Remove excess records.
1479
1835
  Citation.limit(32768).order(id: :desc).delete_all
1480
1836
 
1481
1837
  # Perform test
1482
1838
  citation_count = Citation.count
1483
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1839
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1484
1840
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1485
1841
  end
1486
1842
  end
@@ -1492,6 +1848,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
1492
1848
  def test_vebose_query_logs_coerced
1493
1849
  original_test_vebose_query_logs
1494
1850
  end
1851
+
1852
+ # Bindings logged slightly differently.
1853
+ coerce_tests! :test_where_in_binds_logging_include_attribute_names
1854
+ def test_where_in_binds_logging_include_attribute_names_coerced
1855
+ Developer.where(id: [1, 2, 3, 4, 5]).load
1856
+ wait
1857
+ assert_match(%{@0 = 1, @1 = 2, @2 = 3, @3 = 4, @4 = 5 [["id", nil], ["id", nil], ["id", nil], ["id", nil], ["id", nil]]}, @logger.logged(:debug).last)
1858
+ end
1495
1859
  end
1496
1860
 
1497
1861
  class ActiveRecordSchemaTest < ActiveRecord::TestCase
@@ -1503,22 +1867,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1503
1867
  end
1504
1868
  end
1505
1869
 
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)
1870
+ class ReloadModelsTest < ActiveRecord::TestCase
1871
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
1872
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1873
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1874
+ end
1875
+
1876
+ require "models/post"
1877
+ class AnnotateTest < ActiveRecord::TestCase
1878
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1879
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1880
+ coerce_tests! :test_annotate_wraps_content_in_an_inline_comment
1881
+ def test_annotate_wraps_content_in_an_inline_comment_coerced
1882
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1883
+
1884
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1885
+ posts = Post.select(:id).annotate("foo")
1886
+ assert posts.first
1887
+ end
1888
+ end
1889
+
1890
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1891
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1892
+ coerce_tests! :test_annotate_is_sanitized
1893
+ def test_annotate_is_sanitized_coerced
1894
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1895
+
1896
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1897
+ posts = Post.select(:id).annotate("*/foo/*")
1898
+ assert posts.first
1899
+ end
1900
+
1901
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1902
+ posts = Post.select(:id).annotate("**//foo//**")
1903
+ assert posts.first
1904
+ end
1905
+
1906
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
1907
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
1908
+ assert posts.first
1909
+ end
1910
+
1911
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
1912
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
1913
+ assert posts.first
1511
1914
  end
1512
1915
  end
1513
1916
  end
1514
1917
 
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/
1918
+ class MarshalSerializationTest < ActiveRecord::TestCase
1919
+ private
1920
+
1921
+ def marshal_fixture_path(file_name)
1922
+ File.expand_path(
1923
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
1924
+ ARTest::SQLServer.test_root_sqlserver
1925
+ )
1926
+ end
1518
1927
  end
1519
1928
 
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/
1929
+ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1930
+ # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
1931
+ # "A column has been specified more than once in the order by list"
1932
+ # Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
1933
+ 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
1934
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
1935
+ # preload table schemas
1936
+ Author.joins(:category_post_comments).first
1937
+
1938
+ assert_includes_and_joins_equal(
1939
+ Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
1940
+ [authors(:david), authors(:mary)], :category_post_comments
1941
+ )
1942
+ end
1943
+
1944
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
1945
+ # preload table schemas
1946
+ Category.joins(:post_comments).first
1947
+
1948
+ assert_includes_and_joins_equal(
1949
+ Category.where("comments.id" => comments(:more_greetings).id).order(:id),
1950
+ [categories(:general), categories(:technology)], :post_comments
1951
+ )
1952
+ end
1953
+ end
1954
+
1955
+ class BasePreventWritesTest < 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.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
+
1968
+ class BasePreventWritesLegacyTest < ActiveRecord::TestCase
1969
+ # SQL Server does not have query for release_savepoint
1970
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
1971
+ test "an empty transaction does not raise if preventing writes coerced" do
1972
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
1973
+ assert_queries(1, ignore_none: true) do
1974
+ Bird.transaction do
1975
+ ActiveRecord::Base.connection.materialize_transactions
1976
+ end
1977
+ end
1978
+ end
1979
+ end
1980
+ end
1981
+ end
1982
+
1983
+ class MigratorTest < ActiveRecord::TestCase
1984
+ # Test fails on Windows AppVeyor CI for unknown reason.
1985
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1986
+ end
1987
+
1988
+ class MultiDbMigratorTest < ActiveRecord::TestCase
1989
+ # Test fails on Windows AppVeyor CI for unknown reason.
1990
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1524
1991
  end