activerecord-sqlserver-adapter 6.1.2.1 → 7.2.4

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +30 -0
  3. data/.devcontainer/boot.sh +22 -0
  4. data/.devcontainer/devcontainer.json +38 -0
  5. data/.devcontainer/docker-compose.yml +42 -0
  6. data/.github/workflows/ci.yml +7 -4
  7. data/.gitignore +3 -1
  8. data/CHANGELOG.md +19 -42
  9. data/Dockerfile.ci +3 -3
  10. data/Gemfile +6 -1
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +113 -27
  13. data/RUNNING_UNIT_TESTS.md +27 -14
  14. data/Rakefile +2 -6
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +3 -3
  17. data/appveyor.yml +4 -6
  18. data/docker-compose.ci.yml +2 -1
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +12 -2
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
  26. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  27. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
  28. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  29. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
  30. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
  32. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
  33. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
  34. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  35. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
  36. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  37. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
  39. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  41. data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
  43. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  44. data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
  45. data/lib/arel/visitors/sqlserver.rb +77 -34
  46. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  47. data/test/cases/adapter_test_sqlserver.rb +114 -26
  48. data/test/cases/coerced_tests.rb +1121 -340
  49. data/test/cases/column_test_sqlserver.rb +67 -64
  50. data/test/cases/connection_test_sqlserver.rb +3 -6
  51. data/test/cases/dbconsole.rb +19 -0
  52. data/test/cases/disconnected_test_sqlserver.rb +8 -5
  53. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  54. data/test/cases/enum_test_sqlserver.rb +49 -0
  55. data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
  56. data/test/cases/fetch_test_sqlserver.rb +19 -0
  57. data/test/cases/helper_sqlserver.rb +11 -5
  58. data/test/cases/index_test_sqlserver.rb +8 -6
  59. data/test/cases/json_test_sqlserver.rb +1 -1
  60. data/test/cases/lateral_test_sqlserver.rb +2 -2
  61. data/test/cases/migration_test_sqlserver.rb +19 -1
  62. data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
  63. data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
  64. data/test/cases/primary_keys_test_sqlserver.rb +2 -2
  65. data/test/cases/rake_test_sqlserver.rb +10 -5
  66. data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
  67. data/test/cases/schema_test_sqlserver.rb +64 -1
  68. data/test/cases/showplan_test_sqlserver.rb +7 -7
  69. data/test/cases/specific_schema_test_sqlserver.rb +17 -13
  70. data/test/cases/transaction_test_sqlserver.rb +13 -8
  71. data/test/cases/trigger_test_sqlserver.rb +20 -0
  72. data/test/cases/utils_test_sqlserver.rb +2 -2
  73. data/test/cases/uuid_test_sqlserver.rb +8 -0
  74. data/test/cases/view_test_sqlserver.rb +58 -0
  75. data/test/config.yml +1 -2
  76. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  77. data/test/models/sqlserver/alien.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/trigger.rb +8 -0
  80. data/test/schema/sqlserver_specific_schema.rb +54 -6
  81. data/test/support/coerceable_test_sqlserver.rb +4 -4
  82. data/test/support/connection_reflection.rb +3 -9
  83. data/test/support/core_ext/query_cache.rb +7 -1
  84. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  85. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  86. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  87. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  88. data/test/support/query_assertions.rb +49 -0
  89. data/test/support/rake_helpers.rb +3 -1
  90. data/test/support/table_definition_sqlserver.rb +24 -0
  91. data/test/support/test_in_memory_oltp.rb +2 -2
  92. metadata +41 -17
  93. data/lib/active_record/sqlserver_base.rb +0 -18
  94. data/test/cases/scratchpad_test_sqlserver.rb +0 -8
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  96. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  97. data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -47,12 +47,26 @@ class UniquenessValidationTest < ActiveRecord::TestCase
47
47
  end
48
48
  end
49
49
 
50
+ class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
51
+ # Need to explicitly set the WHERE clause to truthy.
52
+ coerce_tests! :test_partial_index
53
+ def test_partial_index_coerced
54
+ Topic.validates_uniqueness_of(:title)
55
+ @connection.add_index(:topics, :title, unique: true, where: "approved=1", name: :topics_index)
56
+
57
+ t = Topic.create!(title: "abc")
58
+ t.author_name = "John"
59
+ assert_queries_count(1) do
60
+ t.valid?
61
+ end
62
+ end
63
+ end
64
+
50
65
  require "models/event"
51
66
  module ActiveRecord
52
67
  class AdapterTest < ActiveRecord::TestCase
53
68
  # Legacy binds are not supported.
54
69
  coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
55
- coerce_tests! :test_select_all_insert_update_delete_with_legacy_binds
56
70
 
57
71
  # As far as I can tell, SQL Server does not support null bytes in strings.
58
72
  coerce_tests! :test_update_prepared_statement
@@ -92,37 +106,17 @@ module ActiveRecord
92
106
  Subscriber.send(:load_schema!)
93
107
  original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
94
108
  end
95
- end
96
- end
97
-
98
- module ActiveRecord
99
- class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
100
- # Fix randomly failing test. The loading of the model's schema was affecting the test.
101
- coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
102
- def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
103
- Subscriber.send(:load_schema!)
104
- original_test_errors_when_an_insert_query_is_called_while_preventing_writes
105
- end
106
109
 
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
110
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
111
+ coerce_tests! :test_doesnt_error_when_a_select_query_has_encoding_errors
112
+ def test_doesnt_error_when_a_select_query_has_encoding_errors_coerced
113
+ ActiveRecord::Base.while_preventing_writes do
114
+ # TinyTDS fail on encoding errors.
115
+ # But at least we can assert it fails in the client and not before when trying to match the query.
116
+ assert_raises ActiveRecord::StatementInvalid do
117
+ @connection.select_all("SELECT '\xC8'")
118
+ end
119
+ end
126
120
  end
127
121
  end
128
122
  end
@@ -203,7 +197,7 @@ class BasicsTest < ActiveRecord::TestCase
203
197
  # Use square brackets as SQL Server escaped character
204
198
  coerce_tests! :test_column_names_are_escaped
205
199
  def test_column_names_are_escaped_coerced
206
- conn = ActiveRecord::Base.connection
200
+ conn = ActiveRecord::Base.lease_connection
207
201
  assert_equal "[t]]]", conn.quote_column_name("t]")
208
202
  end
209
203
 
@@ -235,18 +229,6 @@ class BasicsTest < ActiveRecord::TestCase
235
229
  end
236
230
  end
237
231
  end
238
-
239
- # SQL Server does not have query for release_savepoint
240
- coerce_tests! %r{an empty transaction does not raise if preventing writes}
241
- test "an empty transaction does not raise if preventing writes coerced" do
242
- ActiveRecord::Base.while_preventing_writes do
243
- assert_queries(1, ignore_none: true) do
244
- Bird.transaction do
245
- ActiveRecord::Base.connection.materialize_transactions
246
- end
247
- end
248
- end
249
- end
250
232
  end
251
233
 
252
234
  class BelongsToAssociationsTest < ActiveRecord::TestCase
@@ -266,7 +248,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
266
248
  def test_belongs_to_coerced
267
249
  client = Client.find(3)
268
250
  first_firm = companies(:first_firm)
269
- assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
251
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
270
252
  assert_equal first_firm, client.firm
271
253
  assert_equal first_firm.name, client.firm.name
272
254
  end
@@ -329,7 +311,7 @@ module ActiveRecord
329
311
 
330
312
  authors = Author.where(id: [1, 2, 3, nil])
331
313
  assert_equal sql_unprepared, @connection.to_sql(authors.arel)
332
- assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
314
+ assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
333
315
 
334
316
  # prepared_statements: true
335
317
  #
@@ -344,7 +326,7 @@ module ActiveRecord
344
326
 
345
327
  authors = Author.where(id: [1, 2, 3, 9223372036854775808])
346
328
  assert_equal sql_unprepared, @connection.to_sql(authors.arel)
347
- assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
329
+ assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
348
330
  end
349
331
  end
350
332
  end
@@ -357,10 +339,55 @@ module ActiveRecord
357
339
  Book.send(:load_schema!)
358
340
  original_test_payload_name_on_load
359
341
  end
342
+
343
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
344
+ coerce_tests! :test_payload_row_count_on_select_all
345
+ def test_payload_row_count_on_select_all_coerced
346
+ connection.remove_index(:books, column: [:author_id, :name])
347
+
348
+ original_test_payload_row_count_on_select_all
349
+ ensure
350
+ Book.where(author_id: nil, name: 'row count book 1').delete_all
351
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
352
+ end
353
+
354
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
355
+ coerce_tests! :test_payload_row_count_on_pluck
356
+ def test_payload_row_count_on_pluck_coerced
357
+ connection.remove_index(:books, column: [:author_id, :name])
358
+
359
+ original_test_payload_row_count_on_pluck
360
+ ensure
361
+ Book.where(author_id: nil, name: 'row count book 2').delete_all
362
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
363
+ end
364
+
365
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
366
+ coerce_tests! :test_payload_row_count_on_raw_sql
367
+ def test_payload_row_count_on_raw_sql_coerced
368
+ connection.remove_index(:books, column: [:author_id, :name])
369
+
370
+ original_test_payload_row_count_on_raw_sql
371
+ ensure
372
+ Book.where(author_id: nil, name: 'row count book 3').delete_all
373
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
374
+ end
360
375
  end
361
376
  end
362
377
 
363
378
  class CalculationsTest < ActiveRecord::TestCase
379
+ # SELECT columns must be in the GROUP clause.
380
+ coerce_tests! :test_should_count_with_group_by_qualified_name_on_loaded
381
+ def test_should_count_with_group_by_qualified_name_on_loaded_coerced
382
+ accounts = Account.group("accounts.id").select("accounts.id")
383
+ expected = { 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1, 6 => 1 }
384
+ assert_not_predicate accounts, :loaded?
385
+ assert_equal expected, accounts.count
386
+ accounts.load
387
+ assert_predicate accounts, :loaded?
388
+ assert_equal expected, accounts.count(:id)
389
+ end
390
+
364
391
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
365
392
  coerce_tests! :test_offset_is_kept
366
393
  def test_offset_is_kept_coerced
@@ -439,7 +466,7 @@ class CalculationsTest < ActiveRecord::TestCase
439
466
  FROM companies
440
467
  INNER JOIN accounts ON companies.id = accounts.firm_id
441
468
  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
469
+ GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description, companies.status
443
470
  ORDER BY companies.id
444
471
  OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
445
472
  SQL
@@ -465,7 +492,7 @@ class CalculationsTest < ActiveRecord::TestCase
465
492
  .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
466
493
  .where(id: rails_core)
467
494
  .joins(:account)
468
- .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description)
495
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
469
496
  .take!
470
497
 
471
498
  # all the DependentFirm attributes should be present
@@ -479,7 +506,7 @@ class CalculationsTest < ActiveRecord::TestCase
479
506
  # Match SQL Server limit implementation
480
507
  coerce_tests! :test_limit_is_kept
481
508
  def test_limit_is_kept_coerced
482
- queries = capture_sql_ss { Account.limit(1).count }
509
+ queries = capture_sql { Account.limit(1).count }
483
510
  assert_equal 1, queries.length
484
511
  assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
485
512
  end
@@ -487,7 +514,7 @@ class CalculationsTest < ActiveRecord::TestCase
487
514
  # Match SQL Server limit implementation
488
515
  coerce_tests! :test_limit_with_offset_is_kept
489
516
  def test_limit_with_offset_is_kept_coerced
490
- queries = capture_sql_ss { Account.limit(1).offset(1).count }
517
+ queries = capture_sql { Account.limit(1).offset(1).count }
491
518
  assert_equal 1, queries.length
492
519
  assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
493
520
  end
@@ -496,12 +523,15 @@ class CalculationsTest < ActiveRecord::TestCase
496
523
  coerce_tests! :test_distinct_count_all_with_custom_select_and_order
497
524
  def test_distinct_count_all_with_custom_select_and_order_coerced
498
525
  accounts = Account.distinct.select("credit_limit % 10 AS the_limit").order(Arel.sql("credit_limit % 10"))
499
- assert_queries(1) { assert_equal 3, accounts.count(:all) }
500
- assert_queries(1) { assert_equal 3, accounts.load.size }
526
+ assert_queries_count(1) { assert_equal 3, accounts.count(:all) }
527
+ assert_queries_count(1) { assert_equal 3, accounts.load.size }
501
528
  end
502
529
 
503
530
  # Leave it up to users to format selects/functions so HAVING works correctly.
504
531
  coerce_tests! :test_having_with_strong_parameters
532
+
533
+ # SELECT columns must be in the GROUP clause. Since since `ids` only selects the primary key you cannot perform this query in SQL Server.
534
+ coerce_tests! :test_ids_with_includes_and_non_primary_key_order
505
535
  end
506
536
 
507
537
  module ActiveRecord
@@ -531,29 +561,46 @@ module ActiveRecord
531
561
  assert_equal 1, four.default
532
562
  assert_equal "hello", five.default
533
563
  end
534
- end
535
- end
536
- end
537
564
 
538
- module ActiveRecord
539
- module ConnectionAdapters
540
- class QuoteARBaseTest < ActiveRecord::TestCase
541
- # Use our date format.
542
- coerce_tests! :test_quote_ar_object
543
- def test_quote_ar_object_coerced
544
- value = DatetimePrimaryKey.new(id: @time)
545
- assert_deprecated do
546
- assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
565
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
566
+ coerce_tests! :test_add_column_with_postgresql_datetime_type
567
+ def test_add_column_with_postgresql_datetime_type_coerced
568
+ connection.create_table :testings do |t|
569
+ t.column :foo, :datetime
547
570
  end
571
+
572
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
573
+
574
+ assert_equal :datetime, column.type
575
+ assert_equal "datetime2(6)", column.sql_type
576
+ end
577
+
578
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
579
+ coerce_tests! :test_change_column_with_timestamp_type
580
+ def test_change_column_with_timestamp_type_coerced
581
+ connection.create_table :testings do |t|
582
+ t.column :foo, :datetime, null: false
583
+ end
584
+
585
+ connection.change_column :testings, :foo, :timestamp
586
+
587
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
588
+
589
+ assert_equal :datetime, column.type
590
+ assert_equal "datetime2(6)", column.sql_type
548
591
  end
549
592
 
550
- # Use our date format.
551
- coerce_tests! :test_type_cast_ar_object
552
- def test_type_cast_ar_object_coerced
553
- value = DatetimePrimaryKey.new(id: @time)
554
- assert_deprecated do
555
- assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
593
+ # Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
594
+ coerce_tests! :test_add_column_with_timestamp_type
595
+ def test_add_column_with_timestamp_type_coerced
596
+ connection.create_table :testings do |t|
597
+ t.column :foo, :timestamp
556
598
  end
599
+
600
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
601
+
602
+ assert_equal :datetime, column.type
603
+ assert_equal "datetime2(6)", column.sql_type
557
604
  end
558
605
  end
559
606
  end
@@ -616,6 +663,129 @@ class MigrationTest < ActiveRecord::TestCase
616
663
  # For some reason our tests set Rails.@_env which breaks test env switching.
617
664
  coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
618
665
  coerce_tests! :test_internal_metadata_stores_environment
666
+
667
+ # Same as original but using binary type instead of blob
668
+ coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
669
+ def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
670
+ migration_a = Class.new(ActiveRecord::Migration::Current) {
671
+ def version; 100 end
672
+ def migrate(x)
673
+ add_column "people", "last_name", :binary
674
+ end
675
+ }.new
676
+
677
+ migration_b = Class.new(ActiveRecord::Migration::Current) {
678
+ def version; 101 end
679
+ def migrate(x)
680
+ add_column "people", "last_name", :binary, if_not_exists: true
681
+ end
682
+ }.new
683
+
684
+ ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, @internal_metadata, 100).migrate
685
+ assert_column Person, :last_name, "migration_a should have created the last_name column on people"
686
+
687
+ assert_nothing_raised do
688
+ ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, @internal_metadata, 101).migrate
689
+ end
690
+ ensure
691
+ Person.reset_column_information
692
+ if Person.column_names.include?("last_name")
693
+ Person.lease_connection.remove_column("people", "last_name")
694
+ end
695
+ end
696
+ end
697
+
698
+ module ActiveRecord
699
+ class Migration
700
+ class CompatibilityTest < ActiveRecord::TestCase
701
+ # Error message depends on the database adapter.
702
+ coerce_tests! :test_create_table_on_7_0
703
+ def test_create_table_on_7_0_coerced
704
+ long_table_name = "a" * (connection.table_name_length + 1)
705
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
706
+ @@long_table_name = long_table_name
707
+ def version; 100 end
708
+ def migrate(x)
709
+ create_table @@long_table_name
710
+ end
711
+ }.new
712
+
713
+ error = assert_raises(StandardError) do
714
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
715
+ end
716
+ assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
717
+ ensure
718
+ connection.drop_table(long_table_name) rescue nil
719
+ end
720
+
721
+ # 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).
722
+ coerce_tests! :test_rename_table_on_7_0
723
+ def test_rename_table_on_7_0_coerced
724
+ long_table_name = "a" * (connection.table_name_length + 1)
725
+ connection.create_table(:more_testings)
726
+
727
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
728
+ @@long_table_name = long_table_name
729
+ def version; 100 end
730
+ def migrate(x)
731
+ rename_table :more_testings, @@long_table_name
732
+ end
733
+ }.new
734
+
735
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
736
+ assert connection.table_exists?(long_table_name[0...-1])
737
+ assert_not connection.table_exists?(:more_testings)
738
+ assert connection.table_exists?(long_table_name[0...-1])
739
+ ensure
740
+ connection.drop_table(:more_testings) rescue nil
741
+ connection.drop_table(long_table_name[0...-1]) rescue nil
742
+ end
743
+
744
+ # SQL Server has a different maximum index name length.
745
+ coerce_tests! :test_add_index_errors_on_too_long_name_7_0
746
+ def test_add_index_errors_on_too_long_name_7_0_coerced
747
+ long_index_name = 'a' * (connection.index_name_length + 1)
748
+
749
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
750
+ @@long_index_name = long_index_name
751
+ def migrate(x)
752
+ add_column :testings, :very_long_column_name_to_test_with, :string
753
+ add_index :testings, [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
754
+ end
755
+ }.new
756
+
757
+ error = assert_raises(StandardError) do
758
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
759
+ end
760
+ assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
761
+ end
762
+
763
+ # SQL Server has a different maximum index name length.
764
+ coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
765
+ def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
766
+ long_index_name = 'a' * (connection.index_name_length + 1)
767
+
768
+ migration = Class.new(ActiveRecord::Migration[7.0]) {
769
+ @@long_index_name = long_index_name
770
+ def migrate(x)
771
+ create_table :more_testings do |t|
772
+ t.integer :foo
773
+ t.integer :bar
774
+ t.integer :very_long_column_name_to_test_with
775
+ t.index [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
776
+ end
777
+ end
778
+ }.new
779
+
780
+ error = assert_raises(StandardError) do
781
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
782
+ end
783
+ assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
784
+ ensure
785
+ connection.drop_table :more_testings rescue nil
786
+ end
787
+ end
788
+ end
619
789
  end
620
790
 
621
791
  class CoreTest < ActiveRecord::TestCase
@@ -641,6 +811,7 @@ end
641
811
  module ActiveRecord
642
812
  # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
643
813
  module DatabaseTasksSetupper
814
+ undef_method :setup
644
815
  def setup
645
816
  @sqlserver_tasks =
646
817
  Class.new do
@@ -663,6 +834,7 @@ module ActiveRecord
663
834
  $stderr, @original_stderr = StringIO.new, $stderr
664
835
  end
665
836
 
837
+ undef_method :with_stubbed_new
666
838
  def with_stubbed_new
667
839
  ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
668
840
  yield
@@ -782,52 +954,16 @@ end
782
954
  class DefaultScopingTest < ActiveRecord::TestCase
783
955
  # We are not doing order duplicate removal anymore.
784
956
  coerce_tests! :test_order_in_default_scope_should_not_prevail
785
-
786
- # Use our escaped format in assertion.
787
- coerce_tests! :test_with_abstract_class_scope_should_be_executed_in_correct_context
788
- def test_with_abstract_class_scope_should_be_executed_in_correct_context_coerced
789
- vegetarian_pattern, gender_pattern = [/[lions].[is_vegetarian]/, /[lions].[gender]/]
790
- assert_match vegetarian_pattern, Lion.all.to_sql
791
- assert_match gender_pattern, Lion.female.to_sql
792
- end
793
- end
794
-
795
- require "models/post"
796
- require "models/subscriber"
797
- class EachTest < ActiveRecord::TestCase
798
- # Quoting in tests does not cope with bracket quoting.
799
- coerce_tests! :test_find_in_batches_should_quote_batch_order
800
- def test_find_in_batches_should_quote_batch_order_coerced
801
- Post.connection
802
- assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
803
- Post.find_in_batches(:batch_size => 1) do |batch|
804
- assert_kind_of Array, batch
805
- assert_kind_of Post, batch.first
806
- end
807
- end
808
- end
809
-
810
- # Quoting in tests does not cope with bracket quoting.
811
- coerce_tests! :test_in_batches_should_quote_batch_order
812
- def test_in_batches_should_quote_batch_order_coerced
813
- Post.connection
814
- assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
815
- Post.in_batches(of: 1) do |relation|
816
- assert_kind_of ActiveRecord::Relation, relation
817
- assert_kind_of Post, relation.first
818
- end
819
- end
820
- end
821
957
  end
822
958
 
823
959
  class EagerAssociationTest < ActiveRecord::TestCase
824
- # Use LEN() vs length() function.
960
+ # Use LEN() instead of LENGTH() function.
825
961
  coerce_tests! :test_count_with_include
826
962
  def test_count_with_include_coerced
827
963
  assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
828
964
  end
829
965
 
830
- # Use TOP (1) in scope vs limit 1.
966
+ # The raw SQL in the scope uses `limit 1`.
831
967
  coerce_tests! %r{including association based on sql condition and no database column}
832
968
  end
833
969
 
@@ -841,20 +977,12 @@ class FinderTest < ActiveRecord::TestCase
841
977
  coerce_tests! %r{doesn't have implicit ordering},
842
978
  :test_find_doesnt_have_implicit_ordering
843
979
 
844
- # Square brackets around column name
845
- coerce_tests! :test_exists_does_not_select_columns_without_alias
846
- def test_exists_does_not_select_columns_without_alias_coerced
847
- assert_sql(/SELECT\s+1 AS one FROM \[topics\].*OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) do
848
- Topic.exists?
849
- end
850
- end
851
-
852
980
  # Assert SQL Server limit implementation
853
981
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
854
982
  def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
855
- assert_sql(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
856
- assert_sql(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 2/) { Topic.first(2).entries }
857
- assert_sql(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 5/) { Topic.last(5).entries }
983
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
984
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 2/) { Topic.first(2).entries }
985
+ assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 5/) { Topic.last(5).entries }
858
986
  end
859
987
 
860
988
  # This fails only when run in the full test suite task. Just taking it out of the mix.
@@ -885,7 +1013,7 @@ class FinderTest < ActiveRecord::TestCase
885
1013
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
886
1014
  coerce_tests! :test_include_on_unloaded_relation_with_match
887
1015
  def test_include_on_unloaded_relation_with_match_coerced
888
- assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1016
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
889
1017
  assert_equal true, Customer.where(name: "David").include?(customers(:david))
890
1018
  end
891
1019
  end
@@ -893,7 +1021,7 @@ class FinderTest < ActiveRecord::TestCase
893
1021
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
894
1022
  coerce_tests! :test_include_on_unloaded_relation_without_match
895
1023
  def test_include_on_unloaded_relation_without_match_coerced
896
- assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1024
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
897
1025
  assert_equal false, Customer.where(name: "David").include?(customers(:mary))
898
1026
  end
899
1027
  end
@@ -901,7 +1029,7 @@ class FinderTest < ActiveRecord::TestCase
901
1029
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
902
1030
  coerce_tests! :test_member_on_unloaded_relation_with_match
903
1031
  def test_member_on_unloaded_relation_with_match_coerced
904
- assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1032
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
905
1033
  assert_equal true, Customer.where(name: "David").member?(customers(:david))
906
1034
  end
907
1035
  end
@@ -909,7 +1037,7 @@ class FinderTest < ActiveRecord::TestCase
909
1037
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
910
1038
  coerce_tests! :test_member_on_unloaded_relation_without_match
911
1039
  def test_member_on_unloaded_relation_without_match_coerced
912
- assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
1040
+ assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
913
1041
  assert_equal false, Customer.where(name: "David").member?(customers(:mary))
914
1042
  end
915
1043
  end
@@ -923,8 +1051,8 @@ class FinderTest < ActiveRecord::TestCase
923
1051
  assert_equal topics(:fifth), Topic.first
924
1052
  assert_equal topics(:third), Topic.last
925
1053
 
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) {
1054
+ c = Topic.lease_connection
1055
+ 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) {
928
1056
  Topic.last
929
1057
  }
930
1058
  ensure
@@ -937,8 +1065,8 @@ class FinderTest < ActiveRecord::TestCase
937
1065
  old_implicit_order_column = Topic.implicit_order_column
938
1066
  Topic.implicit_order_column = "id"
939
1067
 
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) {
1068
+ c = Topic.lease_connection
1069
+ 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) {
942
1070
  Topic.last
943
1071
  }
944
1072
  ensure
@@ -951,15 +1079,101 @@ class FinderTest < ActiveRecord::TestCase
951
1079
  old_implicit_order_column = NonPrimaryKey.implicit_order_column
952
1080
  NonPrimaryKey.implicit_order_column = "created_at"
953
1081
 
954
- c = NonPrimaryKey.connection
1082
+ c = NonPrimaryKey.lease_connection
955
1083
 
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) {
1084
+ 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) {
957
1085
  NonPrimaryKey.last
958
1086
  }
959
1087
  ensure
960
1088
  NonPrimaryKey.implicit_order_column = old_implicit_order_column
961
1089
  end
962
1090
 
1091
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1092
+ coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
1093
+ def test_member_on_unloaded_relation_with_composite_primary_key_coerced
1094
+ assert_queries_match(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1095
+ book = cpk_books(:cpk_great_author_first_book)
1096
+ assert Cpk::Book.where(title: "The first book").member?(book)
1097
+ end
1098
+ end
1099
+
1100
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1101
+ coerce_tests! :test_implicit_order_column_prepends_query_constraints
1102
+ def test_implicit_order_column_prepends_query_constraints_coerced
1103
+ c = ClothingItem.lease_connection
1104
+ ClothingItem.implicit_order_column = "description"
1105
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1106
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1107
+ quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1108
+
1109
+ 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
1110
+ assert_kind_of ClothingItem, ClothingItem.first
1111
+ end
1112
+ ensure
1113
+ ClothingItem.implicit_order_column = nil
1114
+ end
1115
+
1116
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1117
+ coerce_tests! %r{#last for a model with composite query constraints}
1118
+ test "#last for a model with composite query constraints coerced" do
1119
+ c = ClothingItem.lease_connection
1120
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1121
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1122
+
1123
+ assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1124
+ assert_kind_of ClothingItem, ClothingItem.last
1125
+ end
1126
+ end
1127
+
1128
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1129
+ coerce_tests! %r{#first for a model with composite query constraints}
1130
+ test "#first for a model with composite query constraints coerced" do
1131
+ c = ClothingItem.lease_connection
1132
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1133
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1134
+
1135
+ assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1136
+ assert_kind_of ClothingItem, ClothingItem.first
1137
+ end
1138
+ end
1139
+
1140
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1141
+ coerce_tests! :test_implicit_order_column_reorders_query_constraints
1142
+ def test_implicit_order_column_reorders_query_constraints_coerced
1143
+ c = ClothingItem.lease_connection
1144
+ ClothingItem.implicit_order_column = "color"
1145
+ quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1146
+ quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1147
+
1148
+ assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
1149
+ assert_kind_of ClothingItem, ClothingItem.first
1150
+ end
1151
+ ensure
1152
+ ClothingItem.implicit_order_column = nil
1153
+ end
1154
+
1155
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1156
+ coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
1157
+ def test_include_on_unloaded_relation_with_composite_primary_key_coerced
1158
+ assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
1159
+ book = cpk_books(:cpk_great_author_first_book)
1160
+ assert Cpk::Book.where(title: "The first book").include?(book)
1161
+ end
1162
+ end
1163
+
1164
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1165
+ coerce_tests! :test_nth_to_last_with_order_uses_limit
1166
+ def test_nth_to_last_with_order_uses_limit_coerced
1167
+ c = Topic.lease_connection
1168
+ 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
1169
+ Topic.second_to_last
1170
+ end
1171
+
1172
+ 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
1173
+ Topic.order(:updated_at).second_to_last
1174
+ end
1175
+ end
1176
+
963
1177
  # SQL Server is unable to use aliased SELECT in the HAVING clause.
964
1178
  coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
965
1179
  end
@@ -967,7 +1181,7 @@ end
967
1181
  module ActiveRecord
968
1182
  class Migration
969
1183
  class ForeignKeyTest < ActiveRecord::TestCase
970
- # We do not support :restrict.
1184
+ # SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
971
1185
  coerce_tests! :test_add_on_delete_restrict_foreign_key
972
1186
  def test_add_on_delete_restrict_foreign_key_coerced
973
1187
  assert_raises ArgumentError do
@@ -977,6 +1191,19 @@ module ActiveRecord
977
1191
  @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :restrict
978
1192
  end
979
1193
  end
1194
+
1195
+ # Error message depends on the database adapter.
1196
+ coerce_tests! :test_add_foreign_key_with_if_not_exists_not_set
1197
+ def test_add_foreign_key_with_if_not_exists_not_set_coerced
1198
+ @connection.add_foreign_key :astronauts, :rockets
1199
+ assert_equal 1, @connection.foreign_keys("astronauts").size
1200
+
1201
+ error = assert_raises do
1202
+ @connection.add_foreign_key :astronauts, :rockets
1203
+ end
1204
+
1205
+ assert_match(/TinyTds::Error: There is already an object named '.*' in the database/, error.message)
1206
+ end
980
1207
  end
981
1208
  end
982
1209
  end
@@ -990,7 +1217,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
990
1217
  def test_has_one_coerced
991
1218
  firm = companies(:first_firm)
992
1219
  first_account = Account.find(1)
993
- assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1220
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
994
1221
  assert_equal first_account, firm.account
995
1222
  assert_equal first_account.credit_limit, firm.account.credit_limit
996
1223
  end
@@ -1002,33 +1229,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
1002
1229
  coerce_tests! :test_has_one_through_executes_limited_query
1003
1230
  def test_has_one_through_executes_limited_query_coerced
1004
1231
  boring_club = clubs(:boring_club)
1005
- assert_sql(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1232
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
1006
1233
  assert_equal boring_club, @member.general_club
1007
1234
  end
1008
1235
  end
1009
1236
  end
1010
1237
 
1011
- require "models/company"
1012
- class InheritanceTest < ActiveRecord::TestCase
1013
- # Rails test required inserting to a identity column.
1014
- coerce_tests! :test_a_bad_type_column
1015
- def test_a_bad_type_column_coerced
1016
- Company.connection.with_identity_insert_enabled("companies") do
1017
- Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
1018
- end
1019
- assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
1020
- end
1021
-
1022
- # Use Square brackets around column name
1023
- coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
1024
- def test_eager_load_belongs_to_primary_key_quoting_coerced
1025
- Account.connection
1026
- assert_sql(/\[companies\]\.\[id\] = @0.* @0 = 1/) do
1027
- Account.all.merge!(:includes => :firm).find(1)
1028
- end
1029
- end
1030
- end
1031
-
1032
1238
  class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1033
1239
  # Uses || operator in SQL. Just trust core gets value out of this test.
1034
1240
  coerce_tests! :test_does_not_override_select
@@ -1073,6 +1279,9 @@ class PersistenceTest < ActiveRecord::TestCase
1073
1279
  assert_not_predicate topic, :approved?
1074
1280
  assert_equal "The First Topic", topic.title
1075
1281
  end
1282
+
1283
+ # In SQL Server it's not possible to set the primary key column using a trigger and to get it then to return.
1284
+ coerce_tests! :test_model_with_no_auto_populated_fields_still_returns_primary_key_after_insert
1076
1285
  end
1077
1286
 
1078
1287
  require "models/author"
@@ -1084,12 +1293,46 @@ class UpdateAllTest < ActiveRecord::TestCase
1084
1293
  _(david.id).must_equal 1
1085
1294
  _(mary.id).must_equal 2
1086
1295
  _(david.name).wont_equal mary.name
1087
- assert_sql(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
1296
+ assert_queries_match(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
1088
1297
  Author.where("[id] > 1").order(:id).update_all(name: "Test")
1089
1298
  end
1090
1299
  _(david.reload.name).must_equal "David"
1091
1300
  _(mary.reload.name).must_equal "Test"
1092
1301
  end
1302
+
1303
+ # SELECT columns must be in the GROUP clause.
1304
+ coerce_tests! :test_update_all_with_group_by
1305
+ def test_update_all_with_group_by_coerced
1306
+ minimum_comments_count = 2
1307
+
1308
+ Post.most_commented(minimum_comments_count).update_all(title: "ig")
1309
+ posts = Post.select(:id, :title).group(:title).most_commented(minimum_comments_count).all.to_a
1310
+
1311
+ assert_operator posts.length, :>, 0
1312
+ assert posts.all? { |post| post.comments.length >= minimum_comments_count }
1313
+ assert posts.all? { |post| "ig" == post.title }
1314
+
1315
+ post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
1316
+ assert_not_equal "ig", post.title
1317
+ end
1318
+ end
1319
+
1320
+ class DeleteAllTest < ActiveRecord::TestCase
1321
+ # SELECT columns must be in the GROUP clause.
1322
+ coerce_tests! :test_delete_all_with_group_by_and_having
1323
+ def test_delete_all_with_group_by_and_having_coerced
1324
+ minimum_comments_count = 2
1325
+ posts_to_be_deleted = Post.select(:id).most_commented(minimum_comments_count).all.to_a
1326
+ assert_operator posts_to_be_deleted.length, :>, 0
1327
+
1328
+ assert_difference("Post.count", -posts_to_be_deleted.length) do
1329
+ Post.most_commented(minimum_comments_count).delete_all
1330
+ end
1331
+
1332
+ posts_to_be_deleted.each do |deleted_post|
1333
+ assert_raise(ActiveRecord::RecordNotFound) { deleted_post.reload }
1334
+ end
1335
+ end
1093
1336
  end
1094
1337
 
1095
1338
  require "models/topic"
@@ -1109,65 +1352,27 @@ module ActiveRecord
1109
1352
  end
1110
1353
  end
1111
1354
 
1112
- class PrimaryKeysTest < ActiveRecord::TestCase
1113
- # SQL Server does not have query for release_savepoint
1114
- coerce_tests! :test_create_without_primary_key_no_extra_query
1115
- def test_create_without_primary_key_no_extra_query_coerced
1116
- klass = Class.new(ActiveRecord::Base) do
1117
- self.table_name = "dashboards"
1118
- end
1119
- klass.create! # warmup schema cache
1120
- assert_queries(2, ignore_none: true) { klass.create! }
1121
- end
1122
- end
1123
-
1124
1355
  require "models/task"
1125
1356
  class QueryCacheTest < ActiveRecord::TestCase
1126
1357
  # SQL Server adapter not in list of supported adapters in original test.
1127
1358
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
1128
1359
  def test_cache_does_not_wrap_results_in_arrays_coerced
1129
1360
  Task.cache do
1130
- assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
1131
- end
1132
- end
1133
-
1134
- # Same as original test except that we expect one query to be performed to retrieve the table's primary key.
1135
- # When we generate the SQL for the `find` it includes ordering on the primary key. If we reset the column
1136
- # information then the primary key needs to be retrieved from the database again to generate the SQL causing the
1137
- # original test's `assert_no_queries` assertion to fail. Assert that the query was to get the primary key.
1138
- coerce_tests! :test_query_cached_even_when_types_are_reset
1139
- def test_query_cached_even_when_types_are_reset_coerced
1140
- Task.cache do
1141
- # Warm the cache
1142
- Task.find(1)
1143
-
1144
- # Preload the type cache again (so we don't have those queries issued during our assertions)
1145
- Task.connection.send(:reload_type_map)
1146
-
1147
- # Clear places where type information is cached
1148
- Task.reset_column_information
1149
- Task.initialize_find_by_cache
1150
- Task.define_attribute_methods
1151
-
1152
- assert_queries(1, ignore_none: true) do
1153
- Task.find(1)
1154
- end
1155
-
1156
- assert_includes ActiveRecord::SQLCounter.log_all.first, "TC.CONSTRAINT_TYPE = N''PRIMARY KEY''"
1361
+ assert_equal 2, Task.lease_connection.select_value("SELECT count(*) AS count_all FROM tasks")
1157
1362
  end
1158
1363
  end
1159
1364
  end
1160
1365
 
1161
1366
  require "models/post"
1162
1367
  class RelationTest < ActiveRecord::TestCase
1163
- # Use LEN vs LENGTH function.
1368
+ # Use LEN() instead of LENGTH() function.
1164
1369
  coerce_tests! :test_reverse_order_with_function
1165
1370
  def test_reverse_order_with_function_coerced
1166
1371
  topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
1167
1372
  assert_equal topics(:second).title, topics.first.title
1168
1373
  end
1169
1374
 
1170
- # Use LEN vs LENGTH function.
1375
+ # Use LEN() instead of LENGTH() function.
1171
1376
  coerce_tests! :test_reverse_order_with_function_other_predicates
1172
1377
  def test_reverse_order_with_function_other_predicates_coerced
1173
1378
  topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
@@ -1185,24 +1390,19 @@ class RelationTest < ActiveRecord::TestCase
1185
1390
  sql_log = capture_sql do
1186
1391
  assert Post.order(:title).reorder(nil).take
1187
1392
  end
1188
- assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1393
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1189
1394
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1190
1395
  end
1191
1396
 
1192
1397
  # We have implicit ordering, via FETCH.
1193
1398
  coerce_tests! :test_reorder_with_first
1194
1399
  def test_reorder_with_first_coerced
1400
+ post = nil
1195
1401
  sql_log = capture_sql do
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
1402
+ post = Post.order(:title).reorder(nil).first
1204
1403
  end
1205
- assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1404
+ assert_equal posts(:welcome), post
1405
+ assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1206
1406
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1207
1407
  end
1208
1408
 
@@ -1212,22 +1412,55 @@ class RelationTest < ActiveRecord::TestCase
1212
1412
  # We are not doing order duplicate removal anymore.
1213
1413
  coerce_tests! :test_default_scope_order_with_scope_order
1214
1414
 
1215
- # Leave it up to users to format selects/functions so HAVING works correctly.
1415
+ # Order column must be in the GROUP clause.
1216
1416
  coerce_tests! :test_multiple_where_and_having_clauses
1417
+ def test_multiple_where_and_having_clauses_coerced
1418
+ post = Post.first
1419
+ having_then_where = Post.having(id: post.id).where(title: post.title)
1420
+ .having(id: post.id).where(title: post.title).group(:id).select(:id)
1421
+
1422
+ assert_equal [post], having_then_where
1423
+ end
1424
+
1425
+ # Order column must be in the GROUP clause.
1217
1426
  coerce_tests! :test_having_with_binds_for_both_where_and_having
1427
+ def test_having_with_binds_for_both_where_and_having
1428
+ post = Post.first
1429
+ having_then_where = Post.having(id: post.id).where(title: post.title).group(:id).select(:id)
1430
+ where_then_having = Post.where(title: post.title).having(id: post.id).group(:id).select(:id)
1431
+
1432
+ assert_equal [post], having_then_where
1433
+ assert_equal [post], where_then_having
1434
+ end
1218
1435
 
1219
1436
  # Find any limit via our expression.
1220
1437
  coerce_tests! %r{relations don't load all records in #inspect}
1221
1438
  def test_relations_dont_load_all_records_in_inspect_coerced
1222
- assert_sql(/NEXT @0 ROWS.*@0 = \d+/) do
1439
+ assert_queries_match(/NEXT @0 ROWS.*@0 = \d+/) do
1223
1440
  Post.all.inspect
1224
1441
  end
1225
1442
  end
1226
1443
 
1227
- # I wanted to add `.order("author_id")` scope to avoid error: Column "posts.id" is invalid in the ORDER BY
1228
- # However, this pull request on Rails core drops order on exists relation. https://github.com/rails/rails/pull/28699
1229
- # so we are skipping all together.
1444
+ # Find any limit via our expression.
1445
+ coerce_tests! %r{relations don't load all records in #pretty_print}
1446
+ def test_relations_dont_load_all_records_in_pretty_print_coerced
1447
+ assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
1448
+ PP.pp Post.all, StringIO.new # avoid outputting.
1449
+ end
1450
+ end
1451
+
1452
+ # Order column must be in the GROUP clause.
1230
1453
  coerce_tests! :test_empty_complex_chained_relations
1454
+ def test_empty_complex_chained_relations_coerced
1455
+ posts = Post.select("comments_count").where("id is not null").group("author_id", "id").where("legacy_comments_count > 0")
1456
+
1457
+ assert_queries_count(1) { assert_equal false, posts.empty? }
1458
+ assert_not_predicate posts, :loaded?
1459
+
1460
+ no_posts = posts.where(title: "")
1461
+ assert_queries_count(1) { assert_equal true, no_posts.empty? }
1462
+ assert_not_predicate no_posts, :loaded?
1463
+ end
1231
1464
 
1232
1465
  # Can't apply offset without ORDER
1233
1466
  coerce_tests! %r{using a custom table affects the wheres}
@@ -1245,7 +1478,7 @@ class RelationTest < ActiveRecord::TestCase
1245
1478
  assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
1246
1479
  end
1247
1480
 
1248
- # Use LEN() vs length() function.
1481
+ # Use LEN() instead of LENGTH() function.
1249
1482
  coerce_tests! :test_reverse_arel_assoc_order_with_function
1250
1483
  def test_reverse_arel_assoc_order_with_function_coerced
1251
1484
  topics = Topic.order(Arel.sql("LEN(title)") => :asc).reverse_order
@@ -1260,21 +1493,11 @@ module ActiveRecord
1260
1493
 
1261
1494
  coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1262
1495
  def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1263
- escaped_table = Post.connection.quote_table_name("posts")
1496
+ escaped_table = Post.lease_connection.quote_table_name("posts")
1264
1497
  expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1265
1498
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1266
1499
  assert_equal expected, query
1267
1500
  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
1501
  end
1279
1502
  end
1280
1503
 
@@ -1293,17 +1516,44 @@ class SanitizeTest < ActiveRecord::TestCase
1293
1516
  }
1294
1517
  end
1295
1518
 
1296
- assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1519
+ assert_queries_match(/LIKE @0/) do
1297
1520
  searchable_post.search_as_method("20% _reduction_!").to_a
1298
1521
  end
1299
1522
 
1300
- assert_sql(/LIKE N'20!% !_reduction!_!!'/) do
1523
+ assert_queries_match(/LIKE @0/) do
1301
1524
  searchable_post.search_as_scope("20% _reduction_!").to_a
1302
1525
  end
1303
1526
  end
1527
+
1528
+ # Use nvarchar string (N'') in assert
1529
+ coerce_tests! :test_named_bind_with_literal_colons
1530
+ def test_named_bind_with_literal_colons_coerced
1531
+ assert_equal "TO_TIMESTAMP(N'2017/08/02 10:59:00', 'YYYY/MM/DD HH12:MI:SS')", bind("TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "2017/08/02 10:59:00")
1532
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
1533
+ end
1304
1534
  end
1305
1535
 
1306
1536
  class SchemaDumperTest < ActiveRecord::TestCase
1537
+ # Use nvarchar string (N'') in assert
1538
+ coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1539
+ def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1540
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
1541
+ versions.shuffle.each do |v|
1542
+ @schema_migration.create_version(v)
1543
+ end
1544
+
1545
+ schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
1546
+ expected = <<~STR
1547
+ INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
1548
+ (N'20100301010101'),
1549
+ (N'20100201010101'),
1550
+ (N'20100101010101');
1551
+ STR
1552
+ assert_equal expected.strip, schema_info
1553
+ ensure
1554
+ @schema_migration.delete_all_versions
1555
+ end
1556
+
1307
1557
  # We have precision to 38.
1308
1558
  coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
1309
1559
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
@@ -1311,7 +1561,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
1311
1561
  assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 38}, output
1312
1562
  end
1313
1563
 
1314
- # This is a poorly written test and really does not catch the bottom'ness it is meant too. Ours throw it off.
1564
+ # This is a poorly written test and really does not catch the bottom'ness it is meant to. Ours throw it off.
1315
1565
  coerce_tests! :test_foreign_keys_are_dumped_at_the_bottom_to_circumvent_dependency_issues
1316
1566
 
1317
1567
  # Fall through false positive with no filter.
@@ -1327,11 +1577,50 @@ class SchemaDumperTest < ActiveRecord::TestCase
1327
1577
  output = dump_all_table_schema([/^[^n]/])
1328
1578
  assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
1329
1579
  end
1580
+
1581
+ # Tests are not about a specific adapter.
1582
+ coerce_tests! :test_do_not_dump_foreign_keys_when_bypassed_by_config
1583
+
1584
+ # SQL Server formats the check constraint expression differently.
1585
+ coerce_tests! :test_schema_dumps_check_constraints
1586
+ def test_schema_dumps_check_constraints_coerced
1587
+ constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
1588
+ assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
1589
+ end
1330
1590
  end
1331
1591
 
1332
1592
  class SchemaDumperDefaultsTest < ActiveRecord::TestCase
1333
1593
  # These date formats do not match ours. We got these covered in our dumper tests.
1334
1594
  coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
1595
+
1596
+ # SQL Server uses different method to generate a UUID than Rails test uses. Reimplemented the
1597
+ # test in 'SchemaDumperDefaultsCoerceTest'.
1598
+ coerce_tests! :test_schema_dump_with_text_column
1599
+ end
1600
+
1601
+ class SchemaDumperDefaultsCoerceTest < ActiveRecord::TestCase
1602
+ include SchemaDumpingHelper
1603
+
1604
+ setup do
1605
+ @connection = ActiveRecord::Base.lease_connection
1606
+ @connection.create_table :dump_defaults, force: true do |t|
1607
+ t.string :string_with_default, default: "Hello!"
1608
+ t.date :date_with_default, default: "2014-06-05"
1609
+ t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
1610
+ t.time :time_with_default, default: "07:17:04"
1611
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1612
+
1613
+ t.text :text_with_default, default: "John' Doe"
1614
+ t.text :uuid, default: -> { "newid()" }
1615
+ end
1616
+ end
1617
+
1618
+ def test_schema_dump_with_text_column_coerced
1619
+ output = dump_table_schema("dump_defaults")
1620
+
1621
+ assert_match %r{t\.text\s+"text_with_default",.*?default: "John' Doe"}, output
1622
+ assert_match %r{t\.text\s+"uuid",.*?default: -> \{ "newid\(\)" \}}, output
1623
+ end
1335
1624
  end
1336
1625
 
1337
1626
  class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
@@ -1341,14 +1630,92 @@ end
1341
1630
 
1342
1631
  require "models/topic"
1343
1632
  class TransactionTest < ActiveRecord::TestCase
1344
- # SQL Server does not have query for release_savepoint
1633
+ # SQL Server does not have query for release_savepoint.
1345
1634
  coerce_tests! :test_releasing_named_savepoints
1346
1635
  def test_releasing_named_savepoints_coerced
1347
1636
  Topic.transaction do
1348
- Topic.connection.create_savepoint("another")
1349
- Topic.connection.release_savepoint("another")
1350
- # We do not have a notion of releasing, so this does nothing vs raise an error.
1351
- Topic.connection.release_savepoint("another")
1637
+ Topic.lease_connection.materialize_transactions
1638
+
1639
+ Topic.lease_connection.create_savepoint("another")
1640
+ Topic.lease_connection.release_savepoint("another")
1641
+
1642
+ # We do not have a notion of releasing, so this does nothing and doesn't raise an error.
1643
+ assert_nothing_raised do
1644
+ Topic.lease_connection.release_savepoint("another")
1645
+ end
1646
+ end
1647
+ end
1648
+
1649
+ # SQL Server does not have query for release_savepoint.
1650
+ coerce_tests! :test_nested_transactions_after_disable_lazy_transactions
1651
+ def test_nested_transactions_after_disable_lazy_transactions_coerced
1652
+ Topic.lease_connection.disable_lazy_transactions!
1653
+
1654
+ actual_queries = capture_sql(include_schema: true) do
1655
+ # RealTransaction (begin..commit)
1656
+ Topic.transaction(requires_new: true) do
1657
+ # ResetParentTransaction (no queries)
1658
+ Topic.transaction(requires_new: true) do
1659
+ Topic.delete_all
1660
+ # SavepointTransaction (savepoint..release)
1661
+ Topic.transaction(requires_new: true) do
1662
+ # ResetParentTransaction (no queries)
1663
+ Topic.transaction(requires_new: true) do
1664
+ # no-op
1665
+ end
1666
+ end
1667
+ end
1668
+ Topic.delete_all
1669
+ end
1670
+ end
1671
+
1672
+ expected_queries = [
1673
+ /BEGIN/i,
1674
+ /DELETE/i,
1675
+ /^SAVE TRANSACTION/i,
1676
+ /DELETE/i,
1677
+ /COMMIT/i,
1678
+ ]
1679
+
1680
+ assert_equal expected_queries.size, actual_queries.size
1681
+ expected_queries.zip(actual_queries) do |expected, actual|
1682
+ assert_match expected, actual
1683
+ end
1684
+ end
1685
+
1686
+ # SQL Server does not have query for release_savepoint.
1687
+ coerce_tests! :test_nested_transactions_skip_excess_savepoints
1688
+ def test_nested_transactions_skip_excess_savepoints_coerced
1689
+ actual_queries = capture_sql(include_schema: true) do
1690
+ # RealTransaction (begin..commit)
1691
+ Topic.transaction(requires_new: true) do
1692
+ # ResetParentTransaction (no queries)
1693
+ Topic.transaction(requires_new: true) do
1694
+ Topic.delete_all
1695
+ # SavepointTransaction (savepoint..release)
1696
+ Topic.transaction(requires_new: true) do
1697
+ # ResetParentTransaction (no queries)
1698
+ Topic.transaction(requires_new: true) do
1699
+ Topic.delete_all
1700
+ end
1701
+ end
1702
+ end
1703
+ Topic.delete_all
1704
+ end
1705
+ end
1706
+
1707
+ expected_queries = [
1708
+ /BEGIN/i,
1709
+ /DELETE/i,
1710
+ /^SAVE TRANSACTION/i,
1711
+ /DELETE/i,
1712
+ /DELETE/i,
1713
+ /COMMIT/i,
1714
+ ]
1715
+
1716
+ assert_equal expected_queries.size, actual_queries.size
1717
+ expected_queries.zip(actual_queries) do |expected, actual|
1718
+ assert_match expected, actual
1352
1719
  end
1353
1720
  end
1354
1721
  end
@@ -1402,7 +1769,8 @@ class YamlSerializationTest < ActiveRecord::TestCase
1402
1769
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1403
1770
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
1404
1771
  author = Author.select("authors.*, 5 as posts_count").first
1405
- dumped = YAML.load(YAML.dump(author))
1772
+ dumped_author = YAML.dump(author)
1773
+ dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
1406
1774
  assert_equal 5, author.posts_count
1407
1775
  assert_equal 5, dumped.posts_count
1408
1776
  end
@@ -1463,6 +1831,9 @@ class TimePrecisionTest < ActiveRecord::TestCase
1463
1831
 
1464
1832
  # SQL Server uses default precision for time.
1465
1833
  coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
1834
+
1835
+ # SQL Server accepts precision of 7 for time.
1836
+ coerce_tests! :test_invalid_time_precision_raises_error
1466
1837
  end
1467
1838
 
1468
1839
  class DefaultNumbersTest < ActiveRecord::TestCase
@@ -1478,8 +1849,8 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1478
1849
  coerce_tests! :test_default_negative_integer
1479
1850
  def test_default_negative_integer_coerced
1480
1851
  record = DefaultNumber.new
1481
- assert_equal -5, record.negative_integer
1482
- assert_equal -5, record.negative_integer_before_type_cast
1852
+ assert_equal (-5), record.negative_integer
1853
+ assert_equal (-5), record.negative_integer_before_type_cast
1483
1854
  end
1484
1855
 
1485
1856
  # We do better with native types and do not return strings for everything.
@@ -1517,12 +1888,12 @@ module ActiveRecord
1517
1888
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1518
1889
  coerce_tests! :test_statement_cache_values_differ
1519
1890
  def test_statement_cache_values_differ_coerced
1520
- Book.connection.remove_index(:books, column: [:author_id, :name])
1891
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1521
1892
 
1522
1893
  original_test_statement_cache_values_differ
1523
1894
  ensure
1524
1895
  Book.where(author_id: nil, name: 'my book').delete_all
1525
- Book.connection.add_index(:books, [:author_id, :name], unique: true)
1896
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1526
1897
  end
1527
1898
  end
1528
1899
  end
@@ -1568,8 +1939,9 @@ module ActiveRecord
1568
1939
  end
1569
1940
 
1570
1941
  # We need to give the full path for this to work.
1942
+ undef_method :schema_dump_path
1571
1943
  def schema_dump_path
1572
- File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
1944
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1573
1945
  end
1574
1946
  end
1575
1947
  end
@@ -1580,7 +1952,7 @@ require "models/comment"
1580
1952
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1581
1953
  fixtures :posts
1582
1954
 
1583
- # Use LEN() vs length() function.
1955
+ # Use LEN() instead of LENGTH() function.
1584
1956
  coerce_tests! %r{order: always allows Arel}
1585
1957
  test "order: always allows Arel" do
1586
1958
  titles = Post.order(Arel.sql("len(title)")).pluck(:title)
@@ -1588,7 +1960,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1588
1960
  assert_not_empty titles
1589
1961
  end
1590
1962
 
1591
- # Use LEN() vs length() function.
1963
+ # Use LEN() instead of LENGTH() function.
1592
1964
  coerce_tests! %r{pluck: always allows Arel}
1593
1965
  test "pluck: always allows Arel" do
1594
1966
  excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
@@ -1597,7 +1969,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1597
1969
  assert_equal excepted_values, values
1598
1970
  end
1599
1971
 
1600
- # Use LEN() vs length() function.
1972
+ # Use LEN() instead of LENGTH() function.
1601
1973
  coerce_tests! %r{order: allows valid Array arguments}
1602
1974
  test "order: allows valid Array arguments" do
1603
1975
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
@@ -1607,6 +1979,27 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1607
1979
  assert_equal ids_expected, ids
1608
1980
  end
1609
1981
 
1982
+ # Use LEN() instead of LENGTH() function.
1983
+ coerce_tests! %r{order: allows nested functions}
1984
+ test "order: allows nested functions" do
1985
+ ids_expected = Post.order(Arel.sql("author_id, len(trim(title))")).pluck(:id)
1986
+
1987
+ # $DEBUG = true
1988
+ ids = Post.order("author_id, len(trim(title))").pluck(:id)
1989
+
1990
+ assert_equal ids_expected, ids
1991
+ end
1992
+
1993
+ # Use LEN() instead of LENGTH() function.
1994
+ coerce_tests! %r{pluck: allows nested functions}
1995
+ test "pluck: allows nested functions" do
1996
+ title_lengths_expected = Post.pluck(Arel.sql("len(trim(title))"))
1997
+
1998
+ title_lengths = Post.pluck("len(trim(title))")
1999
+
2000
+ assert_equal title_lengths_expected, title_lengths
2001
+ end
2002
+
1610
2003
  test "order: allows string column names that are quoted" do
1611
2004
  ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1612
2005
 
@@ -1670,6 +2063,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1670
2063
 
1671
2064
  assert_equal titles_expected, titles
1672
2065
  end
2066
+
2067
+ # Collation name should not be quoted. Hardcoded values for different adapters.
2068
+ coerce_tests! %r{order: allows valid arguments with COLLATE}
2069
+ test "order: allows valid arguments with COLLATE" do
2070
+ collation_name = "Latin1_General_CS_AS_WS"
2071
+
2072
+ ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2073
+
2074
+ ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2075
+
2076
+ assert_equal ids_expected, ids
2077
+ end
1673
2078
  end
1674
2079
 
1675
2080
  class ReservedWordTest < ActiveRecord::TestCase
@@ -1718,14 +2123,14 @@ class RelationMergingTest < ActiveRecord::TestCase
1718
2123
 
1719
2124
  non_mary_and_bob = Author.where.not(id: [mary, bob])
1720
2125
 
1721
- author_id = Author.connection.quote_table_name("authors.id")
1722
- assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
2126
+ author_id = Author.lease_connection.quote_table_name("authors.id")
2127
+ assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
1723
2128
  assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
1724
2129
  end
1725
2130
 
1726
2131
  only_david = Author.where("#{author_id} IN (?)", david)
1727
2132
 
1728
- assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
2133
+ assert_queries_match(/WHERE \(#{Regexp.escape(author_id)} IN \(@\d\)\)/) do
1729
2134
  assert_equal [david], only_david.merge(only_david)
1730
2135
  end
1731
2136
  end
@@ -1745,45 +2150,56 @@ class EnumTest < ActiveRecord::TestCase
1745
2150
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1746
2151
  coerce_tests! %r{enums are distinct per class}
1747
2152
  test "enums are distinct per class coerced" do
1748
- Book.connection.remove_index(:books, column: [:author_id, :name])
2153
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1749
2154
 
1750
2155
  send(:'original_enums are distinct per class')
1751
2156
  ensure
1752
2157
  Book.where(author_id: nil, name: nil).delete_all
1753
- Book.connection.add_index(:books, [:author_id, :name], unique: true)
2158
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1754
2159
  end
1755
2160
 
1756
2161
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1757
2162
  coerce_tests! %r{creating new objects with enum scopes}
1758
2163
  test "creating new objects with enum scopes coerced" do
1759
- Book.connection.remove_index(:books, column: [:author_id, :name])
2164
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1760
2165
 
1761
2166
  send(:'original_creating new objects with enum scopes')
1762
2167
  ensure
1763
2168
  Book.where(author_id: nil, name: nil).delete_all
1764
- Book.connection.add_index(:books, [:author_id, :name], unique: true)
2169
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1765
2170
  end
1766
2171
 
1767
2172
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1768
2173
  coerce_tests! %r{enums are inheritable}
1769
2174
  test "enums are inheritable coerced" do
1770
- Book.connection.remove_index(:books, column: [:author_id, :name])
2175
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1771
2176
 
1772
2177
  send(:'original_enums are inheritable')
1773
2178
  ensure
1774
2179
  Book.where(author_id: nil, name: nil).delete_all
1775
- Book.connection.add_index(:books, [:author_id, :name], unique: true)
2180
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1776
2181
  end
1777
2182
 
1778
2183
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
1779
2184
  coerce_tests! %r{declare multiple enums at a time}
1780
2185
  test "declare multiple enums at a time coerced" do
1781
- Book.connection.remove_index(:books, column: [:author_id, :name])
2186
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
1782
2187
 
1783
2188
  send(:'original_declare multiple enums at a time')
1784
2189
  ensure
1785
2190
  Book.where(author_id: nil, name: nil).delete_all
1786
- Book.connection.add_index(:books, [:author_id, :name], unique: true)
2191
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2192
+ end
2193
+
2194
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2195
+ coerce_tests! %r{serializable\? with large number label}
2196
+ test "serializable? with large number label coerced" do
2197
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2198
+
2199
+ send(:'original_serializable\? with large number label')
2200
+ ensure
2201
+ Book.where(author_id: nil, name: nil).delete_all
2202
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1787
2203
  end
1788
2204
  end
1789
2205
 
@@ -1796,18 +2212,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
1796
2212
  Task.cache { Task.insert({ starting: Time.now }) }
1797
2213
  end
1798
2214
 
1799
- assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1800
- Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1801
- end
1802
-
1803
- assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1804
- Task.cache { Task.insert!({ starting: Time.now }) }
1805
- end
1806
-
1807
- assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
1808
- Task.cache { Task.insert_all!([{ starting: Time.now }]) }
1809
- end
1810
-
1811
2215
  assert_raises(ArgumentError, /does not support upsert/) do
1812
2216
  Task.cache { Task.upsert({ starting: Time.now }) }
1813
2217
  end
@@ -1815,6 +2219,16 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
1815
2219
  assert_raises(ArgumentError, /does not support upsert/) do
1816
2220
  Task.cache { Task.upsert_all([{ starting: Time.now }]) }
1817
2221
  end
2222
+
2223
+ Task.cache do
2224
+ assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
2225
+ Task.insert_all!([ starting: Time.now ])
2226
+ end
2227
+
2228
+ assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
2229
+ Task.insert!({ starting: Time.now })
2230
+ end
2231
+ end
1818
2232
  end
1819
2233
  end
1820
2234
 
@@ -1836,7 +2250,7 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1836
2250
 
1837
2251
  # Perform test
1838
2252
  citation_count = Citation.count
1839
- assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
2253
+ assert_queries_match(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1840
2254
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1841
2255
  end
1842
2256
  end
@@ -1844,9 +2258,9 @@ end
1844
2258
 
1845
2259
  class LogSubscriberTest < ActiveRecord::TestCase
1846
2260
  # Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
1847
- coerce_tests! :test_vebose_query_logs
1848
- def test_vebose_query_logs_coerced
1849
- original_test_vebose_query_logs
2261
+ coerce_tests! :test_verbose_query_logs
2262
+ def test_verbose_query_logs_coerced
2263
+ original_test_verbose_query_logs
1850
2264
  end
1851
2265
 
1852
2266
  # Bindings logged slightly differently.
@@ -1858,69 +2272,19 @@ class LogSubscriberTest < ActiveRecord::TestCase
1858
2272
  end
1859
2273
  end
1860
2274
 
1861
- class ActiveRecordSchemaTest < ActiveRecord::TestCase
1862
- # Workaround for randomly failing test.
1863
- coerce_tests! :test_has_primary_key
1864
- def test_has_primary_key_coerced
1865
- @schema_migration.reset_column_information
1866
- original_test_has_primary_key
1867
- end
1868
- end
1869
-
1870
2275
  class ReloadModelsTest < ActiveRecord::TestCase
1871
2276
  # Skip test on Windows. The number of arguments passed to `IO.popen` in
1872
2277
  # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1873
2278
  coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1874
2279
  end
1875
2280
 
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
1914
- end
1915
- end
1916
- end
1917
-
1918
2281
  class MarshalSerializationTest < ActiveRecord::TestCase
1919
2282
  private
1920
2283
 
2284
+ undef_method :marshal_fixture_path
1921
2285
  def marshal_fixture_path(file_name)
1922
2286
  File.expand_path(
1923
- "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
2287
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.lease_connection.adapter_name}/#{file_name}.dump",
1924
2288
  ARTest::SQLServer.test_root_sqlserver
1925
2289
  )
1926
2290
  end
@@ -1952,31 +2316,56 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1952
2316
  end
1953
2317
  end
1954
2318
 
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
2319
+ class PreloaderTest < ActiveRecord::TestCase
2320
+ # Need to handle query parameters in SQL regex.
2321
+ coerce_tests! :test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
2322
+ def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute_coerced
2323
+ order = cpk_orders(:cpk_groceries_order_2)
2324
+ _shop_id, order_id = order.id
2325
+ order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
2326
+
2327
+ assert_not_empty order_agreements
2328
+ assert_equal order_agreements.sort, order.order_agreements.sort
2329
+
2330
+ loaded_order = nil
2331
+ sql = capture_sql do
2332
+ loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
1965
2333
  end
2334
+
2335
+ assert_equal 2, sql.size
2336
+ preload_sql = sql.last
2337
+
2338
+ c = Cpk::OrderAgreement.lease_connection
2339
+ order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
2340
+ order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
2341
+ expectation = /SELECT.*WHERE.* #{order_id_constraint}/
2342
+
2343
+ assert_match(expectation, preload_sql)
2344
+ assert_equal order_agreements.sort, loaded_order.order_agreements.sort
1966
2345
  end
1967
2346
 
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
2347
+ # Need to handle query parameters in SQL regex.
2348
+ coerce_tests! :test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
2349
+ def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute_coerced
2350
+ order_agreement = cpk_order_agreements(:order_agreement_three)
2351
+ order = cpk_orders(:cpk_groceries_order_2)
2352
+ assert_equal order, order_agreement.order
2353
+
2354
+ loaded_order_agreement = nil
2355
+ sql = capture_sql do
2356
+ loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
1979
2357
  end
2358
+
2359
+ assert_equal 2, sql.size
2360
+ preload_sql = sql.last
2361
+
2362
+ c = Cpk::Order.lease_connection
2363
+ order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
2364
+ order_constraint = /#{order_id} = @0.*@0 = \d+$/
2365
+ expectation = /SELECT.*WHERE.* #{order_constraint}/
2366
+
2367
+ assert_match(expectation, preload_sql)
2368
+ assert_equal order, loaded_order_agreement.order
1980
2369
  end
1981
2370
  end
1982
2371
 
@@ -1989,3 +2378,395 @@ class MultiDbMigratorTest < ActiveRecord::TestCase
1989
2378
  # Test fails on Windows AppVeyor CI for unknown reason.
1990
2379
  coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1991
2380
  end
2381
+
2382
+ require "models/book"
2383
+ class FieldOrderedValuesTest < ActiveRecord::TestCase
2384
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2385
+ coerce_tests! :test_in_order_of_with_enums_values
2386
+ def test_in_order_of_with_enums_values_coerced
2387
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2388
+
2389
+ original_test_in_order_of_with_enums_values
2390
+ ensure
2391
+ Book.where(author_id: nil, name: nil).delete_all
2392
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2393
+ end
2394
+
2395
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2396
+ coerce_tests! :test_in_order_of_with_string_column
2397
+ def test_in_order_of_with_string_column_coerced
2398
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2399
+
2400
+ original_test_in_order_of_with_string_column
2401
+ ensure
2402
+ Book.where(author_id: nil, name: nil).delete_all
2403
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2404
+ end
2405
+
2406
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2407
+ coerce_tests! :test_in_order_of_with_enums_keys
2408
+ def test_in_order_of_with_enums_keys_coerced
2409
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2410
+
2411
+ original_test_in_order_of_with_enums_keys
2412
+ ensure
2413
+ Book.where(author_id: nil, name: nil).delete_all
2414
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2415
+ end
2416
+
2417
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2418
+ coerce_tests! :test_in_order_of_with_nil
2419
+ def test_in_order_of_with_nil_coerced
2420
+ Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2421
+
2422
+ original_test_in_order_of_with_nil
2423
+ ensure
2424
+ Book.where(author_id: nil, name: nil).delete_all
2425
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2426
+ end
2427
+ end
2428
+
2429
+ require "models/dashboard"
2430
+ class QueryLogsTest < ActiveRecord::TestCase
2431
+ # SQL requires double single-quotes.
2432
+ coerce_tests! :test_sql_commenter_format
2433
+ def test_sql_commenter_format_coerced
2434
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2435
+ assert_queries_match(%r{/\*application=''active_record''\*/}) do
2436
+ Dashboard.first
2437
+ end
2438
+ end
2439
+
2440
+ # SQL requires double single-quotes.
2441
+ coerce_tests! :test_sqlcommenter_format_value
2442
+ def test_sqlcommenter_format_value_coerced
2443
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2444
+
2445
+ ActiveRecord::QueryLogs.tags = [
2446
+ :application,
2447
+ { tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
2448
+ ]
2449
+
2450
+ assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2451
+ Dashboard.first
2452
+ end
2453
+ end
2454
+
2455
+ # SQL requires double single-quotes.
2456
+ coerce_tests! :test_sqlcommenter_format_value_string_coercible
2457
+ def test_sqlcommenter_format_value_string_coercible_coerced
2458
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2459
+
2460
+ ActiveRecord::QueryLogs.tags = [
2461
+ :application,
2462
+ { custom_proc: -> { 1234 } },
2463
+ ]
2464
+
2465
+ assert_queries_match(%r{custom_proc=''1234''\*/}) do
2466
+ Dashboard.first
2467
+ end
2468
+ end
2469
+
2470
+ # SQL requires double single-quotes.
2471
+ coerce_tests! :test_sqlcommenter_format_allows_string_keys
2472
+ def test_sqlcommenter_format_allows_string_keys_coerced
2473
+ ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2474
+
2475
+ ActiveRecord::QueryLogs.tags = [
2476
+ :application,
2477
+ {
2478
+ "string" => "value",
2479
+ tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7",
2480
+ custom_proc: -> { "Joe's Shack" }
2481
+ },
2482
+ ]
2483
+
2484
+ assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
2485
+ Dashboard.first
2486
+ end
2487
+ end
2488
+
2489
+ # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2490
+ coerce_tests! :test_invalid_encoding_query
2491
+ def test_invalid_encoding_query_coerced
2492
+ ActiveRecord::QueryLogs.tags = [ :application ]
2493
+ assert_raises ActiveRecord::StatementInvalid do
2494
+ ActiveRecord::Base.lease_connection.execute "select 1 as '\xFF'"
2495
+ end
2496
+ end
2497
+ end
2498
+
2499
+ class InsertAllTest < ActiveRecord::TestCase
2500
+ # Same as original but using INSERTED.name as UPPER argument
2501
+ coerce_tests! :test_insert_all_returns_requested_sql_fields
2502
+ def test_insert_all_returns_requested_sql_fields_coerced
2503
+ skip unless supports_insert_returning?
2504
+
2505
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2506
+ assert_equal %w[ REWORK ], result.pluck("name")
2507
+ end
2508
+ end
2509
+
2510
+ module ActiveRecord
2511
+ class Migration
2512
+ class InvalidOptionsTest < ActiveRecord::TestCase
2513
+ # Include the additional SQL Server migration options.
2514
+ undef_method :invalid_add_column_option_exception_message
2515
+ def invalid_add_column_option_exception_message(key)
2516
+ default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2517
+ default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2518
+
2519
+ "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2520
+ end
2521
+ end
2522
+ end
2523
+ end
2524
+
2525
+ # SQL Server does not support upsert. Removed dependency on `insert_all` that uses upsert.
2526
+ class ActiveRecord::Encryption::ConcurrencyTest < ActiveRecord::EncryptionTestCase
2527
+ undef_method :thread_encrypting_and_decrypting
2528
+ def thread_encrypting_and_decrypting(thread_label)
2529
+ posts = 100.times.collect { |index| EncryptedPost.create! title: "Article #{index} (#{thread_label})", body: "Body #{index} (#{thread_label})" }
2530
+
2531
+ Thread.new do
2532
+ posts.each.with_index do |article, index|
2533
+ assert_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2534
+ article.decrypt
2535
+ assert_not_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
2536
+ end
2537
+ end
2538
+ end
2539
+ end
2540
+
2541
+ # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2542
+ # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2543
+ class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2544
+ private
2545
+ undef_method :serializer
2546
+ def serializer
2547
+ @serializer ||= ::MessagePack::Factory.new.tap do |factory|
2548
+ ActiveRecord::MessagePack::Extensions.install(factory)
2549
+ ActiveSupport::MessagePack::Extensions.install(factory)
2550
+ ActiveSupport::MessagePack::Extensions.install_unregistered_type_fallback(factory)
2551
+ end
2552
+ end
2553
+ end
2554
+
2555
+ class StoreTest < ActiveRecord::TestCase
2556
+ # Set the attribute as JSON type for the `StoreTest#saved changes tracking for accessors with json column` test.
2557
+ Admin::User.attribute :json_options, ActiveRecord::Type::SQLServer::Json.new
2558
+ end
2559
+
2560
+ class TestDatabasesTest < ActiveRecord::TestCase
2561
+ # Tests are not about a specific adapter.
2562
+ coerce_all_tests!
2563
+ end
2564
+
2565
+ module ActiveRecord
2566
+ module ConnectionAdapters
2567
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2568
+ # Tests are not about a specific adapter.
2569
+ coerce_all_tests!
2570
+ end
2571
+ end
2572
+ end
2573
+
2574
+ module ActiveRecord
2575
+ module ConnectionAdapters
2576
+ class ConnectionSwappingNestedTest < ActiveRecord::TestCase
2577
+ # Tests are not about a specific adapter.
2578
+ coerce_all_tests!
2579
+ end
2580
+ end
2581
+ end
2582
+
2583
+ module ActiveRecord
2584
+ module ConnectionAdapters
2585
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
2586
+ # Tests are not about a specific adapter.
2587
+ coerce_tests! :test_switching_connections_via_handler
2588
+ end
2589
+ end
2590
+ end
2591
+
2592
+ module ActiveRecord
2593
+ module ConnectionAdapters
2594
+ class ConnectionHandlersMultiPoolConfigTest < ActiveRecord::TestCase
2595
+ # Tests are not about a specific adapter.
2596
+ coerce_all_tests!
2597
+ end
2598
+ end
2599
+ end
2600
+
2601
+ module ActiveRecord
2602
+ class Migration
2603
+ class CheckConstraintTest < ActiveRecord::TestCase
2604
+ # SQL Server formats the check constraint expression differently.
2605
+ coerce_tests! :test_check_constraints
2606
+ def test_check_constraints_coerced
2607
+ check_constraints = @connection.check_constraints("products")
2608
+ assert_equal 1, check_constraints.size
2609
+
2610
+ constraint = check_constraints.first
2611
+ assert_equal "products", constraint.table_name
2612
+ assert_equal "products_price_check", constraint.name
2613
+ assert_equal "[price]>[discounted_price]", constraint.expression
2614
+ end
2615
+
2616
+ # SQL Server formats the check constraint expression differently.
2617
+ coerce_tests! :test_add_check_constraint
2618
+ def test_add_check_constraint_coerced
2619
+ @connection.add_check_constraint :trades, "quantity > 0"
2620
+
2621
+ check_constraints = @connection.check_constraints("trades")
2622
+ assert_equal 1, check_constraints.size
2623
+
2624
+ constraint = check_constraints.first
2625
+ assert_equal "trades", constraint.table_name
2626
+ assert_equal "chk_rails_2189e9f96c", constraint.name
2627
+ assert_equal "[quantity]>(0)", constraint.expression
2628
+ end
2629
+
2630
+ # SQL Server formats the check constraint expression differently.
2631
+ coerce_tests! :test_remove_check_constraint
2632
+ def test_remove_check_constraint_coerced
2633
+ @connection.add_check_constraint :trades, "price > 0", name: "price_check"
2634
+ @connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
2635
+
2636
+ assert_equal 2, @connection.check_constraints("trades").size
2637
+ @connection.remove_check_constraint :trades, name: "quantity_check"
2638
+ assert_equal 1, @connection.check_constraints("trades").size
2639
+
2640
+ constraint = @connection.check_constraints("trades").first
2641
+ assert_equal "trades", constraint.table_name
2642
+ assert_equal "price_check", constraint.name
2643
+ assert_equal "[price]>(0)", constraint.expression
2644
+
2645
+ @connection.remove_check_constraint :trades, name: :price_check # name as a symbol
2646
+ assert_empty @connection.check_constraints("trades")
2647
+ end
2648
+ end
2649
+ end
2650
+ end
2651
+
2652
+ module ActiveRecord
2653
+ module ConnectionAdapters
2654
+ class PoolConfig
2655
+ class ResolverTest < ActiveRecord::TestCase
2656
+ # SQL Server was not included in the list of available adapters in the error message.
2657
+ coerce_tests! :test_url_invalid_adapter
2658
+ def test_url_invalid_adapter_coerced
2659
+ error = assert_raises(AdapterNotFound) do
2660
+ Base.connection_handler.establish_connection "ridiculous://foo?encoding=utf8"
2661
+ end
2662
+
2663
+ assert_match "Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.", error.message
2664
+ end
2665
+ end
2666
+ end
2667
+ end
2668
+ end
2669
+
2670
+ module ActiveRecord
2671
+ class TableMetadataTest < ActiveSupport::TestCase
2672
+ # Adapter returns an object that is subclass of what is expected in the original test.
2673
+ coerce_tests! %r{#associated_table creates the right type caster for joined table with different association name}
2674
+ def associated_table_creates_the_right_type_caster_for_joined_table_with_different_association_name_coerced
2675
+ base_table_metadata = TableMetadata.new(AuditRequiredDeveloper, Arel::Table.new("developers"))
2676
+
2677
+ associated_table_metadata = base_table_metadata.associated_table("audit_logs")
2678
+
2679
+ assert associated_table_metadata.arel_table.type_for_attribute(:message).is_a?(ActiveRecord::Type::String)
2680
+ end
2681
+ end
2682
+ end
2683
+
2684
+ module ActiveRecord
2685
+ module TypeCaster
2686
+ class ConnectionTest < ActiveSupport::TestCase
2687
+ # Adapter returns an object that is subclass of what is expected in the original test.
2688
+ coerce_tests! %r{#type_for_attribute is not aware of custom types}
2689
+ def type_for_attribute_is_not_aware_of_custom_types_coerced
2690
+ type_caster = Connection.new(AttributedDeveloper, "developers")
2691
+
2692
+ type = type_caster.type_for_attribute(:name)
2693
+
2694
+ assert_not_equal DeveloperName, type.class
2695
+ assert type.is_a?(ActiveRecord::Type::String)
2696
+ end
2697
+ end
2698
+ end
2699
+ end
2700
+
2701
+ require "models/car"
2702
+ class ExplainTest < ActiveRecord::TestCase
2703
+ # Expected query slightly different from because of 'sp_executesql' and query parameters.
2704
+ coerce_tests! :test_relation_explain_with_first
2705
+ def test_relation_explain_with_first_coerced
2706
+ expected_query = capture_sql {
2707
+ Car.all.first
2708
+ }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2709
+ message = Car.all.explain.first
2710
+ assert_match(/^EXPLAIN/, message)
2711
+ assert_match(expected_query, message)
2712
+ end
2713
+
2714
+ # Expected query slightly different from because of 'sp_executesql' and query parameters.
2715
+ coerce_tests! :test_relation_explain_with_last
2716
+ def test_relation_explain_with_last_coerced
2717
+ expected_query = capture_sql {
2718
+ Car.all.last
2719
+ }.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
2720
+ expected_query = expected_query
2721
+ message = Car.all.explain.last
2722
+
2723
+ assert_match(/^EXPLAIN/, message)
2724
+ assert_match(expected_query, message)
2725
+ end
2726
+ end
2727
+
2728
+ module ActiveRecord
2729
+ module Assertions
2730
+ class QueryAssertionsTest < ActiveSupport::TestCase
2731
+ # Query slightly different in original test.
2732
+ coerce_tests! :test_assert_queries_match
2733
+ def test_assert_queries_match_coerced
2734
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 1) { Post.first }
2735
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i) { Post.first }
2736
+
2737
+ error = assert_raises(Minitest::Assertion) {
2738
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 2) { Post.first }
2739
+ }
2740
+ assert_match(/1 instead of 2 queries/, error.message)
2741
+
2742
+ error = assert_raises(Minitest::Assertion) {
2743
+ assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 0) { Post.first }
2744
+ }
2745
+ assert_match(/1 instead of 0 queries/, error.message)
2746
+ end
2747
+ end
2748
+ end
2749
+ end
2750
+
2751
+ module ActiveRecord
2752
+ class WithTest < ActiveRecord::TestCase
2753
+ # SQL contains just 'WITH' instead of 'WITH RECURSIVE' as expected by the original test.
2754
+ coerce_tests! :test_with_recursive
2755
+ def test_with_recursive_coerced
2756
+ top_companies = Company.where(firm_id: nil).to_a
2757
+ child_companies = Company.where(firm_id: top_companies).to_a
2758
+ top_companies_and_children = (top_companies.map(&:id) + child_companies.map(&:id)).sort
2759
+
2760
+ relation = Company.with_recursive(
2761
+ top_companies_and_children: [
2762
+ Company.where(firm_id: nil),
2763
+ Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id"),
2764
+ ]
2765
+ ).from("top_companies_and_children AS companies")
2766
+
2767
+ assert_equal top_companies_and_children, relation.order(:id).pluck(:id)
2768
+ assert_match "WITH ", relation.to_sql
2769
+ end
2770
+ end
2771
+ end
2772
+