activerecord-sqlserver-adapter 8.0.9 → 8.1.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +1 -1
  3. data/.github/workflows/ci.yml +34 -3
  4. data/CHANGELOG.md +14 -62
  5. data/Dockerfile.ci +1 -1
  6. data/Gemfile +7 -9
  7. data/Guardfile +2 -2
  8. data/README.md +33 -13
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/activerecord-sqlserver-adapter.gemspec +15 -16
  12. data/compose.ci.yaml +8 -1
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -1
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +1 -2
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +1 -1
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +4 -4
  17. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +121 -90
  18. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +3 -4
  19. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
  20. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -12
  21. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +17 -8
  22. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +162 -156
  23. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  24. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +5 -5
  25. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -7
  26. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +3 -1
  27. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -3
  28. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -3
  29. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -4
  30. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +1 -1
  31. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +4 -6
  32. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +1 -1
  33. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +0 -2
  34. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -12
  35. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +118 -66
  36. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -9
  37. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -5
  38. data/lib/arel/visitors/sqlserver.rb +55 -26
  39. data/test/cases/active_schema_test_sqlserver.rb +45 -23
  40. data/test/cases/adapter_test_sqlserver.rb +72 -59
  41. data/test/cases/coerced_tests.rb +396 -343
  42. data/test/cases/column_test_sqlserver.rb +328 -316
  43. data/test/cases/connection_test_sqlserver.rb +15 -11
  44. data/test/cases/enum_test_sqlserver.rb +8 -9
  45. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  46. data/test/cases/fetch_test_sqlserver.rb +1 -1
  47. data/test/cases/helper_sqlserver.rb +7 -3
  48. data/test/cases/index_test_sqlserver.rb +8 -6
  49. data/test/cases/insert_all_test_sqlserver.rb +3 -28
  50. data/test/cases/json_test_sqlserver.rb +8 -8
  51. data/test/cases/lateral_test_sqlserver.rb +2 -2
  52. data/test/cases/migration_test_sqlserver.rb +12 -12
  53. data/test/cases/optimizer_hints_test_sqlserver.rb +1 -1
  54. data/test/cases/pessimistic_locking_test_sqlserver.rb +6 -6
  55. data/test/cases/primary_keys_test_sqlserver.rb +4 -4
  56. data/test/cases/rake_test_sqlserver.rb +15 -7
  57. data/test/cases/schema_dumper_test_sqlserver.rb +109 -113
  58. data/test/cases/schema_test_sqlserver.rb +7 -7
  59. data/test/cases/showplan_test_sqlserver.rb +2 -2
  60. data/test/cases/specific_schema_test_sqlserver.rb +6 -6
  61. data/test/cases/transaction_test_sqlserver.rb +6 -8
  62. data/test/cases/trigger_test_sqlserver.rb +1 -1
  63. data/test/cases/utils_test_sqlserver.rb +3 -3
  64. data/test/cases/view_test_sqlserver.rb +12 -8
  65. data/test/cases/virtual_column_test_sqlserver.rb +113 -0
  66. data/test/migrations/create_clients_and_change_column_collation.rb +2 -2
  67. data/test/models/sqlserver/edge_schema.rb +2 -2
  68. data/test/schema/sqlserver_specific_schema.rb +49 -37
  69. data/test/support/coerceable_test_sqlserver.rb +10 -10
  70. data/test/support/connection_reflection.rb +0 -5
  71. data/test/support/core_ext/backtrace_cleaner.rb +36 -0
  72. data/test/support/query_assertions.rb +25 -3
  73. data/test/support/rake_helpers.rb +6 -10
  74. metadata +12 -107
@@ -2,7 +2,22 @@
2
2
 
3
3
  require "cases/helper_sqlserver"
4
4
 
5
+ require "models/author"
6
+ require "models/book"
7
+ require "models/car"
8
+ require "models/citation"
9
+ require "models/comment"
10
+ require "models/computer"
11
+ require "models/customer"
12
+ require "models/dashboard"
13
+ require "models/developer"
5
14
  require "models/event"
15
+ require "models/non_primary_key"
16
+ require "models/post"
17
+ require "models/tag"
18
+ require "models/task"
19
+ require "models/topic"
20
+
6
21
  class UniquenessValidationTest < ActiveRecord::TestCase
7
22
  # So sp_executesql swallows this exception. Run without prepared to see it.
8
23
  coerce_tests! :test_validate_uniqueness_with_limit
@@ -62,7 +77,6 @@ class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
62
77
  end
63
78
  end
64
79
 
65
- require "models/event"
66
80
  module ActiveRecord
67
81
  class AdapterTest < ActiveRecord::TestCase
68
82
  # Legacy binds are not supported.
@@ -174,21 +188,20 @@ module ActiveRecord
174
188
  end
175
189
  end
176
190
 
177
- require "models/topic"
178
191
  class AttributeMethodsTest < ActiveRecord::TestCase
179
192
  # Use IFF for boolean statement in SELECT
180
193
  coerce_tests! %r{typecast attribute from select to false}
181
194
  def test_typecast_attribute_from_select_to_false_coerced
182
- Topic.create(:title => "Budget")
183
- topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
195
+ Topic.create(title: "Budget")
196
+ topic = Topic.all.merge!(select: "topics.*, IIF (1 = 2, 1, 0) as is_test").first
184
197
  assert_not_predicate topic, :is_test?
185
198
  end
186
199
 
187
200
  # Use IFF for boolean statement in SELECT
188
201
  coerce_tests! %r{typecast attribute from select to true}
189
202
  def test_typecast_attribute_from_select_to_true_coerced
190
- Topic.create(:title => "Budget")
191
- topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
203
+ Topic.create(title: "Budget")
204
+ topic = Topic.all.merge!(select: "topics.*, IIF (1 = 1, 1, 0) as is_test").first
192
205
  assert_predicate topic, :is_test?
193
206
  end
194
207
  end
@@ -248,7 +261,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
248
261
  def test_belongs_to_coerced
249
262
  client = Client.find(3)
250
263
  first_firm = companies(:first_firm)
251
- assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
264
+ assert_queries_and_values_match(/FETCH NEXT @3 ROWS ONLY/, ["Firm", "Agency", 1, 1]) do
252
265
  assert_equal first_firm, client.firm
253
266
  assert_equal first_firm.name, client.firm.name
254
267
  end
@@ -257,21 +270,6 @@ end
257
270
 
258
271
  module ActiveRecord
259
272
  class BindParameterTest < ActiveRecord::TestCase
260
- # Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
261
- coerce_tests! :test_binds_are_logged
262
- def test_binds_are_logged_coerced
263
- sub = Arel::Nodes::BindParam.new(1)
264
- binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
265
- sql = "select * from topics where id = #{sub.to_sql}"
266
-
267
- @connection.exec_query(sql, "SQL", binds)
268
-
269
- logged_sql = "EXEC sp_executesql N'#{sql}', N'#{sub.to_sql} int', #{sub.to_sql} = 1"
270
- message = @subscriber.calls.find { |args| args[4][:sql] == logged_sql }
271
-
272
- assert_equal binds, message[4][:binds]
273
- end
274
-
275
273
  # SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
276
274
  coerce_tests! :test_statement_cache
277
275
  coerce_tests! :test_statement_cache_with_query_cache
@@ -279,55 +277,6 @@ module ActiveRecord
279
277
  coerce_tests! :test_statement_cache_with_find_by
280
278
  coerce_tests! :test_statement_cache_with_in_clause
281
279
  coerce_tests! :test_statement_cache_with_sql_string_literal
282
-
283
- # Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
284
- coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
285
- def test_bind_params_to_sql_with_prepared_statements_coerced
286
- assert_bind_params_to_sql_coerced(prepared: true)
287
- end
288
-
289
- def test_bind_params_to_sql_with_unprepared_statements_coerced
290
- @connection.unprepared_statement do
291
- assert_bind_params_to_sql_coerced(prepared: false)
292
- end
293
- end
294
-
295
- private
296
-
297
- def assert_bind_params_to_sql_coerced(prepared:)
298
- table = Author.quoted_table_name
299
- pk = "#{table}.#{Author.quoted_primary_key}"
300
-
301
- # prepared_statements: true
302
- #
303
- # 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
304
- #
305
- # prepared_statements: false
306
- #
307
- # SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
308
- #
309
- sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
310
- 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"
311
-
312
- authors = Author.where(id: [1, 2, 3, nil])
313
- assert_equal sql_unprepared, @connection.to_sql(authors.arel)
314
- assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
315
-
316
- # prepared_statements: true
317
- #
318
- # 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
319
- #
320
- # prepared_statements: false
321
- #
322
- # SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
323
- #
324
- sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
325
- 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"
326
-
327
- authors = Author.where(id: [1, 2, 3, 9223372036854775808])
328
- assert_equal sql_unprepared, @connection.to_sql(authors.arel)
329
- assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
330
- end
331
280
  end
332
281
  end
333
282
 
@@ -347,7 +296,7 @@ module ActiveRecord
347
296
 
348
297
  original_test_payload_row_count_on_select_all
349
298
  ensure
350
- Book.where(author_id: nil, name: 'row count book 1').delete_all
299
+ Book.where(author_id: nil, name: "row count book 1").delete_all
351
300
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
352
301
  end
353
302
 
@@ -358,7 +307,7 @@ module ActiveRecord
358
307
 
359
308
  original_test_payload_row_count_on_pluck
360
309
  ensure
361
- Book.where(author_id: nil, name: 'row count book 2').delete_all
310
+ Book.where(author_id: nil, name: "row count book 2").delete_all
362
311
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
363
312
  end
364
313
 
@@ -369,9 +318,16 @@ module ActiveRecord
369
318
 
370
319
  original_test_payload_row_count_on_raw_sql
371
320
  ensure
372
- Book.where(author_id: nil, name: 'row count book 3').delete_all
321
+ Book.where(author_id: nil, name: "row count book 3").delete_all
373
322
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
374
323
  end
324
+
325
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
326
+ coerce_tests! :test_payload_affected_rows
327
+ def test_payload_affected_rows_coerced
328
+ Book.create!(name: "TEMP RECORD TO RUN SCHEMA QUERIES").destroy!
329
+ original_test_payload_affected_rows
330
+ end
375
331
  end
376
332
  end
377
333
 
@@ -391,7 +347,7 @@ class CalculationsTest < ActiveRecord::TestCase
391
347
  assert_predicate accounts, :loaded?
392
348
  assert_equal expected, accounts.count(:id)
393
349
  end
394
-
350
+
395
351
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
396
352
  coerce_tests! :test_offset_is_kept
397
353
  def test_offset_is_kept_coerced
@@ -441,11 +397,11 @@ class CalculationsTest < ActiveRecord::TestCase
441
397
  rails_core = companies(:rails_core)
442
398
 
443
399
  account = Account
444
- .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
445
- .where(firm: rails_core)
446
- .group(:firm_id)
447
- .order(:firm_id)
448
- .take!
400
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
401
+ .where(firm: rails_core)
402
+ .group(:firm_id)
403
+ .order(:firm_id)
404
+ .take!
449
405
 
450
406
  # id was not selected, so it should be nil
451
407
  # (cannot select id because it wasn't used in the GROUP BY clause)
@@ -485,7 +441,6 @@ class CalculationsTest < ActiveRecord::TestCase
485
441
  assert_equal(52.5, firm.avg_credit_limit)
486
442
  end
487
443
 
488
-
489
444
  # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
490
445
  # SELECT columns must be in the GROUP clause.
491
446
  coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
@@ -493,11 +448,11 @@ class CalculationsTest < ActiveRecord::TestCase
493
448
  rails_core = companies(:rails_core)
494
449
 
495
450
  firm = DependentFirm
496
- .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
497
- .where(id: rails_core)
498
- .joins(:account)
499
- .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
500
- .take!
451
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
452
+ .where(id: rails_core)
453
+ .joins(:account)
454
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
455
+ .take!
501
456
 
502
457
  # all the DependentFirm attributes should be present
503
458
  assert_equal rails_core, firm
@@ -512,7 +467,7 @@ class CalculationsTest < ActiveRecord::TestCase
512
467
  def test_limit_is_kept_coerced
513
468
  queries = capture_sql { Account.limit(1).count }
514
469
  assert_equal 1, queries.length
515
- assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
470
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, queries.first)
516
471
  end
517
472
 
518
473
  # Match SQL Server limit implementation
@@ -520,7 +475,7 @@ class CalculationsTest < ActiveRecord::TestCase
520
475
  def test_limit_with_offset_is_kept_coerced
521
476
  queries = capture_sql { Account.limit(1).offset(1).count }
522
477
  assert_equal 1, queries.length
523
- assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
478
+ assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/, queries.first)
524
479
  end
525
480
 
526
481
  # SQL Server needs an alias for the calculated column
@@ -563,8 +518,8 @@ module ActiveRecord
563
518
  five = columns.detect { |c| c.name == "five" }
564
519
 
565
520
  assert_equal "hello", one.default
566
- assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
567
- assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
521
+ assert_equal true, two.fetch_cast_type(connection).deserialize(two.default)
522
+ assert_equal false, three.fetch_cast_type(connection).deserialize(three.default)
568
523
  assert_equal 1, four.default
569
524
  assert_equal "hello", five.default
570
525
  end
@@ -633,7 +588,7 @@ module ActiveRecord
633
588
  # Our defaults are real 70000 integers vs '70000' strings.
634
589
  coerce_tests! :test_rename_column_preserves_default_value_not_null
635
590
  def test_rename_column_preserves_default_value_not_null_coerced
636
- add_column "test_models", "salary", :integer, :default => 70000
591
+ add_column "test_models", "salary", :integer, default: 70000
637
592
  default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
638
593
  assert_equal 70000, default_before
639
594
  rename_column "test_models", "salary", "annual_salary"
@@ -647,8 +602,8 @@ module ActiveRecord
647
602
  coerce_tests! :test_remove_column_with_multi_column_index
648
603
  def test_remove_column_with_multi_column_index_coerced
649
604
  add_column "test_models", :hat_size, :integer
650
- add_column "test_models", :hat_style, :string, :limit => 100
651
- add_index "test_models", ["hat_style", "hat_size"], :unique => true
605
+ add_column "test_models", :hat_style, :string, limit: 100
606
+ add_index "test_models", ["hat_style", "hat_size"], unique: true
652
607
  assert_equal 1, connection.indexes("test_models").size
653
608
  remove_column("test_models", "hat_size")
654
609
  assert_equal [], connection.indexes("test_models").map(&:name)
@@ -675,14 +630,20 @@ class MigrationTest < ActiveRecord::TestCase
675
630
  coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
676
631
  def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
677
632
  migration_a = Class.new(ActiveRecord::Migration::Current) {
678
- def version; 100 end
633
+ def version
634
+ 100
635
+ end
636
+
679
637
  def migrate(x)
680
638
  add_column "people", "last_name", :binary
681
639
  end
682
640
  }.new
683
641
 
684
642
  migration_b = Class.new(ActiveRecord::Migration::Current) {
685
- def version; 101 end
643
+ def version
644
+ 101
645
+ end
646
+
686
647
  def migrate(x)
687
648
  add_column "people", "last_name", :binary, if_not_exists: true
688
649
  end
@@ -711,7 +672,10 @@ module ActiveRecord
711
672
  long_table_name = "a" * (connection.table_name_length + 1)
712
673
  migration = Class.new(ActiveRecord::Migration[7.0]) {
713
674
  @@long_table_name = long_table_name
714
- def version; 100 end
675
+ def version
676
+ 100
677
+ end
678
+
715
679
  def migrate(x)
716
680
  create_table @@long_table_name
717
681
  end
@@ -722,7 +686,11 @@ module ActiveRecord
722
686
  end
723
687
  assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
724
688
  ensure
725
- connection.drop_table(long_table_name) rescue nil
689
+ begin
690
+ connection.drop_table(long_table_name)
691
+ rescue
692
+ nil
693
+ end
726
694
  end
727
695
 
728
696
  # SQL Server truncates long table names when renaming (https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-ver16).
@@ -733,7 +701,10 @@ module ActiveRecord
733
701
 
734
702
  migration = Class.new(ActiveRecord::Migration[7.0]) {
735
703
  @@long_table_name = long_table_name
736
- def version; 100 end
704
+ def version
705
+ 100
706
+ end
707
+
737
708
  def migrate(x)
738
709
  rename_table :more_testings, @@long_table_name
739
710
  end
@@ -744,14 +715,22 @@ module ActiveRecord
744
715
  assert_not connection.table_exists?(:more_testings)
745
716
  assert connection.table_exists?(long_table_name[0...-1])
746
717
  ensure
747
- connection.drop_table(:more_testings) rescue nil
748
- connection.drop_table(long_table_name[0...-1]) rescue nil
718
+ begin
719
+ connection.drop_table(:more_testings)
720
+ rescue
721
+ nil
722
+ end
723
+ begin
724
+ connection.drop_table(long_table_name[0...-1])
725
+ rescue
726
+ nil
727
+ end
749
728
  end
750
729
 
751
730
  # SQL Server has a different maximum index name length.
752
731
  coerce_tests! :test_add_index_errors_on_too_long_name_7_0
753
732
  def test_add_index_errors_on_too_long_name_7_0_coerced
754
- long_index_name = 'a' * (connection.index_name_length + 1)
733
+ long_index_name = "a" * (connection.index_name_length + 1)
755
734
 
756
735
  migration = Class.new(ActiveRecord::Migration[7.0]) {
757
736
  @@long_index_name = long_index_name
@@ -764,13 +743,13 @@ module ActiveRecord
764
743
  error = assert_raises(StandardError) do
765
744
  ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
766
745
  end
767
- assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
746
+ assert_match(/Index name '#{long_index_name}' on table 'testings' is too long/i, error.message)
768
747
  end
769
748
 
770
749
  # SQL Server has a different maximum index name length.
771
750
  coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
772
751
  def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
773
- long_index_name = 'a' * (connection.index_name_length + 1)
752
+ long_index_name = "a" * (connection.index_name_length + 1)
774
753
 
775
754
  migration = Class.new(ActiveRecord::Migration[7.0]) {
776
755
  @@long_index_name = long_index_name
@@ -787,9 +766,41 @@ module ActiveRecord
787
766
  error = assert_raises(StandardError) do
788
767
  ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
789
768
  end
790
- assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
769
+ assert_match(/Index name '#{long_index_name}' on table 'more_testings' is too long/i, error.message)
791
770
  ensure
792
- connection.drop_table :more_testings rescue nil
771
+ begin
772
+ connection.drop_table :more_testings
773
+ rescue
774
+ nil
775
+ end
776
+ end
777
+
778
+ # Foreign key count is the same as PostgreSQL/SQLite.
779
+ coerce_tests! :test_remove_foreign_key_on_8_0
780
+ def test_remove_foreign_key_on_8_0_coerced
781
+ connection.create_table(:sub_testings) do |t|
782
+ t.references :testing, foreign_key: true, type: :bigint
783
+ t.references :experiment, foreign_key: {to_table: :testings}, type: :bigint
784
+ end
785
+
786
+ migration = Class.new(ActiveRecord::Migration[8.0]) do
787
+ def up
788
+ change_table(:sub_testings) do |t|
789
+ t.remove_foreign_key :testings
790
+ t.remove_foreign_key :testings, column: :experiment_id
791
+ end
792
+ end
793
+ end
794
+
795
+ assert_raise(StandardError, match: /Table 'sub_testings' has no foreign key for testings$/) {
796
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
797
+ }
798
+
799
+ foreign_keys = @connection.foreign_keys("sub_testings")
800
+ assert_equal 2, foreign_keys.size
801
+ ensure
802
+ connection.drop_table(:sub_testings, if_exists: true)
803
+ ActiveRecord::Base.clear_cache!
793
804
  end
794
805
  end
795
806
  end
@@ -822,19 +833,26 @@ module ActiveRecord
822
833
  def setup
823
834
  @sqlserver_tasks =
824
835
  Class.new do
825
- def create; end
836
+ def create
837
+ end
826
838
 
827
- def drop; end
839
+ def drop
840
+ end
828
841
 
829
- def purge; end
842
+ def purge
843
+ end
830
844
 
831
- def charset; end
845
+ def charset
846
+ end
832
847
 
833
- def collation; end
848
+ def collation
849
+ end
834
850
 
835
- def structure_dump(*); end
851
+ def structure_dump(*)
852
+ end
836
853
 
837
- def structure_load(*); end
854
+ def structure_load(*)
855
+ end
838
856
  end.new
839
857
 
840
858
  $stdout, @original_stdout = StringIO.new, $stdout
@@ -855,7 +873,7 @@ module ActiveRecord
855
873
 
856
874
  def test_sqlserver_create
857
875
  with_stubbed_new do
858
- assert_called(eval("@sqlserver_tasks"), :create) do
876
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :create) do
859
877
  ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
860
878
  end
861
879
  end
@@ -868,7 +886,7 @@ module ActiveRecord
868
886
 
869
887
  def test_sqlserver_drop
870
888
  with_stubbed_new do
871
- assert_called(eval("@sqlserver_tasks"), :drop) do
889
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :drop) do
872
890
  ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
873
891
  end
874
892
  end
@@ -881,7 +899,7 @@ module ActiveRecord
881
899
 
882
900
  def test_sqlserver_purge
883
901
  with_stubbed_new do
884
- assert_called(eval("@sqlserver_tasks"), :purge) do
902
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :purge) do
885
903
  ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
886
904
  end
887
905
  end
@@ -894,7 +912,7 @@ module ActiveRecord
894
912
 
895
913
  def test_sqlserver_charset
896
914
  with_stubbed_new do
897
- assert_called(eval("@sqlserver_tasks"), :charset) do
915
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :charset) do
898
916
  ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
899
917
  end
900
918
  end
@@ -907,7 +925,7 @@ module ActiveRecord
907
925
 
908
926
  def test_sqlserver_collation
909
927
  with_stubbed_new do
910
- assert_called(eval("@sqlserver_tasks"), :collation) do
928
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :collation) do
911
929
  ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
912
930
  end
913
931
  end
@@ -921,10 +939,10 @@ module ActiveRecord
921
939
  def test_sqlserver_structure_dump
922
940
  with_stubbed_new do
923
941
  assert_called_with(
924
- eval("@sqlserver_tasks"), :structure_dump,
942
+ eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :structure_dump,
925
943
  ["awesome-file.sql", nil]
926
944
  ) do
927
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
945
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({"adapter" => :sqlserver}, "awesome-file.sql")
928
946
  end
929
947
  end
930
948
  end
@@ -937,11 +955,11 @@ module ActiveRecord
937
955
  def test_sqlserver_structure_load
938
956
  with_stubbed_new do
939
957
  assert_called_with(
940
- eval("@sqlserver_tasks"),
958
+ eval("@sqlserver_tasks", binding, __FILE__, __LINE__),
941
959
  :structure_load,
942
960
  ["awesome-file.sql", nil]
943
961
  ) do
944
- ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
962
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({"adapter" => :sqlserver}, "awesome-file.sql")
945
963
  end
946
964
  end
947
965
  end
@@ -974,22 +992,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
974
992
  coerce_tests! %r{including association based on sql condition and no database column}
975
993
  end
976
994
 
977
- require "models/topic"
978
- require "models/customer"
979
- require "models/non_primary_key"
980
995
  class FinderTest < ActiveRecord::TestCase
981
996
  fixtures :customers, :topics, :authors
982
997
 
983
998
  # We have implicit ordering, via FETCH.
984
999
  coerce_tests! %r{doesn't have implicit ordering},
985
- :test_find_doesnt_have_implicit_ordering
1000
+ :test_find_doesnt_have_implicit_ordering
986
1001
 
987
1002
  # Assert SQL Server limit implementation
988
1003
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
989
1004
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
990
- assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
991
- assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 2/) { Topic.first(2).entries }
992
- assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 5/) { Topic.last(5).entries }
1005
+ assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [3]) { Topic.take(3).entries }
1006
+ assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [2]) { Topic.first(2).entries }
1007
+ assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [5]) { Topic.last(5).entries }
993
1008
  end
994
1009
 
995
1010
  # This fails only when run in the full test suite task. Just taking it out of the mix.
@@ -1020,7 +1035,7 @@ class FinderTest < ActiveRecord::TestCase
1020
1035
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1021
1036
  coerce_tests! :test_include_on_unloaded_relation_with_match
1022
1037
  def test_include_on_unloaded_relation_with_match_coerced
1023
- assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1038
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
1024
1039
  assert_equal true, Customer.where(name: "David").include?(customers(:david))
1025
1040
  end
1026
1041
  end
@@ -1028,7 +1043,7 @@ class FinderTest < ActiveRecord::TestCase
1028
1043
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1029
1044
  coerce_tests! :test_include_on_unloaded_relation_without_match
1030
1045
  def test_include_on_unloaded_relation_without_match_coerced
1031
- assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1046
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
1032
1047
  assert_equal false, Customer.where(name: "David").include?(customers(:mary))
1033
1048
  end
1034
1049
  end
@@ -1036,7 +1051,7 @@ class FinderTest < ActiveRecord::TestCase
1036
1051
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1037
1052
  coerce_tests! :test_member_on_unloaded_relation_with_match
1038
1053
  def test_member_on_unloaded_relation_with_match_coerced
1039
- assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1054
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
1040
1055
  assert_equal true, Customer.where(name: "David").member?(customers(:david))
1041
1056
  end
1042
1057
  end
@@ -1044,14 +1059,14 @@ class FinderTest < ActiveRecord::TestCase
1044
1059
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1045
1060
  coerce_tests! :test_member_on_unloaded_relation_without_match
1046
1061
  def test_member_on_unloaded_relation_without_match_coerced
1047
- assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1062
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
1048
1063
  assert_equal false, Customer.where(name: "David").member?(customers(:mary))
1049
1064
  end
1050
1065
  end
1051
1066
 
1052
1067
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1053
- coerce_tests! :test_implicit_order_column_is_configurable
1054
- def test_implicit_order_column_is_configurable_coerced
1068
+ coerce_tests! :test_implicit_order_column_is_configurable_with_a_single_value
1069
+ def test_implicit_order_column_is_configurable_with_a_single_value_coerced
1055
1070
  old_implicit_order_column = Topic.implicit_order_column
1056
1071
  Topic.implicit_order_column = "title"
1057
1072
 
@@ -1059,7 +1074,33 @@ class FinderTest < ActiveRecord::TestCase
1059
1074
  assert_equal topics(:third), Topic.last
1060
1075
 
1061
1076
  c = Topic.lease_connection
1062
- assert_queries_match(/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) {
1077
+ assert_queries_and_values_match(/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/i, [1]) {
1078
+ Topic.last
1079
+ }
1080
+ ensure
1081
+ Topic.implicit_order_column = old_implicit_order_column
1082
+ end
1083
+
1084
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1085
+ coerce_tests! :test_implicit_order_column_is_configurable_with_multiple_values
1086
+ def test_implicit_order_column_is_configurable_with_multiple_values_coerced
1087
+ old_implicit_order_column = Topic.implicit_order_column
1088
+ Topic.implicit_order_column = ["title", "author_name"]
1089
+
1090
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(quote_table_name("topics.title"))} DESC, #{Regexp.escape(quote_table_name("topics.author_name"))} DESC, #{Regexp.escape(quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1091
+ Topic.last
1092
+ }
1093
+ ensure
1094
+ Topic.implicit_order_column = old_implicit_order_column
1095
+ end
1096
+
1097
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1098
+ coerce_tests! :test_ordering_does_not_append_primary_keys_or_query_constraints_if_passed_an_implicit_order_column_array_ending_in_nil
1099
+ def test_ordering_does_not_append_primary_keys_or_query_constraints_if_passed_an_implicit_order_column_array_ending_in_nil_coerced
1100
+ old_implicit_order_column = Topic.implicit_order_column
1101
+ Topic.implicit_order_column = ["author_name", nil]
1102
+
1103
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(quote_table_name("topics.author_name"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1063
1104
  Topic.last
1064
1105
  }
1065
1106
  ensure
@@ -1073,7 +1114,7 @@ class FinderTest < ActiveRecord::TestCase
1073
1114
  Topic.implicit_order_column = "id"
1074
1115
 
1075
1116
  c = Topic.lease_connection
1076
- assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
1117
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1077
1118
  Topic.last
1078
1119
  }
1079
1120
  ensure
@@ -1088,7 +1129,7 @@ class FinderTest < ActiveRecord::TestCase
1088
1129
 
1089
1130
  c = NonPrimaryKey.lease_connection
1090
1131
 
1091
- assert_queries_match(/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) {
1132
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1092
1133
  NonPrimaryKey.last
1093
1134
  }
1094
1135
  ensure
@@ -1098,7 +1139,7 @@ class FinderTest < ActiveRecord::TestCase
1098
1139
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1099
1140
  coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
1100
1141
  def test_member_on_unloaded_relation_with_composite_primary_key_coerced
1101
- assert_queries_match(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1142
+ assert_queries_match(/1 AS one.* FETCH NEXT @3 ROWS ONLY/) do
1102
1143
  book = cpk_books(:cpk_great_author_first_book)
1103
1144
  assert Cpk::Book.where(title: "The first book").member?(book)
1104
1145
  end
@@ -1113,7 +1154,7 @@ class FinderTest < ActiveRecord::TestCase
1113
1154
  quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1114
1155
  quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1115
1156
 
1116
- assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1157
+ assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1117
1158
  assert_kind_of ClothingItem, ClothingItem.first
1118
1159
  end
1119
1160
  ensure
@@ -1127,7 +1168,7 @@ class FinderTest < ActiveRecord::TestCase
1127
1168
  quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1128
1169
  quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1129
1170
 
1130
- assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1171
+ assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1131
1172
  assert_kind_of ClothingItem, ClothingItem.last
1132
1173
  end
1133
1174
  end
@@ -1139,7 +1180,7 @@ class FinderTest < ActiveRecord::TestCase
1139
1180
  quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1140
1181
  quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1141
1182
 
1142
- assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1183
+ assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1143
1184
  assert_kind_of ClothingItem, ClothingItem.first
1144
1185
  end
1145
1186
  end
@@ -1152,7 +1193,7 @@ class FinderTest < ActiveRecord::TestCase
1152
1193
  quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1153
1194
  quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1154
1195
 
1155
- assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1196
+ assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1156
1197
  assert_kind_of ClothingItem, ClothingItem.first
1157
1198
  end
1158
1199
  ensure
@@ -1162,7 +1203,7 @@ class FinderTest < ActiveRecord::TestCase
1162
1203
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1163
1204
  coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
1164
1205
  def test_include_on_unloaded_relation_with_composite_primary_key_coerced
1165
- assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1206
+ assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/) do
1166
1207
  book = cpk_books(:cpk_great_author_first_book)
1167
1208
  assert Cpk::Book.where(title: "The first book").include?(book)
1168
1209
  end
@@ -1172,11 +1213,11 @@ class FinderTest < ActiveRecord::TestCase
1172
1213
  coerce_tests! :test_nth_to_last_with_order_uses_limit
1173
1214
  def test_nth_to_last_with_order_uses_limit_coerced
1174
1215
  c = Topic.lease_connection
1175
- assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1216
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1176
1217
  Topic.second_to_last
1177
1218
  end
1178
1219
 
1179
- assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
1220
+ assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
1180
1221
  Topic.order(:updated_at).second_to_last
1181
1222
  end
1182
1223
  end
@@ -1227,7 +1268,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
1227
1268
  def test_has_one_coerced
1228
1269
  firm = companies(:first_firm)
1229
1270
  first_account = Account.find(1)
1230
- assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1271
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
1231
1272
  assert_equal first_account, firm.account
1232
1273
  assert_equal first_account.credit_limit, firm.account.credit_limit
1233
1274
  end
@@ -1239,7 +1280,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1239
1280
  coerce_tests! :test_has_one_through_executes_limited_query
1240
1281
  def test_has_one_through_executes_limited_query_coerced
1241
1282
  boring_club = clubs(:boring_club)
1242
- assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1283
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
1243
1284
  assert_equal boring_club, @member.general_club
1244
1285
  end
1245
1286
  end
@@ -1250,8 +1291,6 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1250
1291
  coerce_tests! :test_does_not_override_select
1251
1292
  end
1252
1293
 
1253
- require "models/developer"
1254
- require "models/computer"
1255
1294
  class NestedRelationScopingTest < ActiveRecord::TestCase
1256
1295
  # Assert SQL Server limit implementation
1257
1296
  coerce_tests! :test_merge_options
@@ -1267,7 +1306,6 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
1267
1306
  end
1268
1307
  end
1269
1308
 
1270
- require "models/topic"
1271
1309
  class PersistenceTest < ActiveRecord::TestCase
1272
1310
  # Rails test required updating a identity column.
1273
1311
  coerce_tests! :test_update_columns_changing_id
@@ -1294,20 +1332,26 @@ class PersistenceTest < ActiveRecord::TestCase
1294
1332
  coerce_tests! :test_model_with_no_auto_populated_fields_still_returns_primary_key_after_insert
1295
1333
  end
1296
1334
 
1297
- require "models/author"
1298
1335
  class UpdateAllTest < ActiveRecord::TestCase
1299
- # Rails test required updating a identity column.
1336
+ # Regular expression slightly different.
1300
1337
  coerce_tests! :test_update_all_doesnt_ignore_order
1301
1338
  def test_update_all_doesnt_ignore_order_coerced
1302
- david, mary = authors(:david), authors(:mary)
1303
- _(david.id).must_equal 1
1304
- _(mary.id).must_equal 2
1305
- _(david.name).wont_equal mary.name
1306
- assert_queries_match(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
1307
- Author.where("[id] > 1").order(:id).update_all(name: "Test")
1339
+ assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
1340
+ test_update_with_order_succeeds = lambda do |order|
1341
+ Author.order(order).update_all("id = id + 1")
1342
+ rescue ActiveRecord::ActiveRecordError
1343
+ false
1344
+ end
1345
+
1346
+ if test_update_with_order_succeeds.call("id DESC")
1347
+ # test that this wasn't a fluke and using an incorrect order results in an exception
1348
+ assert_not test_update_with_order_succeeds.call("id ASC")
1349
+ else
1350
+ # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
1351
+ assert_queries_match(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC.*\)/i) do
1352
+ test_update_with_order_succeeds.call("id DESC")
1353
+ end
1308
1354
  end
1309
- _(david.reload.name).must_equal "David"
1310
- _(mary.reload.name).must_equal "Test"
1311
1355
  end
1312
1356
 
1313
1357
  # SELECT columns must be in the GROUP clause.
@@ -1320,7 +1364,7 @@ class UpdateAllTest < ActiveRecord::TestCase
1320
1364
 
1321
1365
  assert_operator posts.length, :>, 0
1322
1366
  assert posts.all? { |post| post.comments.length >= minimum_comments_count }
1323
- assert posts.all? { |post| "ig" == post.title }
1367
+ assert posts.all? { |post| post.title == "ig" }
1324
1368
 
1325
1369
  post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
1326
1370
  assert_not_equal "ig", post.title
@@ -1345,7 +1389,6 @@ class DeleteAllTest < ActiveRecord::TestCase
1345
1389
  end
1346
1390
  end
1347
1391
 
1348
- require "models/topic"
1349
1392
  module ActiveRecord
1350
1393
  class PredicateBuilderTest < ActiveRecord::TestCase
1351
1394
  # Same as original test except string has `N` prefix to indicate unicode string.
@@ -1357,7 +1400,7 @@ module ActiveRecord
1357
1400
  # Same as original test except string has `N` prefix to indicate unicode string.
1358
1401
  coerce_tests! :test_registering_new_handlers_for_association
1359
1402
  def test_registering_new_handlers_for_association_coerced
1360
- assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
1403
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: {title: /rails/}).to_sql
1361
1404
  end
1362
1405
 
1363
1406
  # Same as original test except string has `N` prefix to indicate unicode string.
@@ -1376,7 +1419,6 @@ module ActiveRecord
1376
1419
  end
1377
1420
  end
1378
1421
 
1379
- require "models/task"
1380
1422
  class QueryCacheTest < ActiveRecord::TestCase
1381
1423
  # SQL Server adapter not in list of supported adapters in original test.
1382
1424
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
@@ -1387,7 +1429,6 @@ class QueryCacheTest < ActiveRecord::TestCase
1387
1429
  end
1388
1430
  end
1389
1431
 
1390
- require "models/post"
1391
1432
  class RelationTest < ActiveRecord::TestCase
1392
1433
  # Use LEN() instead of LENGTH() function.
1393
1434
  coerce_tests! :test_reverse_order_with_function
@@ -1415,7 +1456,7 @@ class RelationTest < ActiveRecord::TestCase
1415
1456
  assert Post.order(:title).reorder(nil).take
1416
1457
  end
1417
1458
  assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1418
- assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1459
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1419
1460
  end
1420
1461
 
1421
1462
  # We have implicit ordering, via FETCH.
@@ -1427,7 +1468,7 @@ class RelationTest < ActiveRecord::TestCase
1427
1468
  end
1428
1469
  assert_equal posts(:welcome), post
1429
1470
  assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1430
- assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1471
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1431
1472
  end
1432
1473
 
1433
1474
  # We are not doing order duplicate removal anymore.
@@ -1441,7 +1482,7 @@ class RelationTest < ActiveRecord::TestCase
1441
1482
  def test_multiple_where_and_having_clauses_coerced
1442
1483
  post = Post.first
1443
1484
  having_then_where = Post.having(id: post.id).where(title: post.title)
1444
- .having(id: post.id).where(title: post.title).group(:id).select(:id)
1485
+ .having(id: post.id).where(title: post.title).group(:id).select(:id)
1445
1486
 
1446
1487
  assert_equal [post], having_then_where
1447
1488
  end
@@ -1460,7 +1501,7 @@ class RelationTest < ActiveRecord::TestCase
1460
1501
  # Find any limit via our expression.
1461
1502
  coerce_tests! %r{relations don't load all records in #inspect}
1462
1503
  def test_relations_dont_load_all_records_in_inspect_coerced
1463
- assert_queries_match(/NEXT @0 ROWS.*@0 = \d+/) do
1504
+ assert_queries_match(/NEXT @0 ROWS/) do
1464
1505
  Post.all.inspect
1465
1506
  end
1466
1507
  end
@@ -1522,10 +1563,15 @@ module ActiveRecord
1522
1563
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1523
1564
  assert_equal expected, query
1524
1565
  end
1566
+
1567
+ # Order column must be in the GROUP clause. However, with implicit ordering we can't test this when selecting non-aggregate expression column.
1568
+ coerce_tests! %r{no queries when using pick with non-aggregate expression and empty IN}
1569
+
1570
+ # Order column must be in the GROUP clause. However, with implicit ordering we can't test this when selecting aggregate expression column.
1571
+ coerce_tests! %r{runs queries when using pick with aggregate expression despite empty IN}
1525
1572
  end
1526
1573
  end
1527
1574
 
1528
- require "models/post"
1529
1575
  class SanitizeTest < ActiveRecord::TestCase
1530
1576
  # Use nvarchar string (N'') in assert
1531
1577
  coerce_tests! :test_sanitize_sql_like_example_use_case
@@ -1559,19 +1605,19 @@ end
1559
1605
 
1560
1606
  class SchemaDumperTest < ActiveRecord::TestCase
1561
1607
  # Use nvarchar string (N'') in assert
1562
- coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1563
- def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1564
- versions = %w{ 20100101010101 20100201010101 20100301010101 }
1608
+ coerce_tests! :test_dump_schema_versions_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1609
+ def test_dump_schema_versions_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1610
+ versions = %w[20100101010101 20100201010101 20100301010101]
1565
1611
  versions.shuffle.each do |v|
1566
1612
  @schema_migration.create_version(v)
1567
1613
  end
1568
1614
 
1569
- schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
1615
+ schema_info = ActiveRecord::Base.lease_connection.dump_schema_versions
1570
1616
  expected = <<~STR
1571
- INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
1572
- (N'20100301010101'),
1573
- (N'20100201010101'),
1574
- (N'20100101010101');
1617
+ INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
1618
+ (N'20100301010101'),
1619
+ (N'20100201010101'),
1620
+ (N'20100101010101');
1575
1621
  STR
1576
1622
  assert_equal expected.strip, schema_info
1577
1623
  ensure
@@ -1591,7 +1637,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
1591
1637
  # Fall through false positive with no filter.
1592
1638
  coerce_tests! :test_schema_dumps_partial_indices
1593
1639
  def test_schema_dumps_partial_indices_coerced
1594
- index_definition = standard_dump.split(/\n/).grep(/t.index.*company_partial_index/).first.strip
1640
+ index_definition = standard_dump.split("\n").grep(/t.index.*company_partial_index/).first.strip
1595
1641
  assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "([rating]>(10))"', index_definition
1596
1642
  end
1597
1643
 
@@ -1608,7 +1654,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
1608
1654
  # SQL Server formats the check constraint expression differently.
1609
1655
  coerce_tests! :test_schema_dumps_check_constraints
1610
1656
  def test_schema_dumps_check_constraints_coerced
1611
- constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
1657
+ constraint_definition = dump_table_schema("products").split("\n").grep(/t.check_constraint.*products_price_check/).first.strip
1612
1658
  assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
1613
1659
  end
1614
1660
  end
@@ -1628,14 +1674,14 @@ class SchemaDumperDefaultsCoerceTest < ActiveRecord::TestCase
1628
1674
  setup do
1629
1675
  @connection = ActiveRecord::Base.lease_connection
1630
1676
  @connection.create_table :dump_defaults, force: true do |t|
1631
- t.string :string_with_default, default: "Hello!"
1632
- t.date :date_with_default, default: "2014-06-05"
1677
+ t.string :string_with_default, default: "Hello!"
1678
+ t.date :date_with_default, default: "2014-06-05"
1633
1679
  t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
1634
- t.time :time_with_default, default: "07:17:04"
1635
- t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1680
+ t.time :time_with_default, default: "07:17:04"
1681
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1636
1682
 
1637
- t.text :text_with_default, default: "John' Doe"
1638
- t.text :uuid, default: -> { "newid()" }
1683
+ t.text :text_with_default, default: "John' Doe"
1684
+ t.text :uuid, default: -> { "newid()" }
1639
1685
  end
1640
1686
  end
1641
1687
 
@@ -1652,7 +1698,6 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
1652
1698
  coerce_tests! %r{inspect on Model class does not raise}
1653
1699
  end
1654
1700
 
1655
- require "models/topic"
1656
1701
  class TransactionTest < ActiveRecord::TestCase
1657
1702
  # SQL Server does not have query for release_savepoint.
1658
1703
  coerce_tests! :test_releasing_named_savepoints
@@ -1698,7 +1743,7 @@ class TransactionTest < ActiveRecord::TestCase
1698
1743
  /DELETE/i,
1699
1744
  /^SAVE TRANSACTION/i,
1700
1745
  /DELETE/i,
1701
- /COMMIT/i,
1746
+ /COMMIT/i
1702
1747
  ]
1703
1748
 
1704
1749
  assert_equal expected_queries.size, actual_queries.size
@@ -1734,7 +1779,7 @@ class TransactionTest < ActiveRecord::TestCase
1734
1779
  /^SAVE TRANSACTION/i,
1735
1780
  /DELETE/i,
1736
1781
  /DELETE/i,
1737
- /COMMIT/i,
1782
+ /COMMIT/i
1738
1783
  ]
1739
1784
 
1740
1785
  assert_equal expected_queries.size, actual_queries.size
@@ -1744,7 +1789,6 @@ class TransactionTest < ActiveRecord::TestCase
1744
1789
  end
1745
1790
  end
1746
1791
 
1747
- require "models/tag"
1748
1792
  class TransactionIsolationTest < ActiveRecord::TestCase
1749
1793
  # SQL Server will lock the table for counts even when both
1750
1794
  # connections are `READ COMMITTED`. So we bypass with `READPAST`.
@@ -1762,9 +1806,27 @@ class TransactionIsolationTest < ActiveRecord::TestCase
1762
1806
 
1763
1807
  # I really need some help understanding this one.
1764
1808
  coerce_tests! %r{repeatable read}
1809
+
1810
+ private
1811
+
1812
+ # Need to handle the resetting of the isolation level in the adapter by `SQLServerRealTransaction#commit` for each
1813
+ # connection pool. After the resetting events have been removed we can assert the number of expected isolation level
1814
+ # events. This workaround assumes that the `count` also matches the number of connection pools used in the test.
1815
+ # Note: MySQL & PostgreSQL do not reset the connection and SQLite does support transaction isolation.
1816
+ undef_method :assert_begin_isolation_level_event
1817
+ def assert_begin_isolation_level_event(events, isolation: "READ COMMITTED", count: 1)
1818
+ isolation_events = events.select { |event| event.match(/SET TRANSACTION ISOLATION LEVEL/) }
1819
+
1820
+ count.times do
1821
+ index_of_reset_starting_isolation_level_event = isolation_events.index("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
1822
+ assert index_of_reset_starting_isolation_level_event.present?
1823
+ isolation_events.delete_at(index_of_reset_starting_isolation_level_event)
1824
+ end
1825
+
1826
+ assert_equal count, isolation_events.count { |event| event.match(/SET TRANSACTION ISOLATION LEVEL #{isolation}/) }
1827
+ end
1765
1828
  end
1766
1829
 
1767
- require "models/book"
1768
1830
  class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
1769
1831
  # We have a few view tables. use includes vs equality.
1770
1832
  coerce_tests! :test_views
@@ -1788,7 +1850,6 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
1788
1850
  end
1789
1851
  end
1790
1852
 
1791
- require "models/author"
1792
1853
  class YamlSerializationTest < ActiveRecord::TestCase
1793
1854
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1794
1855
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
@@ -1837,7 +1898,7 @@ class TimePrecisionTest < ActiveRecord::TestCase
1837
1898
  coerce_tests! :test_time_precision_is_truncated_on_assignment
1838
1899
  def test_time_precision_is_truncated_on_assignment_coerced
1839
1900
  @connection.create_table(:foos, force: true)
1840
- @connection.add_column :foos, :start, :time, precision: 0
1901
+ @connection.add_column :foos, :start, :time, precision: 0
1841
1902
  @connection.add_column :foos, :finish, :time, precision: 6
1842
1903
 
1843
1904
  time = ::Time.now.change(nsec: 123456789)
@@ -1873,8 +1934,8 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1873
1934
  coerce_tests! :test_default_negative_integer
1874
1935
  def test_default_negative_integer_coerced
1875
1936
  record = DefaultNumber.new
1876
- assert_equal (-5), record.negative_integer
1877
- assert_equal (-5), record.negative_integer_before_type_cast
1937
+ assert_equal(-5, record.negative_integer)
1938
+ assert_equal(-5, record.negative_integer_before_type_cast)
1878
1939
  end
1879
1940
 
1880
1941
  # We do better with native types and do not return strings for everything.
@@ -1903,7 +1964,6 @@ module ActiveRecord
1903
1964
  end
1904
1965
  end
1905
1966
 
1906
- require "models/book"
1907
1967
  module ActiveRecord
1908
1968
  class StatementCacheTest < ActiveRecord::TestCase
1909
1969
  # Getting random failures.
@@ -1916,7 +1976,7 @@ module ActiveRecord
1916
1976
 
1917
1977
  original_test_statement_cache_values_differ
1918
1978
  ensure
1919
- Book.where(author_id: nil, name: 'my book').delete_all
1979
+ Book.where(author_id: nil, name: "my book").delete_all
1920
1980
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1921
1981
  end
1922
1982
  end
@@ -1926,53 +1986,38 @@ module ActiveRecord
1926
1986
  module ConnectionAdapters
1927
1987
  class SchemaCacheTest < ActiveRecord::TestCase
1928
1988
  # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1929
- coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1930
-
1931
- # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1932
- coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1933
-
1934
- # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1935
- unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1936
- def test_marshal_dump_and_load_with_gzip_coerced
1937
- with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1938
- end
1939
- def test_marshal_dump_and_load_via_disk_coerced
1940
- with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1989
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
1990
+
1991
+ # Cast type in SQL Server is :varchar rather than Unicode :string.
1992
+ coerce_tests! :test_yaml_load_8_0_dump_without_cast_type_still_get_the_right_one
1993
+ def test_yaml_load_8_0_dump_without_cast_type_still_get_the_right_one
1994
+ cache = load_bound_reflection(schema_dump_8_0_path)
1995
+
1996
+ assert_no_queries do
1997
+ columns = cache.columns_hash("courses")
1998
+ assert_equal 3, columns.size
1999
+ cast_type = columns["name"].fetch_cast_type(@connection)
2000
+ assert_not_nil cast_type, "expected cast_type to be present"
2001
+ assert_equal :varchar, cast_type.type
1941
2002
  end
1942
2003
  end
1943
2004
 
1944
2005
  private
1945
2006
 
1946
- def with_marshable_time_defaults
1947
- # Detect problems
1948
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1949
- column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1950
- current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1951
- end
1952
-
1953
- # Correct problems
1954
- if current_default.present?
1955
- @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1956
- end
1957
-
1958
- # Run original test
1959
- yield
1960
- ensure
1961
- # Revert changes
1962
- @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
2007
+ # We need to give the full paths for this to work.
2008
+ undef_method :schema_dump_5_1_path
2009
+ def schema_dump_5_1_path
2010
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1963
2011
  end
1964
2012
 
1965
- # We need to give the full path for this to work.
1966
- undef_method :schema_dump_path
1967
- def schema_dump_path
1968
- File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
2013
+ undef_method :schema_dump_8_0_path
2014
+ def schema_dump_8_0_path
2015
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_8_0.yml")
1969
2016
  end
1970
2017
  end
1971
2018
  end
1972
2019
  end
1973
2020
 
1974
- require "models/post"
1975
- require "models/comment"
1976
2021
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1977
2022
  fixtures :posts
1978
2023
 
@@ -2093,9 +2138,9 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
2093
2138
  test "order: allows valid arguments with COLLATE" do
2094
2139
  collation_name = "Latin1_General_CS_AS_WS"
2095
2140
 
2096
- ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2141
+ ids_expected = Post.order(Arel.sql(%(author_id, title COLLATE #{collation_name} DESC))).pluck(:id)
2097
2142
 
2098
- ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2143
+ ids = Post.order(["author_id", %(title COLLATE #{collation_name} DESC)]).pluck(:id)
2099
2144
 
2100
2145
  assert_equal ids_expected, ids
2101
2146
  end
@@ -2148,7 +2193,7 @@ class RelationMergingTest < ActiveRecord::TestCase
2148
2193
  non_mary_and_bob = Author.where.not(id: [mary, bob])
2149
2194
 
2150
2195
  author_id = Author.lease_connection.quote_table_name("authors.id")
2151
- assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
2196
+ assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)/) do
2152
2197
  assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
2153
2198
  end
2154
2199
 
@@ -2169,14 +2214,13 @@ module ActiveRecord
2169
2214
  end
2170
2215
  end
2171
2216
 
2172
- require "models/book"
2173
2217
  class EnumTest < ActiveRecord::TestCase
2174
2218
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2175
2219
  coerce_tests! %r{enums are distinct per class}
2176
2220
  test "enums are distinct per class coerced" do
2177
2221
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2178
2222
 
2179
- send(:'original_enums are distinct per class')
2223
+ send(:"original_enums are distinct per class")
2180
2224
  ensure
2181
2225
  Book.where(author_id: nil, name: nil).delete_all
2182
2226
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2187,7 +2231,7 @@ class EnumTest < ActiveRecord::TestCase
2187
2231
  test "creating new objects with enum scopes coerced" do
2188
2232
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2189
2233
 
2190
- send(:'original_creating new objects with enum scopes')
2234
+ send(:"original_creating new objects with enum scopes")
2191
2235
  ensure
2192
2236
  Book.where(author_id: nil, name: nil).delete_all
2193
2237
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2198,7 +2242,7 @@ class EnumTest < ActiveRecord::TestCase
2198
2242
  test "enums are inheritable coerced" do
2199
2243
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2200
2244
 
2201
- send(:'original_enums are inheritable')
2245
+ send(:"original_enums are inheritable")
2202
2246
  ensure
2203
2247
  Book.where(author_id: nil, name: nil).delete_all
2204
2248
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2209,14 +2253,13 @@ class EnumTest < ActiveRecord::TestCase
2209
2253
  test "serializable? with large number label coerced" do
2210
2254
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2211
2255
 
2212
- send(:'original_serializable\? with large number label')
2256
+ send(:"original_serializable\\? with large number label")
2213
2257
  ensure
2214
2258
  Book.where(author_id: nil, name: nil).delete_all
2215
2259
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2216
2260
  end
2217
2261
  end
2218
2262
 
2219
- require "models/citation"
2220
2263
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
2221
2264
  fixtures :citations
2222
2265
 
@@ -2251,7 +2294,7 @@ end
2251
2294
  class ReloadModelsTest < ActiveRecord::TestCase
2252
2295
  # Skip test on Windows. The number of arguments passed to `IO.popen` in
2253
2296
  # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
2254
- coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2297
+ coerce_tests! :test_has_one_with_reload if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2255
2298
  end
2256
2299
 
2257
2300
  class MarshalSerializationTest < ActiveRecord::TestCase
@@ -2313,7 +2356,7 @@ class PreloaderTest < ActiveRecord::TestCase
2313
2356
 
2314
2357
  c = Cpk::OrderAgreement.lease_connection
2315
2358
  order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
2316
- order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
2359
+ order_id_constraint = /#{order_id_column} = @0$/
2317
2360
  expectation = /SELECT.*WHERE.* #{order_id_constraint}/
2318
2361
 
2319
2362
  assert_match(expectation, preload_sql)
@@ -2337,7 +2380,7 @@ class PreloaderTest < ActiveRecord::TestCase
2337
2380
 
2338
2381
  c = Cpk::Order.lease_connection
2339
2382
  order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
2340
- order_constraint = /#{order_id} = @0.*@0 = \d+$/
2383
+ order_constraint = /#{order_id} = @0$/
2341
2384
  expectation = /SELECT.*WHERE.* #{order_constraint}/
2342
2385
 
2343
2386
  assert_match(expectation, preload_sql)
@@ -2347,15 +2390,14 @@ end
2347
2390
 
2348
2391
  class MigratorTest < ActiveRecord::TestCase
2349
2392
  # Test fails on Windows AppVeyor CI for unknown reason.
2350
- coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2393
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2351
2394
  end
2352
2395
 
2353
2396
  class MultiDbMigratorTest < ActiveRecord::TestCase
2354
2397
  # Test fails on Windows AppVeyor CI for unknown reason.
2355
- coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2398
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2356
2399
  end
2357
2400
 
2358
- require "models/book"
2359
2401
  class FieldOrderedValuesTest < ActiveRecord::TestCase
2360
2402
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2361
2403
  coerce_tests! :test_in_order_of_with_enums_values
@@ -2402,72 +2444,11 @@ class FieldOrderedValuesTest < ActiveRecord::TestCase
2402
2444
  end
2403
2445
  end
2404
2446
 
2405
- require "models/dashboard"
2406
2447
  class QueryLogsTest < ActiveRecord::TestCase
2407
- # SQL requires double single-quotes.
2408
- coerce_tests! :test_sql_commenter_format
2409
- def test_sql_commenter_format_coerced
2410
- ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2411
- ActiveRecord::QueryLogs.tags = [:application]
2412
-
2413
- assert_queries_match(%r{/\*application=''active_record''\*/}) do
2414
- Dashboard.first
2415
- end
2416
- end
2417
-
2418
- # SQL requires double single-quotes.
2419
- coerce_tests! :test_sqlcommenter_format_value
2420
- def test_sqlcommenter_format_value_coerced
2421
- ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2422
-
2423
- ActiveRecord::QueryLogs.tags = [
2424
- :application,
2425
- { tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
2426
- ]
2427
-
2428
- assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2429
- Dashboard.first
2430
- end
2431
- end
2432
-
2433
- # SQL requires double single-quotes.
2434
- coerce_tests! :test_sqlcommenter_format_value_string_coercible
2435
- def test_sqlcommenter_format_value_string_coercible_coerced
2436
- ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2437
-
2438
- ActiveRecord::QueryLogs.tags = [
2439
- :application,
2440
- { custom_proc: -> { 1234 } },
2441
- ]
2442
-
2443
- assert_queries_match(%r{custom_proc=''1234''\*/}) do
2444
- Dashboard.first
2445
- end
2446
- end
2447
-
2448
- # SQL requires double single-quotes.
2449
- coerce_tests! :test_sqlcommenter_format_allows_string_keys
2450
- def test_sqlcommenter_format_allows_string_keys_coerced
2451
- ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2452
-
2453
- ActiveRecord::QueryLogs.tags = [
2454
- :application,
2455
- {
2456
- "string" => "value",
2457
- tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7",
2458
- custom_proc: -> { "Joe's Shack" }
2459
- },
2460
- ]
2461
-
2462
- assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2463
- Dashboard.first
2464
- end
2465
- end
2466
-
2467
2448
  # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2468
2449
  coerce_tests! :test_invalid_encoding_query
2469
2450
  def test_invalid_encoding_query_coerced
2470
- ActiveRecord::QueryLogs.tags = [ :application ]
2451
+ ActiveRecord::QueryLogs.tags = [:application]
2471
2452
  assert_raises ActiveRecord::StatementInvalid do
2472
2453
  ActiveRecord::Base.lease_connection.execute "select 1 as '\xFF'"
2473
2454
  end
@@ -2480,8 +2461,8 @@ class InsertAllTest < ActiveRecord::TestCase
2480
2461
  def test_insert_all_returns_requested_sql_fields_coerced
2481
2462
  skip unless supports_insert_returning?
2482
2463
 
2483
- result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2484
- assert_equal %w[ REWORK ], result.pluck("name")
2464
+ result = Book.insert_all! [{name: "Rework", author_id: 1}], returning: Arel.sql("UPPER(INSERTED.name) as name")
2465
+ assert_equal %w[REWORK], result.pluck("name")
2485
2466
  end
2486
2467
 
2487
2468
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
@@ -2521,7 +2502,7 @@ module ActiveRecord
2521
2502
  undef_method :invalid_add_column_option_exception_message
2522
2503
  def invalid_add_column_option_exception_message(key)
2523
2504
  default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2524
- default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2505
+ default_keys.concat([":is_identity", ":as", ":stored"]) # SQL Server additional valid keys
2525
2506
 
2526
2507
  "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2527
2508
  end
@@ -2529,11 +2510,11 @@ module ActiveRecord
2529
2510
  end
2530
2511
  end
2531
2512
 
2532
-
2533
2513
  # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2534
2514
  # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2535
2515
  class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2536
2516
  private
2517
+
2537
2518
  undef_method :serializer
2538
2519
  def serializer
2539
2520
  @serializer ||= ::MessagePack::Factory.new.tap do |factory|
@@ -2556,7 +2537,7 @@ end
2556
2537
 
2557
2538
  module ActiveRecord
2558
2539
  module ConnectionAdapters
2559
- class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2540
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2560
2541
  # Tests are not about a specific adapter.
2561
2542
  coerce_all_tests!
2562
2543
  end
@@ -2680,7 +2661,6 @@ module ActiveRecord
2680
2661
  end
2681
2662
  end
2682
2663
 
2683
-
2684
2664
  module ActiveRecord
2685
2665
  class TableMetadataTest < ActiveSupport::TestCase
2686
2666
  # Adapter returns an object that is subclass of what is expected in the original test.
@@ -2712,33 +2692,6 @@ module ActiveRecord
2712
2692
  end
2713
2693
  end
2714
2694
 
2715
- require "models/car"
2716
- class ExplainTest < ActiveRecord::TestCase
2717
- # Expected query slightly different from because of 'sp_executesql' and query parameters.
2718
- coerce_tests! :test_relation_explain_with_first
2719
- def test_relation_explain_with_first_coerced
2720
- expected_query = capture_sql {
2721
- Car.all.first
2722
- }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2723
- message = Car.all.explain.first
2724
- assert_match(/^EXPLAIN/, message)
2725
- assert_match(expected_query, message)
2726
- end
2727
-
2728
- # Expected query slightly different from because of 'sp_executesql' and query parameters.
2729
- coerce_tests! :test_relation_explain_with_last
2730
- def test_relation_explain_with_last_coerced
2731
- expected_query = capture_sql {
2732
- Car.all.last
2733
- }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2734
- expected_query = expected_query
2735
- message = Car.all.explain.last
2736
-
2737
- assert_match(/^EXPLAIN/, message)
2738
- assert_match(expected_query, message)
2739
- end
2740
- end
2741
-
2742
2695
  module ActiveRecord
2743
2696
  module Assertions
2744
2697
  class QueryAssertionsTest < ActiveSupport::TestCase
@@ -2774,7 +2727,7 @@ module ActiveRecord
2774
2727
  relation = Company.with_recursive(
2775
2728
  top_companies_and_children: [
2776
2729
  Company.where(firm_id: nil),
2777
- Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id"),
2730
+ Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id")
2778
2731
  ]
2779
2732
  ).from("top_companies_and_children AS companies")
2780
2733
 
@@ -2784,3 +2737,103 @@ module ActiveRecord
2784
2737
  end
2785
2738
  end
2786
2739
 
2740
+ module ActiveRecord
2741
+ class AdapterConnectionTest < ActiveRecord::TestCase
2742
+ # Original method only handled the core adapters.
2743
+ undef_method :raw_transaction_open?
2744
+ def raw_transaction_open?(connection)
2745
+ transaction_count = connection.instance_variable_get(:@raw_connection).execute("SELECT @@TRANCOUNT AS TRANSACTION_COUNT").first["TRANSACTION_COUNT"]
2746
+ transaction_count > 0
2747
+ rescue
2748
+ false
2749
+ end
2750
+ end
2751
+ end
2752
+
2753
+ class EachTest < ActiveRecord::TestCase
2754
+ # Match SQL Server limit implementation.
2755
+ coerce_tests! :test_in_batches_executes_range_queries_when_unconstrained
2756
+ def test_in_batches_executes_range_queries_when_unconstrained_coerced
2757
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2758
+
2759
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET @\S ROWS FETCH NEXT @\S ROWS ONLY/i, count: 6) do
2760
+ assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 1) do
2761
+ Post.in_batches(of: 2).to_a
2762
+ end
2763
+ end
2764
+
2765
+ assert_queries_match(/WHERE #{quoted_posts_id} > .+ AND #{quoted_posts_id} <= .+/i) do
2766
+ relations.each { |relation| assert_kind_of Post, relation.first }
2767
+ end
2768
+ end
2769
+
2770
+ # Match SQL Server limit implementation.
2771
+ coerce_tests! :test_in_batches_executes_in_queries_when_unconstrained_and_opted_out_of_ranges
2772
+ def test_in_batches_executes_in_queries_when_unconstrained_and_opted_out_of_ranges_coerced
2773
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2774
+
2775
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 6) do
2776
+ Post.in_batches(of: 2, use_ranges: false).to_a
2777
+ end
2778
+
2779
+ assert_queries_match(/#{quoted_posts_id} IN \(.+\)/i) do
2780
+ relations.each { |relation| assert_kind_of Post, relation.first }
2781
+ end
2782
+ end
2783
+
2784
+ # Match SQL Server limit implementation.
2785
+ coerce_tests! :test_in_batches_executes_in_queries_when_constrained
2786
+ def test_in_batches_executes_in_queries_when_constrained_coerced
2787
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2788
+
2789
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 3) do
2790
+ Post.where("id < ?", 5).in_batches(of: 2).to_a
2791
+ end
2792
+
2793
+ assert_queries_match(/#{quoted_posts_id} IN \(.+\)/i) do
2794
+ relations.each { |relation| assert_kind_of Post, relation.first }
2795
+ end
2796
+ end
2797
+
2798
+ # Match SQL Server limit implementation.
2799
+ coerce_tests! :test_in_batches_executes_range_queries_when_constrained_and_opted_in_into_ranges
2800
+ def test_in_batches_executes_range_queries_when_constrained_and_opted_in_into_ranges_coerced
2801
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2802
+
2803
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET @\S ROWS FETCH NEXT @\S ROWS ONLY/i, count: 3) do
2804
+ assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 1) do
2805
+ Post.where("id < ?", 5).in_batches(of: 2, use_ranges: true).to_a
2806
+ end
2807
+ end
2808
+
2809
+ assert_queries_match(/#{quoted_posts_id} > .+ AND #{quoted_posts_id} <= .+/i) do
2810
+ relations.each { |relation| assert_kind_of Post, relation.first }
2811
+ end
2812
+ end
2813
+
2814
+ # Match SQL Server SQL format.
2815
+ coerce_tests! :test_in_batches_should_unscope_cursor_after_pluck
2816
+ def test_in_batches_should_unscope_cursor_after_pluck_coerced
2817
+ all_ids = Post.limit(2).pluck(:id)
2818
+ found_ids = []
2819
+ # only a single clause on id (i.e. not 'id IN (?,?) AND id = ?', but only 'id = ?')
2820
+ assert_queries_match(/WHERE #{Regexp.escape(quote_table_name("posts.id"))} = \S+ ORDER BY/) do
2821
+ Post.where(id: all_ids).in_batches(of: 1) do |relation|
2822
+ found_ids << relation.pick(:id)
2823
+ end
2824
+ end
2825
+ assert_equal all_ids.sort, found_ids
2826
+ end
2827
+
2828
+ # Match SQL Server SQL format.
2829
+ coerce_tests! :test_in_batches_loaded_should_unscope_cursor_after_pluck
2830
+ def test_in_batches_loaded_should_unscope_cursor_after_pluck_coerced
2831
+ all_ids = Post.limit(2).pluck(:id)
2832
+ # only a single clause on id (i.e. not 'id IN (?,?) AND id = ?', but only 'id = ?')
2833
+ assert_queries_match(/WHERE #{Regexp.escape(quote_table_name("posts.id"))} = \S+;/) do
2834
+ Post.where(id: all_ids).in_batches(of: 1, load: true) do |relation|
2835
+ relation.delete_all
2836
+ end
2837
+ end
2838
+ end
2839
+ end