activerecord-sqlserver-adapter 6.0.1 → 6.1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/CHANGELOG.md +29 -46
  4. data/README.md +32 -3
  5. data/RUNNING_UNIT_TESTS.md +1 -1
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
  13. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +28 -16
  15. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +8 -7
  16. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  17. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  18. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +31 -9
  19. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  20. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  21. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  22. data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
  23. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
  24. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  25. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -69
  27. data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
  28. data/lib/active_record/sqlserver_base.rb +9 -15
  29. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  30. data/lib/arel/visitors/sqlserver.rb +125 -40
  31. data/test/cases/adapter_test_sqlserver.rb +50 -16
  32. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  33. data/test/cases/coerced_tests.rb +611 -78
  34. data/test/cases/column_test_sqlserver.rb +9 -2
  35. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  36. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  37. data/test/cases/fetch_test_sqlserver.rb +18 -0
  38. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  39. data/test/cases/lateral_test_sqlserver.rb +35 -0
  40. data/test/cases/migration_test_sqlserver.rb +51 -0
  41. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  42. data/test/cases/order_test_sqlserver.rb +7 -0
  43. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  44. data/test/cases/rake_test_sqlserver.rb +38 -2
  45. data/test/cases/schema_dumper_test_sqlserver.rb +14 -3
  46. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  47. data/test/models/sqlserver/composite_pk.rb +9 -0
  48. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  49. data/test/schema/sqlserver_specific_schema.rb +25 -0
  50. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  51. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  52. data/test/support/sql_counter_sqlserver.rb +14 -12
  53. metadata +29 -9
  54. data/.travis.yml +0 -23
  55. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
@@ -6,6 +6,7 @@ require "models/task"
6
6
  require "models/post"
7
7
  require "models/subscriber"
8
8
  require "models/minimalistic"
9
+ require "models/college"
9
10
 
10
11
  class AdapterTestSQLServer < ActiveRecord::TestCase
11
12
  fixtures :tasks
@@ -42,17 +43,49 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
42
43
  assert connection.supports_ddl_transactions?
43
44
  end
44
45
 
45
- it "allow owner table name prefixs like dbo to still allow table exists to return true" do
46
+ it "table exists works if table name prefixed by schema and owner" do
46
47
  begin
47
48
  assert_equal "topics", Topic.table_name
48
49
  assert Topic.table_exists?
50
+
51
+ # Test when owner included in table name.
49
52
  Topic.table_name = "dbo.topics"
50
- assert Topic.table_exists?, "Tasks table name of dbo.topics should return true for exists."
53
+ assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
54
+
55
+ # Test when database and owner included in table name.
56
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
57
+ Topic.table_name = "#{db_config.database}.dbo.topics"
58
+ assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
51
59
  ensure
52
60
  Topic.table_name = "topics"
53
61
  end
54
62
  end
55
63
 
64
+ it "test table existence across database schemas" do
65
+ arunit_connection = Topic.connection
66
+ arunit2_connection = College.connection
67
+
68
+ arunit_database = arunit_connection.pool.db_config.database
69
+ arunit2_database = arunit2_connection.pool.db_config.database
70
+
71
+ # Assert that connections use different default databases schemas.
72
+ assert_not_equal arunit_database, arunit2_database
73
+
74
+ # Assert that the Topics table exists when using the Topics connection.
75
+ assert arunit_connection.table_exists?('topics'), 'Topics table exists using table name'
76
+ assert arunit_connection.table_exists?('dbo.topics'), 'Topics table exists using owner and table name'
77
+ assert arunit_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using database, owner and table name'
78
+
79
+ # Assert that the Colleges table exists when using the Colleges connection.
80
+ assert arunit2_connection.table_exists?('colleges'), 'College table exists using table name'
81
+ assert arunit2_connection.table_exists?('dbo.colleges'), 'College table exists using owner and table name'
82
+ assert arunit2_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'College table exists using database, owner and table name'
83
+
84
+ # Assert that the tables exist when using each others connection.
85
+ assert arunit_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'Colleges table exists using Topics connection'
86
+ assert arunit2_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using Colleges connection'
87
+ end
88
+
56
89
  it "return true to insert sql query for inserts only" do
57
90
  assert connection.send(:insert_sql?, "INSERT...")
58
91
  assert connection.send(:insert_sql?, "EXEC sp_executesql N'INSERT INTO [fk_test_has_fks] ([fk_id]) VALUES (@0); SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident', N'@0 int', @0 = 0")
@@ -68,21 +101,23 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
68
101
 
69
102
  it "test bad connection" do
70
103
  assert_raise ActiveRecord::NoDatabaseError do
71
- config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
72
- 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
73
107
  end
74
108
  end
75
109
 
76
110
  it "test database exists returns false if database does not exist" do
77
- config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
78
- 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),
79
114
  "expected database to not exist"
80
115
  end
81
116
 
82
117
  it "test database exists returns true when the database exists" do
83
- config = ActiveRecord::Base.configurations["arunit"]
84
- assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
85
- "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"
86
121
  end
87
122
 
88
123
  describe "with different language" do
@@ -342,7 +377,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
342
377
  assert !SSTestCustomersView.columns.blank?
343
378
  assert_equal columns.size, SSTestCustomersView.columns.size
344
379
  columns.each do |colname|
345
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
380
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
346
381
  SSTestCustomersView.columns_hash[colname],
347
382
  "Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
348
383
  end
@@ -369,7 +404,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
369
404
  assert !SSTestStringDefaultsView.columns.blank?
370
405
  assert_equal columns.size, SSTestStringDefaultsView.columns.size
371
406
  columns.each do |colname|
372
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
407
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
373
408
  SSTestStringDefaultsView.columns_hash[colname],
374
409
  "Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
375
410
  end
@@ -434,12 +469,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
434
469
  describe "block writes to a database" do
435
470
  def setup
436
471
  @conn = ActiveRecord::Base.connection
437
- @connection_handler = ActiveRecord::Base.connection_handler
438
472
  end
439
473
 
440
474
  def test_errors_when_an_insert_query_is_called_while_preventing_writes
441
475
  assert_raises(ActiveRecord::ReadOnlyError) do
442
- @connection_handler.while_preventing_writes do
476
+ ActiveRecord::Base.while_preventing_writes do
443
477
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
444
478
  end
445
479
  end
@@ -449,7 +483,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
449
483
  @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
450
484
 
451
485
  assert_raises(ActiveRecord::ReadOnlyError) do
452
- @connection_handler.while_preventing_writes do
486
+ ActiveRecord::Base.while_preventing_writes do
453
487
  @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
454
488
  end
455
489
  end
@@ -459,7 +493,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
459
493
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
460
494
 
461
495
  assert_raises(ActiveRecord::ReadOnlyError) do
462
- @connection_handler.while_preventing_writes do
496
+ ActiveRecord::Base.while_preventing_writes do
463
497
  @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
464
498
  end
465
499
  end
@@ -468,7 +502,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
468
502
  def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
469
503
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
470
504
 
471
- @connection_handler.while_preventing_writes do
505
+ ActiveRecord::Base.while_preventing_writes do
472
506
  assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
473
507
  end
474
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
57
81
 
82
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
83
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
84
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
85
+ Subscriber.send(:load_schema!)
86
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
87
+ end
88
+
89
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
90
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
91
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
92
+ Subscriber.send(:load_schema!)
93
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
94
+ end
95
+ end
96
+ end
97
+
98
+ module ActiveRecord
99
+ class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
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
@@ -396,39 +613,6 @@ module ActiveRecord
396
613
  end
397
614
 
398
615
  class MigrationTest < ActiveRecord::TestCase
399
- # We do not have do the DecimalWithoutScale type.
400
- coerce_tests! :test_add_table_with_decimals
401
- def test_add_table_with_decimals_coerced
402
- Person.connection.drop_table :big_numbers rescue nil
403
- assert !BigNumber.table_exists?
404
- GiveMeBigNumbers.up
405
- BigNumber.reset_column_information
406
- assert BigNumber.create(
407
- :bank_balance => 1586.43,
408
- :big_bank_balance => BigDecimal("1000234000567.95"),
409
- :world_population => 6000000000,
410
- :my_house_population => 3,
411
- :value_of_e => BigDecimal("2.7182818284590452353602875")
412
- )
413
- b = BigNumber.first
414
- assert_not_nil b
415
- assert_not_nil b.bank_balance
416
- assert_not_nil b.big_bank_balance
417
- assert_not_nil b.world_population
418
- assert_not_nil b.my_house_population
419
- assert_not_nil b.value_of_e
420
- assert_kind_of BigDecimal, b.world_population
421
- assert_equal "6000000000.0", b.world_population.to_s
422
- assert_kind_of Integer, b.my_house_population
423
- assert_equal 3, b.my_house_population
424
- assert_kind_of BigDecimal, b.bank_balance
425
- assert_equal BigDecimal("1586.43"), b.bank_balance
426
- assert_kind_of BigDecimal, b.big_bank_balance
427
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
428
- GiveMeBigNumbers.down
429
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
430
- end
431
-
432
616
  # For some reason our tests set Rails.@_env which breaks test env switching.
433
617
  coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
434
618
  coerce_tests! :test_internal_metadata_stores_environment
@@ -584,11 +768,6 @@ module ActiveRecord
584
768
  end
585
769
  end
586
770
 
587
- class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
588
- # Skip this test with /tmp/my_schema_cache.yml path on Windows.
589
- coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
590
- end
591
-
592
771
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
593
772
  # We extend `local_database?` so that common VM IPs can be used.
594
773
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
@@ -653,7 +832,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
653
832
  end
654
833
 
655
834
  require "models/topic"
835
+ require "models/customer"
836
+ require "models/non_primary_key"
656
837
  class FinderTest < ActiveRecord::TestCase
838
+ fixtures :customers, :topics, :authors
839
+
657
840
  # We have implicit ordering, via FETCH.
658
841
  coerce_tests! %r{doesn't have implicit ordering},
659
842
  :test_find_doesnt_have_implicit_ordering
@@ -698,6 +881,87 @@ class FinderTest < ActiveRecord::TestCase
698
881
  end
699
882
  end
700
883
  end
884
+
885
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
886
+ coerce_tests! :test_include_on_unloaded_relation_with_match
887
+ def test_include_on_unloaded_relation_with_match_coerced
888
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
889
+ assert_equal true, Customer.where(name: "David").include?(customers(:david))
890
+ end
891
+ end
892
+
893
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
894
+ coerce_tests! :test_include_on_unloaded_relation_without_match
895
+ def test_include_on_unloaded_relation_without_match_coerced
896
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
897
+ assert_equal false, Customer.where(name: "David").include?(customers(:mary))
898
+ end
899
+ end
900
+
901
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
902
+ coerce_tests! :test_member_on_unloaded_relation_with_match
903
+ def test_member_on_unloaded_relation_with_match_coerced
904
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
905
+ assert_equal true, Customer.where(name: "David").member?(customers(:david))
906
+ end
907
+ end
908
+
909
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
910
+ coerce_tests! :test_member_on_unloaded_relation_without_match
911
+ def test_member_on_unloaded_relation_without_match_coerced
912
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
913
+ assert_equal false, Customer.where(name: "David").member?(customers(:mary))
914
+ end
915
+ end
916
+
917
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
918
+ coerce_tests! :test_implicit_order_column_is_configurable
919
+ def test_implicit_order_column_is_configurable_coerced
920
+ old_implicit_order_column = Topic.implicit_order_column
921
+ Topic.implicit_order_column = "title"
922
+
923
+ assert_equal topics(:fifth), Topic.first
924
+ assert_equal topics(:third), Topic.last
925
+
926
+ c = Topic.connection
927
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
928
+ Topic.last
929
+ }
930
+ ensure
931
+ Topic.implicit_order_column = old_implicit_order_column
932
+ end
933
+
934
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
935
+ coerce_tests! :test_implicit_order_set_to_primary_key
936
+ def test_implicit_order_set_to_primary_key_coerced
937
+ old_implicit_order_column = Topic.implicit_order_column
938
+ Topic.implicit_order_column = "id"
939
+
940
+ c = Topic.connection
941
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
942
+ Topic.last
943
+ }
944
+ ensure
945
+ Topic.implicit_order_column = old_implicit_order_column
946
+ end
947
+
948
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
949
+ coerce_tests! :test_implicit_order_for_model_without_primary_key
950
+ def test_implicit_order_for_model_without_primary_key_coerced
951
+ old_implicit_order_column = NonPrimaryKey.implicit_order_column
952
+ NonPrimaryKey.implicit_order_column = "created_at"
953
+
954
+ c = NonPrimaryKey.connection
955
+
956
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
957
+ NonPrimaryKey.last
958
+ }
959
+ ensure
960
+ NonPrimaryKey.implicit_order_column = old_implicit_order_column
961
+ end
962
+
963
+ # SQL Server is unable to use aliased SELECT in the HAVING clause.
964
+ coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
701
965
  end
702
966
 
703
967
  module ActiveRecord
@@ -929,7 +1193,14 @@ class RelationTest < ActiveRecord::TestCase
929
1193
  coerce_tests! :test_reorder_with_first
930
1194
  def test_reorder_with_first_coerced
931
1195
  sql_log = capture_sql do
932
- assert Post.order(:title).reorder(nil).first
1196
+ message = <<~MSG.squish
1197
+ `.reorder(nil)` with `.first` / `.first!` no longer
1198
+ takes non-deterministic result in Rails 6.2.
1199
+ To continue taking non-deterministic result, use `.take` / `.take!` instead.
1200
+ MSG
1201
+ assert_deprecated(message) do
1202
+ assert Post.order(:title).reorder(nil).first
1203
+ end
933
1204
  end
934
1205
  assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
935
1206
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
@@ -982,6 +1253,31 @@ class RelationTest < ActiveRecord::TestCase
982
1253
  end
983
1254
  end
984
1255
 
1256
+ module ActiveRecord
1257
+ class RelationTest < ActiveRecord::TestCase
1258
+ # Skipping this test. SQL Server doesn't support optimizer hint as comments
1259
+ coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
1260
+
1261
+ coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1262
+ def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1263
+ escaped_table = Post.connection.quote_table_name("posts")
1264
+ expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1265
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1266
+ assert_equal expected, query
1267
+ end
1268
+
1269
+ # Original Rails test fails on Windows CI because the dump file was not being binary read.
1270
+ coerce_tests! :test_marshal_load_legacy_relation
1271
+ def test_marshal_load_legacy_relation_coerced
1272
+ path = File.expand_path(
1273
+ "support/marshal_compatibility_fixtures/legacy_relation.dump",
1274
+ ARTest::SQLServer.root_activerecord_test
1275
+ )
1276
+ assert_equal 11, Marshal.load(File.binread(path)).size
1277
+ end
1278
+ end
1279
+ end
1280
+
985
1281
  require "models/post"
986
1282
  class SanitizeTest < ActiveRecord::TestCase
987
1283
  # Use nvarchar string (N'') in assert
@@ -1225,6 +1521,7 @@ module ActiveRecord
1225
1521
 
1226
1522
  original_test_statement_cache_values_differ
1227
1523
  ensure
1524
+ Book.where(author_id: nil, name: 'my book').delete_all
1228
1525
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1229
1526
  end
1230
1527
  end
@@ -1233,8 +1530,43 @@ end
1233
1530
  module ActiveRecord
1234
1531
  module ConnectionAdapters
1235
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
+
1236
1549
  private
1237
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
+
1238
1570
  # We need to give the full path for this to work.
1239
1571
  def schema_dump_path
1240
1572
  File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
@@ -1243,23 +1575,26 @@ module ActiveRecord
1243
1575
  end
1244
1576
  end
1245
1577
 
1578
+ require "models/post"
1579
+ require "models/comment"
1246
1580
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1581
+ fixtures :posts
1582
+
1247
1583
  # Use LEN() vs length() function.
1248
1584
  coerce_tests! %r{order: always allows Arel}
1249
1585
  test "order: always allows Arel" do
1250
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1251
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
1586
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1252
1587
 
1253
- assert_equal ids_depr, ids_disabled
1588
+ assert_not_empty titles
1254
1589
  end
1255
1590
 
1256
1591
  # Use LEN() vs length() function.
1257
1592
  coerce_tests! %r{pluck: always allows Arel}
1258
1593
  test "pluck: always allows Arel" do
1259
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1260
- values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1594
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1595
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1261
1596
 
1262
- assert_equal values_depr, values_disabled
1597
+ assert_equal excepted_values, values
1263
1598
  end
1264
1599
 
1265
1600
  # Use LEN() vs length() function.
@@ -1267,11 +1602,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1267
1602
  test "order: allows valid Array arguments" do
1268
1603
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1269
1604
 
1270
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1271
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "len(title)"]).pluck(:id) }
1605
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1606
+
1607
+ assert_equal ids_expected, ids
1608
+ end
1609
+
1610
+ test "order: allows string column names that are quoted" do
1611
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1612
+
1613
+ ids = Post.order("[id]").pluck(:id)
1614
+
1615
+ assert_equal ids_expected, ids
1616
+ end
1617
+
1618
+ test "order: allows string column names that are quoted with table" do
1619
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1620
+
1621
+ ids = Post.order("[posts].[id]").pluck(:id)
1622
+
1623
+ assert_equal ids_expected, ids
1624
+ end
1625
+
1626
+ test "order: allows string column names that are quoted with table and user" do
1627
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1628
+
1629
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1272
1630
 
1273
- assert_equal ids_expected, ids_depr
1274
- assert_equal ids_expected, ids_disabled
1631
+ assert_equal ids_expected, ids
1632
+ end
1633
+
1634
+ test "order: allows string column names that are quoted with table, user and database" do
1635
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1636
+
1637
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1638
+
1639
+ assert_equal ids_expected, ids
1640
+ end
1641
+
1642
+ test "pluck: allows string column name that are quoted" do
1643
+ titles_expected = Post.pluck(Arel.sql("title"))
1644
+
1645
+ titles = Post.pluck("[title]")
1646
+
1647
+ assert_equal titles_expected, titles
1648
+ end
1649
+
1650
+ test "pluck: allows string column name that are quoted with table" do
1651
+ titles_expected = Post.pluck(Arel.sql("title"))
1652
+
1653
+ titles = Post.pluck("[posts].[title]")
1654
+
1655
+ assert_equal titles_expected, titles
1656
+ end
1657
+
1658
+ test "pluck: allows string column name that are quoted with table and user" do
1659
+ titles_expected = Post.pluck(Arel.sql("title"))
1660
+
1661
+ titles = Post.pluck("[dbo].[posts].[title]")
1662
+
1663
+ assert_equal titles_expected, titles
1664
+ end
1665
+
1666
+ test "pluck: allows string column name that are quoted with table, user and database" do
1667
+ titles_expected = Post.pluck(Arel.sql("title"))
1668
+
1669
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1670
+
1671
+ assert_equal titles_expected, titles
1275
1672
  end
1276
1673
  end
1277
1674
 
@@ -1313,6 +1710,25 @@ class RelationMergingTest < ActiveRecord::TestCase
1313
1710
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1314
1711
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1315
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
1316
1732
  end
1317
1733
 
1318
1734
  module ActiveRecord
@@ -1333,6 +1749,7 @@ class EnumTest < ActiveRecord::TestCase
1333
1749
 
1334
1750
  send(:'original_enums are distinct per class')
1335
1751
  ensure
1752
+ Book.where(author_id: nil, name: nil).delete_all
1336
1753
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1337
1754
  end
1338
1755
 
@@ -1343,6 +1760,7 @@ class EnumTest < ActiveRecord::TestCase
1343
1760
 
1344
1761
  send(:'original_creating new objects with enum scopes')
1345
1762
  ensure
1763
+ Book.where(author_id: nil, name: nil).delete_all
1346
1764
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1347
1765
  end
1348
1766
 
@@ -1353,6 +1771,7 @@ class EnumTest < ActiveRecord::TestCase
1353
1771
 
1354
1772
  send(:'original_enums are inheritable')
1355
1773
  ensure
1774
+ Book.where(author_id: nil, name: nil).delete_all
1356
1775
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1357
1776
  end
1358
1777
 
@@ -1363,6 +1782,7 @@ class EnumTest < ActiveRecord::TestCase
1363
1782
 
1364
1783
  send(:'original_declare multiple enums at a time')
1365
1784
  ensure
1785
+ Book.where(author_id: nil, name: nil).delete_all
1366
1786
  Book.connection.add_index(:books, [:author_id, :name], unique: true)
1367
1787
  end
1368
1788
  end
@@ -1400,6 +1820,8 @@ end
1400
1820
 
1401
1821
  require "models/citation"
1402
1822
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1823
+ fixtures :citations
1824
+
1403
1825
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1404
1826
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1405
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/).
@@ -1407,14 +1829,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1407
1829
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1408
1830
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1409
1831
  # adapter is 2,098.
1410
- coerce_tests! :test_eager_loading_too_may_ids
1411
- 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
1412
1834
  # Remove excess records.
1413
1835
  Citation.limit(32768).order(id: :desc).delete_all
1414
1836
 
1415
1837
  # Perform test
1416
1838
  citation_count = Citation.count
1417
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1839
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1418
1840
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1419
1841
  end
1420
1842
  end
@@ -1426,6 +1848,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
1426
1848
  def test_vebose_query_logs_coerced
1427
1849
  original_test_vebose_query_logs
1428
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
1429
1859
  end
1430
1860
 
1431
1861
  class ActiveRecordSchemaTest < ActiveRecord::TestCase
@@ -1437,22 +1867,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1437
1867
  end
1438
1868
  end
1439
1869
 
1440
- module ActiveRecord
1441
- module ConnectionAdapters
1442
- class ReaperTest < ActiveRecord::TestCase
1443
- # Coerce can be removed if Rails version > 6.0.3
1444
- coerce_tests! :test_connection_pool_starts_reaper_in_fork unless Process.respond_to?(:fork)
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
1445
1914
  end
1446
1915
  end
1447
1916
  end
1448
1917
 
1449
- class FixturesTest < ActiveRecord::TestCase
1450
- # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1451
- coerce_tests! :test_binary_in_fixtures if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
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
1452
1927
  end
1453
1928
 
1454
- class ReloadModelsTest < ActiveRecord::TestCase
1455
- # Skip test on Windows. The number of arguements passed to `IO.popen` in
1456
- # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1457
- coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
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/
1458
1991
  end