activerecord-sqlserver-adapter 8.0.10 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +1 -1
  3. data/.github/workflows/ci.yml +34 -3
  4. data/CHANGELOG.md +14 -68
  5. data/Dockerfile.ci +1 -1
  6. data/Gemfile +7 -9
  7. data/Guardfile +2 -2
  8. data/README.md +33 -13
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/activerecord-sqlserver-adapter.gemspec +15 -16
  12. data/compose.ci.yaml +8 -1
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -1
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +1 -2
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +1 -1
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +4 -4
  17. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +118 -83
  18. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +3 -4
  19. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
  20. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -12
  21. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +17 -8
  22. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +162 -156
  23. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  24. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +5 -5
  25. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -7
  26. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +3 -1
  27. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -3
  28. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -3
  29. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -4
  30. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +1 -1
  31. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +4 -6
  32. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +1 -1
  33. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +0 -2
  34. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -12
  35. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +118 -66
  36. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -9
  37. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -5
  38. data/lib/arel/visitors/sqlserver.rb +55 -26
  39. data/test/cases/active_schema_test_sqlserver.rb +45 -23
  40. data/test/cases/adapter_test_sqlserver.rb +72 -59
  41. data/test/cases/coerced_tests.rb +365 -161
  42. data/test/cases/column_test_sqlserver.rb +328 -316
  43. data/test/cases/connection_test_sqlserver.rb +15 -11
  44. data/test/cases/enum_test_sqlserver.rb +8 -9
  45. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  46. data/test/cases/fetch_test_sqlserver.rb +1 -1
  47. data/test/cases/helper_sqlserver.rb +7 -3
  48. data/test/cases/index_test_sqlserver.rb +8 -6
  49. data/test/cases/insert_all_test_sqlserver.rb +3 -28
  50. data/test/cases/json_test_sqlserver.rb +8 -8
  51. data/test/cases/lateral_test_sqlserver.rb +2 -2
  52. data/test/cases/migration_test_sqlserver.rb +12 -12
  53. data/test/cases/pessimistic_locking_test_sqlserver.rb +6 -6
  54. data/test/cases/primary_keys_test_sqlserver.rb +4 -4
  55. data/test/cases/rake_test_sqlserver.rb +15 -7
  56. data/test/cases/schema_dumper_test_sqlserver.rb +109 -113
  57. data/test/cases/schema_test_sqlserver.rb +7 -7
  58. data/test/cases/transaction_test_sqlserver.rb +6 -8
  59. data/test/cases/trigger_test_sqlserver.rb +1 -1
  60. data/test/cases/utils_test_sqlserver.rb +3 -3
  61. data/test/cases/view_test_sqlserver.rb +12 -8
  62. data/test/cases/virtual_column_test_sqlserver.rb +113 -0
  63. data/test/migrations/create_clients_and_change_column_collation.rb +2 -2
  64. data/test/models/sqlserver/edge_schema.rb +2 -2
  65. data/test/schema/sqlserver_specific_schema.rb +49 -37
  66. data/test/support/coerceable_test_sqlserver.rb +10 -10
  67. data/test/support/connection_reflection.rb +0 -5
  68. data/test/support/core_ext/backtrace_cleaner.rb +36 -0
  69. data/test/support/query_assertions.rb +6 -6
  70. data/test/support/rake_helpers.rb +6 -10
  71. metadata +12 -107
@@ -2,7 +2,22 @@
2
2
 
3
3
  require "cases/helper_sqlserver"
4
4
 
5
+ require "models/author"
6
+ require "models/book"
7
+ require "models/car"
8
+ require "models/citation"
9
+ require "models/comment"
10
+ require "models/computer"
11
+ require "models/customer"
12
+ require "models/dashboard"
13
+ require "models/developer"
5
14
  require "models/event"
15
+ require "models/non_primary_key"
16
+ require "models/post"
17
+ require "models/tag"
18
+ require "models/task"
19
+ require "models/topic"
20
+
6
21
  class UniquenessValidationTest < ActiveRecord::TestCase
7
22
  # So sp_executesql swallows this exception. Run without prepared to see it.
8
23
  coerce_tests! :test_validate_uniqueness_with_limit
@@ -62,7 +77,6 @@ class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
62
77
  end
63
78
  end
64
79
 
65
- require "models/event"
66
80
  module ActiveRecord
67
81
  class AdapterTest < ActiveRecord::TestCase
68
82
  # Legacy binds are not supported.
@@ -174,21 +188,20 @@ module ActiveRecord
174
188
  end
175
189
  end
176
190
 
177
- require "models/topic"
178
191
  class AttributeMethodsTest < ActiveRecord::TestCase
179
192
  # Use IFF for boolean statement in SELECT
180
193
  coerce_tests! %r{typecast attribute from select to false}
181
194
  def test_typecast_attribute_from_select_to_false_coerced
182
- Topic.create(:title => "Budget")
183
- topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
195
+ Topic.create(title: "Budget")
196
+ topic = Topic.all.merge!(select: "topics.*, IIF (1 = 2, 1, 0) as is_test").first
184
197
  assert_not_predicate topic, :is_test?
185
198
  end
186
199
 
187
200
  # Use IFF for boolean statement in SELECT
188
201
  coerce_tests! %r{typecast attribute from select to true}
189
202
  def test_typecast_attribute_from_select_to_true_coerced
190
- Topic.create(:title => "Budget")
191
- topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
203
+ Topic.create(title: "Budget")
204
+ topic = Topic.all.merge!(select: "topics.*, IIF (1 = 1, 1, 0) as is_test").first
192
205
  assert_predicate topic, :is_test?
193
206
  end
194
207
  end
@@ -248,7 +261,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
248
261
  def test_belongs_to_coerced
249
262
  client = Client.find(3)
250
263
  first_firm = companies(:first_firm)
251
- assert_queries_and_values_match(/FETCH NEXT @3 ROWS ONLY/, ['Firm', 'Agency', 1, 1]) do
264
+ assert_queries_and_values_match(/FETCH NEXT @3 ROWS ONLY/, ["Firm", "Agency", 1, 1]) do
252
265
  assert_equal first_firm, client.firm
253
266
  assert_equal first_firm.name, client.firm.name
254
267
  end
@@ -283,7 +296,7 @@ module ActiveRecord
283
296
 
284
297
  original_test_payload_row_count_on_select_all
285
298
  ensure
286
- Book.where(author_id: nil, name: 'row count book 1').delete_all
299
+ Book.where(author_id: nil, name: "row count book 1").delete_all
287
300
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
288
301
  end
289
302
 
@@ -294,7 +307,7 @@ module ActiveRecord
294
307
 
295
308
  original_test_payload_row_count_on_pluck
296
309
  ensure
297
- Book.where(author_id: nil, name: 'row count book 2').delete_all
310
+ Book.where(author_id: nil, name: "row count book 2").delete_all
298
311
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
299
312
  end
300
313
 
@@ -305,9 +318,16 @@ module ActiveRecord
305
318
 
306
319
  original_test_payload_row_count_on_raw_sql
307
320
  ensure
308
- Book.where(author_id: nil, name: 'row count book 3').delete_all
321
+ Book.where(author_id: nil, name: "row count book 3").delete_all
309
322
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
310
323
  end
324
+
325
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
326
+ coerce_tests! :test_payload_affected_rows
327
+ def test_payload_affected_rows_coerced
328
+ Book.create!(name: "TEMP RECORD TO RUN SCHEMA QUERIES").destroy!
329
+ original_test_payload_affected_rows
330
+ end
311
331
  end
312
332
  end
313
333
 
@@ -377,11 +397,11 @@ class CalculationsTest < ActiveRecord::TestCase
377
397
  rails_core = companies(:rails_core)
378
398
 
379
399
  account = Account
380
- .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
381
- .where(firm: rails_core)
382
- .group(:firm_id)
383
- .order(:firm_id)
384
- .take!
400
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
401
+ .where(firm: rails_core)
402
+ .group(:firm_id)
403
+ .order(:firm_id)
404
+ .take!
385
405
 
386
406
  # id was not selected, so it should be nil
387
407
  # (cannot select id because it wasn't used in the GROUP BY clause)
@@ -421,7 +441,6 @@ class CalculationsTest < ActiveRecord::TestCase
421
441
  assert_equal(52.5, firm.avg_credit_limit)
422
442
  end
423
443
 
424
-
425
444
  # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
426
445
  # SELECT columns must be in the GROUP clause.
427
446
  coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
@@ -429,11 +448,11 @@ class CalculationsTest < ActiveRecord::TestCase
429
448
  rails_core = companies(:rails_core)
430
449
 
431
450
  firm = DependentFirm
432
- .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
433
- .where(id: rails_core)
434
- .joins(:account)
435
- .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
436
- .take!
451
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
452
+ .where(id: rails_core)
453
+ .joins(:account)
454
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
455
+ .take!
437
456
 
438
457
  # all the DependentFirm attributes should be present
439
458
  assert_equal rails_core, firm
@@ -499,8 +518,8 @@ module ActiveRecord
499
518
  five = columns.detect { |c| c.name == "five" }
500
519
 
501
520
  assert_equal "hello", one.default
502
- assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
503
- assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
521
+ assert_equal true, two.fetch_cast_type(connection).deserialize(two.default)
522
+ assert_equal false, three.fetch_cast_type(connection).deserialize(three.default)
504
523
  assert_equal 1, four.default
505
524
  assert_equal "hello", five.default
506
525
  end
@@ -569,7 +588,7 @@ module ActiveRecord
569
588
  # Our defaults are real 70000 integers vs '70000' strings.
570
589
  coerce_tests! :test_rename_column_preserves_default_value_not_null
571
590
  def test_rename_column_preserves_default_value_not_null_coerced
572
- add_column "test_models", "salary", :integer, :default => 70000
591
+ add_column "test_models", "salary", :integer, default: 70000
573
592
  default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
574
593
  assert_equal 70000, default_before
575
594
  rename_column "test_models", "salary", "annual_salary"
@@ -583,8 +602,8 @@ module ActiveRecord
583
602
  coerce_tests! :test_remove_column_with_multi_column_index
584
603
  def test_remove_column_with_multi_column_index_coerced
585
604
  add_column "test_models", :hat_size, :integer
586
- add_column "test_models", :hat_style, :string, :limit => 100
587
- add_index "test_models", ["hat_style", "hat_size"], :unique => true
605
+ add_column "test_models", :hat_style, :string, limit: 100
606
+ add_index "test_models", ["hat_style", "hat_size"], unique: true
588
607
  assert_equal 1, connection.indexes("test_models").size
589
608
  remove_column("test_models", "hat_size")
590
609
  assert_equal [], connection.indexes("test_models").map(&:name)
@@ -611,14 +630,20 @@ class MigrationTest < ActiveRecord::TestCase
611
630
  coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
612
631
  def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
613
632
  migration_a = Class.new(ActiveRecord::Migration::Current) {
614
- def version; 100 end
633
+ def version
634
+ 100
635
+ end
636
+
615
637
  def migrate(x)
616
638
  add_column "people", "last_name", :binary
617
639
  end
618
640
  }.new
619
641
 
620
642
  migration_b = Class.new(ActiveRecord::Migration::Current) {
621
- def version; 101 end
643
+ def version
644
+ 101
645
+ end
646
+
622
647
  def migrate(x)
623
648
  add_column "people", "last_name", :binary, if_not_exists: true
624
649
  end
@@ -647,7 +672,10 @@ module ActiveRecord
647
672
  long_table_name = "a" * (connection.table_name_length + 1)
648
673
  migration = Class.new(ActiveRecord::Migration[7.0]) {
649
674
  @@long_table_name = long_table_name
650
- def version; 100 end
675
+ def version
676
+ 100
677
+ end
678
+
651
679
  def migrate(x)
652
680
  create_table @@long_table_name
653
681
  end
@@ -658,7 +686,11 @@ module ActiveRecord
658
686
  end
659
687
  assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
660
688
  ensure
661
- connection.drop_table(long_table_name) rescue nil
689
+ begin
690
+ connection.drop_table(long_table_name)
691
+ rescue
692
+ nil
693
+ end
662
694
  end
663
695
 
664
696
  # SQL Server truncates long table names when renaming (https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-ver16).
@@ -669,7 +701,10 @@ module ActiveRecord
669
701
 
670
702
  migration = Class.new(ActiveRecord::Migration[7.0]) {
671
703
  @@long_table_name = long_table_name
672
- def version; 100 end
704
+ def version
705
+ 100
706
+ end
707
+
673
708
  def migrate(x)
674
709
  rename_table :more_testings, @@long_table_name
675
710
  end
@@ -680,14 +715,22 @@ module ActiveRecord
680
715
  assert_not connection.table_exists?(:more_testings)
681
716
  assert connection.table_exists?(long_table_name[0...-1])
682
717
  ensure
683
- connection.drop_table(:more_testings) rescue nil
684
- connection.drop_table(long_table_name[0...-1]) rescue nil
718
+ begin
719
+ connection.drop_table(:more_testings)
720
+ rescue
721
+ nil
722
+ end
723
+ begin
724
+ connection.drop_table(long_table_name[0...-1])
725
+ rescue
726
+ nil
727
+ end
685
728
  end
686
729
 
687
730
  # SQL Server has a different maximum index name length.
688
731
  coerce_tests! :test_add_index_errors_on_too_long_name_7_0
689
732
  def test_add_index_errors_on_too_long_name_7_0_coerced
690
- long_index_name = 'a' * (connection.index_name_length + 1)
733
+ long_index_name = "a" * (connection.index_name_length + 1)
691
734
 
692
735
  migration = Class.new(ActiveRecord::Migration[7.0]) {
693
736
  @@long_index_name = long_index_name
@@ -700,13 +743,13 @@ module ActiveRecord
700
743
  error = assert_raises(StandardError) do
701
744
  ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
702
745
  end
703
- assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
746
+ assert_match(/Index name '#{long_index_name}' on table 'testings' is too long/i, error.message)
704
747
  end
705
748
 
706
749
  # SQL Server has a different maximum index name length.
707
750
  coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
708
751
  def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
709
- long_index_name = 'a' * (connection.index_name_length + 1)
752
+ long_index_name = "a" * (connection.index_name_length + 1)
710
753
 
711
754
  migration = Class.new(ActiveRecord::Migration[7.0]) {
712
755
  @@long_index_name = long_index_name
@@ -723,9 +766,41 @@ module ActiveRecord
723
766
  error = assert_raises(StandardError) do
724
767
  ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
725
768
  end
726
- assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
769
+ assert_match(/Index name '#{long_index_name}' on table 'more_testings' is too long/i, error.message)
770
+ ensure
771
+ begin
772
+ connection.drop_table :more_testings
773
+ rescue
774
+ nil
775
+ end
776
+ end
777
+
778
+ # Foreign key count is the same as PostgreSQL/SQLite.
779
+ coerce_tests! :test_remove_foreign_key_on_8_0
780
+ def test_remove_foreign_key_on_8_0_coerced
781
+ connection.create_table(:sub_testings) do |t|
782
+ t.references :testing, foreign_key: true, type: :bigint
783
+ t.references :experiment, foreign_key: {to_table: :testings}, type: :bigint
784
+ end
785
+
786
+ migration = Class.new(ActiveRecord::Migration[8.0]) do
787
+ def up
788
+ change_table(:sub_testings) do |t|
789
+ t.remove_foreign_key :testings
790
+ t.remove_foreign_key :testings, column: :experiment_id
791
+ end
792
+ end
793
+ end
794
+
795
+ assert_raise(StandardError, match: /Table 'sub_testings' has no foreign key for testings$/) {
796
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
797
+ }
798
+
799
+ foreign_keys = @connection.foreign_keys("sub_testings")
800
+ assert_equal 2, foreign_keys.size
727
801
  ensure
728
- connection.drop_table :more_testings rescue nil
802
+ connection.drop_table(:sub_testings, if_exists: true)
803
+ ActiveRecord::Base.clear_cache!
729
804
  end
730
805
  end
731
806
  end
@@ -758,19 +833,26 @@ module ActiveRecord
758
833
  def setup
759
834
  @sqlserver_tasks =
760
835
  Class.new do
761
- def create; end
836
+ def create
837
+ end
762
838
 
763
- def drop; end
839
+ def drop
840
+ end
764
841
 
765
- def purge; end
842
+ def purge
843
+ end
766
844
 
767
- def charset; end
845
+ def charset
846
+ end
768
847
 
769
- def collation; end
848
+ def collation
849
+ end
770
850
 
771
- def structure_dump(*); end
851
+ def structure_dump(*)
852
+ end
772
853
 
773
- def structure_load(*); end
854
+ def structure_load(*)
855
+ end
774
856
  end.new
775
857
 
776
858
  $stdout, @original_stdout = StringIO.new, $stdout
@@ -791,7 +873,7 @@ module ActiveRecord
791
873
 
792
874
  def test_sqlserver_create
793
875
  with_stubbed_new do
794
- assert_called(eval("@sqlserver_tasks"), :create) do
876
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :create) do
795
877
  ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
796
878
  end
797
879
  end
@@ -804,7 +886,7 @@ module ActiveRecord
804
886
 
805
887
  def test_sqlserver_drop
806
888
  with_stubbed_new do
807
- assert_called(eval("@sqlserver_tasks"), :drop) do
889
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :drop) do
808
890
  ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
809
891
  end
810
892
  end
@@ -817,7 +899,7 @@ module ActiveRecord
817
899
 
818
900
  def test_sqlserver_purge
819
901
  with_stubbed_new do
820
- assert_called(eval("@sqlserver_tasks"), :purge) do
902
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :purge) do
821
903
  ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
822
904
  end
823
905
  end
@@ -830,7 +912,7 @@ module ActiveRecord
830
912
 
831
913
  def test_sqlserver_charset
832
914
  with_stubbed_new do
833
- assert_called(eval("@sqlserver_tasks"), :charset) do
915
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :charset) do
834
916
  ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
835
917
  end
836
918
  end
@@ -843,7 +925,7 @@ module ActiveRecord
843
925
 
844
926
  def test_sqlserver_collation
845
927
  with_stubbed_new do
846
- assert_called(eval("@sqlserver_tasks"), :collation) do
928
+ assert_called(eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :collation) do
847
929
  ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
848
930
  end
849
931
  end
@@ -857,10 +939,10 @@ module ActiveRecord
857
939
  def test_sqlserver_structure_dump
858
940
  with_stubbed_new do
859
941
  assert_called_with(
860
- eval("@sqlserver_tasks"), :structure_dump,
942
+ eval("@sqlserver_tasks", binding, __FILE__, __LINE__), :structure_dump,
861
943
  ["awesome-file.sql", nil]
862
944
  ) do
863
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
945
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({"adapter" => :sqlserver}, "awesome-file.sql")
864
946
  end
865
947
  end
866
948
  end
@@ -873,11 +955,11 @@ module ActiveRecord
873
955
  def test_sqlserver_structure_load
874
956
  with_stubbed_new do
875
957
  assert_called_with(
876
- eval("@sqlserver_tasks"),
958
+ eval("@sqlserver_tasks", binding, __FILE__, __LINE__),
877
959
  :structure_load,
878
960
  ["awesome-file.sql", nil]
879
961
  ) do
880
- ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
962
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({"adapter" => :sqlserver}, "awesome-file.sql")
881
963
  end
882
964
  end
883
965
  end
@@ -910,15 +992,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
910
992
  coerce_tests! %r{including association based on sql condition and no database column}
911
993
  end
912
994
 
913
- require "models/topic"
914
- require "models/customer"
915
- require "models/non_primary_key"
916
995
  class FinderTest < ActiveRecord::TestCase
917
996
  fixtures :customers, :topics, :authors
918
997
 
919
998
  # We have implicit ordering, via FETCH.
920
999
  coerce_tests! %r{doesn't have implicit ordering},
921
- :test_find_doesnt_have_implicit_ordering
1000
+ :test_find_doesnt_have_implicit_ordering
922
1001
 
923
1002
  # Assert SQL Server limit implementation
924
1003
  coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
@@ -986,8 +1065,8 @@ class FinderTest < ActiveRecord::TestCase
986
1065
  end
987
1066
 
988
1067
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
989
- coerce_tests! :test_implicit_order_column_is_configurable
990
- def test_implicit_order_column_is_configurable_coerced
1068
+ coerce_tests! :test_implicit_order_column_is_configurable_with_a_single_value
1069
+ def test_implicit_order_column_is_configurable_with_a_single_value_coerced
991
1070
  old_implicit_order_column = Topic.implicit_order_column
992
1071
  Topic.implicit_order_column = "title"
993
1072
 
@@ -1002,6 +1081,32 @@ class FinderTest < ActiveRecord::TestCase
1002
1081
  Topic.implicit_order_column = old_implicit_order_column
1003
1082
  end
1004
1083
 
1084
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1085
+ coerce_tests! :test_implicit_order_column_is_configurable_with_multiple_values
1086
+ def test_implicit_order_column_is_configurable_with_multiple_values_coerced
1087
+ old_implicit_order_column = Topic.implicit_order_column
1088
+ Topic.implicit_order_column = ["title", "author_name"]
1089
+
1090
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(quote_table_name("topics.title"))} DESC, #{Regexp.escape(quote_table_name("topics.author_name"))} DESC, #{Regexp.escape(quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1091
+ Topic.last
1092
+ }
1093
+ ensure
1094
+ Topic.implicit_order_column = old_implicit_order_column
1095
+ end
1096
+
1097
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1098
+ coerce_tests! :test_ordering_does_not_append_primary_keys_or_query_constraints_if_passed_an_implicit_order_column_array_ending_in_nil
1099
+ def test_ordering_does_not_append_primary_keys_or_query_constraints_if_passed_an_implicit_order_column_array_ending_in_nil_coerced
1100
+ old_implicit_order_column = Topic.implicit_order_column
1101
+ Topic.implicit_order_column = ["author_name", nil]
1102
+
1103
+ assert_queries_and_values_match(/ORDER BY #{Regexp.escape(quote_table_name("topics.author_name"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
1104
+ Topic.last
1105
+ }
1106
+ ensure
1107
+ Topic.implicit_order_column = old_implicit_order_column
1108
+ end
1109
+
1005
1110
  # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
1006
1111
  coerce_tests! :test_implicit_order_set_to_primary_key
1007
1112
  def test_implicit_order_set_to_primary_key_coerced
@@ -1186,8 +1291,6 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
1186
1291
  coerce_tests! :test_does_not_override_select
1187
1292
  end
1188
1293
 
1189
- require "models/developer"
1190
- require "models/computer"
1191
1294
  class NestedRelationScopingTest < ActiveRecord::TestCase
1192
1295
  # Assert SQL Server limit implementation
1193
1296
  coerce_tests! :test_merge_options
@@ -1203,7 +1306,6 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
1203
1306
  end
1204
1307
  end
1205
1308
 
1206
- require "models/topic"
1207
1309
  class PersistenceTest < ActiveRecord::TestCase
1208
1310
  # Rails test required updating a identity column.
1209
1311
  coerce_tests! :test_update_columns_changing_id
@@ -1230,20 +1332,26 @@ class PersistenceTest < ActiveRecord::TestCase
1230
1332
  coerce_tests! :test_model_with_no_auto_populated_fields_still_returns_primary_key_after_insert
1231
1333
  end
1232
1334
 
1233
- require "models/author"
1234
1335
  class UpdateAllTest < ActiveRecord::TestCase
1235
- # Rails test required updating a identity column.
1336
+ # Regular expression slightly different.
1236
1337
  coerce_tests! :test_update_all_doesnt_ignore_order
1237
1338
  def test_update_all_doesnt_ignore_order_coerced
1238
- david, mary = authors(:david), authors(:mary)
1239
- _(david.id).must_equal 1
1240
- _(mary.id).must_equal 2
1241
- _(david.name).wont_equal mary.name
1242
- assert_queries_match(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
1243
- Author.where("[id] > 1").order(:id).update_all(name: "Test")
1339
+ assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
1340
+ test_update_with_order_succeeds = lambda do |order|
1341
+ Author.order(order).update_all("id = id + 1")
1342
+ rescue ActiveRecord::ActiveRecordError
1343
+ false
1344
+ end
1345
+
1346
+ if test_update_with_order_succeeds.call("id DESC")
1347
+ # test that this wasn't a fluke and using an incorrect order results in an exception
1348
+ assert_not test_update_with_order_succeeds.call("id ASC")
1349
+ else
1350
+ # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
1351
+ assert_queries_match(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC.*\)/i) do
1352
+ test_update_with_order_succeeds.call("id DESC")
1353
+ end
1244
1354
  end
1245
- _(david.reload.name).must_equal "David"
1246
- _(mary.reload.name).must_equal "Test"
1247
1355
  end
1248
1356
 
1249
1357
  # SELECT columns must be in the GROUP clause.
@@ -1256,7 +1364,7 @@ class UpdateAllTest < ActiveRecord::TestCase
1256
1364
 
1257
1365
  assert_operator posts.length, :>, 0
1258
1366
  assert posts.all? { |post| post.comments.length >= minimum_comments_count }
1259
- assert posts.all? { |post| "ig" == post.title }
1367
+ assert posts.all? { |post| post.title == "ig" }
1260
1368
 
1261
1369
  post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
1262
1370
  assert_not_equal "ig", post.title
@@ -1281,7 +1389,6 @@ class DeleteAllTest < ActiveRecord::TestCase
1281
1389
  end
1282
1390
  end
1283
1391
 
1284
- require "models/topic"
1285
1392
  module ActiveRecord
1286
1393
  class PredicateBuilderTest < ActiveRecord::TestCase
1287
1394
  # Same as original test except string has `N` prefix to indicate unicode string.
@@ -1293,7 +1400,7 @@ module ActiveRecord
1293
1400
  # Same as original test except string has `N` prefix to indicate unicode string.
1294
1401
  coerce_tests! :test_registering_new_handlers_for_association
1295
1402
  def test_registering_new_handlers_for_association_coerced
1296
- assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
1403
+ assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: {title: /rails/}).to_sql
1297
1404
  end
1298
1405
 
1299
1406
  # Same as original test except string has `N` prefix to indicate unicode string.
@@ -1312,7 +1419,6 @@ module ActiveRecord
1312
1419
  end
1313
1420
  end
1314
1421
 
1315
- require "models/task"
1316
1422
  class QueryCacheTest < ActiveRecord::TestCase
1317
1423
  # SQL Server adapter not in list of supported adapters in original test.
1318
1424
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
@@ -1323,7 +1429,6 @@ class QueryCacheTest < ActiveRecord::TestCase
1323
1429
  end
1324
1430
  end
1325
1431
 
1326
- require "models/post"
1327
1432
  class RelationTest < ActiveRecord::TestCase
1328
1433
  # Use LEN() instead of LENGTH() function.
1329
1434
  coerce_tests! :test_reverse_order_with_function
@@ -1351,7 +1456,7 @@ class RelationTest < ActiveRecord::TestCase
1351
1456
  assert Post.order(:title).reorder(nil).take
1352
1457
  end
1353
1458
  assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1354
- assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1459
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1355
1460
  end
1356
1461
 
1357
1462
  # We have implicit ordering, via FETCH.
@@ -1363,7 +1468,7 @@ class RelationTest < ActiveRecord::TestCase
1363
1468
  end
1364
1469
  assert_equal posts(:welcome), post
1365
1470
  assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1366
- assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1471
+ assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1367
1472
  end
1368
1473
 
1369
1474
  # We are not doing order duplicate removal anymore.
@@ -1377,7 +1482,7 @@ class RelationTest < ActiveRecord::TestCase
1377
1482
  def test_multiple_where_and_having_clauses_coerced
1378
1483
  post = Post.first
1379
1484
  having_then_where = Post.having(id: post.id).where(title: post.title)
1380
- .having(id: post.id).where(title: post.title).group(:id).select(:id)
1485
+ .having(id: post.id).where(title: post.title).group(:id).select(:id)
1381
1486
 
1382
1487
  assert_equal [post], having_then_where
1383
1488
  end
@@ -1458,10 +1563,15 @@ module ActiveRecord
1458
1563
  query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1459
1564
  assert_equal expected, query
1460
1565
  end
1566
+
1567
+ # Order column must be in the GROUP clause. However, with implicit ordering we can't test this when selecting non-aggregate expression column.
1568
+ coerce_tests! %r{no queries when using pick with non-aggregate expression and empty IN}
1569
+
1570
+ # Order column must be in the GROUP clause. However, with implicit ordering we can't test this when selecting aggregate expression column.
1571
+ coerce_tests! %r{runs queries when using pick with aggregate expression despite empty IN}
1461
1572
  end
1462
1573
  end
1463
1574
 
1464
- require "models/post"
1465
1575
  class SanitizeTest < ActiveRecord::TestCase
1466
1576
  # Use nvarchar string (N'') in assert
1467
1577
  coerce_tests! :test_sanitize_sql_like_example_use_case
@@ -1495,14 +1605,14 @@ end
1495
1605
 
1496
1606
  class SchemaDumperTest < ActiveRecord::TestCase
1497
1607
  # Use nvarchar string (N'') in assert
1498
- coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1499
- def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1500
- versions = %w{ 20100101010101 20100201010101 20100301010101 }
1608
+ coerce_tests! :test_dump_schema_versions_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
1609
+ def test_dump_schema_versions_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
1610
+ versions = %w[20100101010101 20100201010101 20100301010101]
1501
1611
  versions.shuffle.each do |v|
1502
1612
  @schema_migration.create_version(v)
1503
1613
  end
1504
1614
 
1505
- schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
1615
+ schema_info = ActiveRecord::Base.lease_connection.dump_schema_versions
1506
1616
  expected = <<~STR
1507
1617
  INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
1508
1618
  (N'20100301010101'),
@@ -1527,7 +1637,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
1527
1637
  # Fall through false positive with no filter.
1528
1638
  coerce_tests! :test_schema_dumps_partial_indices
1529
1639
  def test_schema_dumps_partial_indices_coerced
1530
- index_definition = standard_dump.split(/\n/).grep(/t.index.*company_partial_index/).first.strip
1640
+ index_definition = standard_dump.split("\n").grep(/t.index.*company_partial_index/).first.strip
1531
1641
  assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "([rating]>(10))"', index_definition
1532
1642
  end
1533
1643
 
@@ -1544,7 +1654,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
1544
1654
  # SQL Server formats the check constraint expression differently.
1545
1655
  coerce_tests! :test_schema_dumps_check_constraints
1546
1656
  def test_schema_dumps_check_constraints_coerced
1547
- constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
1657
+ constraint_definition = dump_table_schema("products").split("\n").grep(/t.check_constraint.*products_price_check/).first.strip
1548
1658
  assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
1549
1659
  end
1550
1660
  end
@@ -1564,14 +1674,14 @@ class SchemaDumperDefaultsCoerceTest < ActiveRecord::TestCase
1564
1674
  setup do
1565
1675
  @connection = ActiveRecord::Base.lease_connection
1566
1676
  @connection.create_table :dump_defaults, force: true do |t|
1567
- t.string :string_with_default, default: "Hello!"
1568
- t.date :date_with_default, default: "2014-06-05"
1677
+ t.string :string_with_default, default: "Hello!"
1678
+ t.date :date_with_default, default: "2014-06-05"
1569
1679
  t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
1570
- t.time :time_with_default, default: "07:17:04"
1571
- t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1680
+ t.time :time_with_default, default: "07:17:04"
1681
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
1572
1682
 
1573
- t.text :text_with_default, default: "John' Doe"
1574
- t.text :uuid, default: -> { "newid()" }
1683
+ t.text :text_with_default, default: "John' Doe"
1684
+ t.text :uuid, default: -> { "newid()" }
1575
1685
  end
1576
1686
  end
1577
1687
 
@@ -1588,7 +1698,6 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
1588
1698
  coerce_tests! %r{inspect on Model class does not raise}
1589
1699
  end
1590
1700
 
1591
- require "models/topic"
1592
1701
  class TransactionTest < ActiveRecord::TestCase
1593
1702
  # SQL Server does not have query for release_savepoint.
1594
1703
  coerce_tests! :test_releasing_named_savepoints
@@ -1634,7 +1743,7 @@ class TransactionTest < ActiveRecord::TestCase
1634
1743
  /DELETE/i,
1635
1744
  /^SAVE TRANSACTION/i,
1636
1745
  /DELETE/i,
1637
- /COMMIT/i,
1746
+ /COMMIT/i
1638
1747
  ]
1639
1748
 
1640
1749
  assert_equal expected_queries.size, actual_queries.size
@@ -1670,7 +1779,7 @@ class TransactionTest < ActiveRecord::TestCase
1670
1779
  /^SAVE TRANSACTION/i,
1671
1780
  /DELETE/i,
1672
1781
  /DELETE/i,
1673
- /COMMIT/i,
1782
+ /COMMIT/i
1674
1783
  ]
1675
1784
 
1676
1785
  assert_equal expected_queries.size, actual_queries.size
@@ -1680,7 +1789,6 @@ class TransactionTest < ActiveRecord::TestCase
1680
1789
  end
1681
1790
  end
1682
1791
 
1683
- require "models/tag"
1684
1792
  class TransactionIsolationTest < ActiveRecord::TestCase
1685
1793
  # SQL Server will lock the table for counts even when both
1686
1794
  # connections are `READ COMMITTED`. So we bypass with `READPAST`.
@@ -1698,9 +1806,27 @@ class TransactionIsolationTest < ActiveRecord::TestCase
1698
1806
 
1699
1807
  # I really need some help understanding this one.
1700
1808
  coerce_tests! %r{repeatable read}
1809
+
1810
+ private
1811
+
1812
+ # Need to handle the resetting of the isolation level in the adapter by `SQLServerRealTransaction#commit` for each
1813
+ # connection pool. After the resetting events have been removed we can assert the number of expected isolation level
1814
+ # events. This workaround assumes that the `count` also matches the number of connection pools used in the test.
1815
+ # Note: MySQL & PostgreSQL do not reset the connection and SQLite does support transaction isolation.
1816
+ undef_method :assert_begin_isolation_level_event
1817
+ def assert_begin_isolation_level_event(events, isolation: "READ COMMITTED", count: 1)
1818
+ isolation_events = events.select { |event| event.match(/SET TRANSACTION ISOLATION LEVEL/) }
1819
+
1820
+ count.times do
1821
+ index_of_reset_starting_isolation_level_event = isolation_events.index("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
1822
+ assert index_of_reset_starting_isolation_level_event.present?
1823
+ isolation_events.delete_at(index_of_reset_starting_isolation_level_event)
1824
+ end
1825
+
1826
+ assert_equal count, isolation_events.count { |event| event.match(/SET TRANSACTION ISOLATION LEVEL #{isolation}/) }
1827
+ end
1701
1828
  end
1702
1829
 
1703
- require "models/book"
1704
1830
  class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
1705
1831
  # We have a few view tables. use includes vs equality.
1706
1832
  coerce_tests! :test_views
@@ -1724,7 +1850,6 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
1724
1850
  end
1725
1851
  end
1726
1852
 
1727
- require "models/author"
1728
1853
  class YamlSerializationTest < ActiveRecord::TestCase
1729
1854
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1730
1855
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
@@ -1773,7 +1898,7 @@ class TimePrecisionTest < ActiveRecord::TestCase
1773
1898
  coerce_tests! :test_time_precision_is_truncated_on_assignment
1774
1899
  def test_time_precision_is_truncated_on_assignment_coerced
1775
1900
  @connection.create_table(:foos, force: true)
1776
- @connection.add_column :foos, :start, :time, precision: 0
1901
+ @connection.add_column :foos, :start, :time, precision: 0
1777
1902
  @connection.add_column :foos, :finish, :time, precision: 6
1778
1903
 
1779
1904
  time = ::Time.now.change(nsec: 123456789)
@@ -1809,8 +1934,8 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1809
1934
  coerce_tests! :test_default_negative_integer
1810
1935
  def test_default_negative_integer_coerced
1811
1936
  record = DefaultNumber.new
1812
- assert_equal (-5), record.negative_integer
1813
- assert_equal (-5), record.negative_integer_before_type_cast
1937
+ assert_equal(-5, record.negative_integer)
1938
+ assert_equal(-5, record.negative_integer_before_type_cast)
1814
1939
  end
1815
1940
 
1816
1941
  # We do better with native types and do not return strings for everything.
@@ -1839,7 +1964,6 @@ module ActiveRecord
1839
1964
  end
1840
1965
  end
1841
1966
 
1842
- require "models/book"
1843
1967
  module ActiveRecord
1844
1968
  class StatementCacheTest < ActiveRecord::TestCase
1845
1969
  # Getting random failures.
@@ -1852,7 +1976,7 @@ module ActiveRecord
1852
1976
 
1853
1977
  original_test_statement_cache_values_differ
1854
1978
  ensure
1855
- Book.where(author_id: nil, name: 'my book').delete_all
1979
+ Book.where(author_id: nil, name: "my book").delete_all
1856
1980
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
1857
1981
  end
1858
1982
  end
@@ -1862,53 +1986,38 @@ module ActiveRecord
1862
1986
  module ConnectionAdapters
1863
1987
  class SchemaCacheTest < ActiveRecord::TestCase
1864
1988
  # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1865
- coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1866
-
1867
- # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1868
- coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1869
-
1870
- # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1871
- unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1872
- def test_marshal_dump_and_load_with_gzip_coerced
1873
- with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1874
- end
1875
- def test_marshal_dump_and_load_via_disk_coerced
1876
- with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1989
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
1990
+
1991
+ # Cast type in SQL Server is :varchar rather than Unicode :string.
1992
+ coerce_tests! :test_yaml_load_8_0_dump_without_cast_type_still_get_the_right_one
1993
+ def test_yaml_load_8_0_dump_without_cast_type_still_get_the_right_one
1994
+ cache = load_bound_reflection(schema_dump_8_0_path)
1995
+
1996
+ assert_no_queries do
1997
+ columns = cache.columns_hash("courses")
1998
+ assert_equal 3, columns.size
1999
+ cast_type = columns["name"].fetch_cast_type(@connection)
2000
+ assert_not_nil cast_type, "expected cast_type to be present"
2001
+ assert_equal :varchar, cast_type.type
1877
2002
  end
1878
2003
  end
1879
2004
 
1880
2005
  private
1881
2006
 
1882
- def with_marshable_time_defaults
1883
- # Detect problems
1884
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1885
- column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1886
- current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1887
- end
1888
-
1889
- # Correct problems
1890
- if current_default.present?
1891
- @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1892
- end
1893
-
1894
- # Run original test
1895
- yield
1896
- ensure
1897
- # Revert changes
1898
- @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
2007
+ # We need to give the full paths for this to work.
2008
+ undef_method :schema_dump_5_1_path
2009
+ def schema_dump_5_1_path
2010
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
1899
2011
  end
1900
2012
 
1901
- # We need to give the full path for this to work.
1902
- undef_method :schema_dump_path
1903
- def schema_dump_path
1904
- File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
2013
+ undef_method :schema_dump_8_0_path
2014
+ def schema_dump_8_0_path
2015
+ File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_8_0.yml")
1905
2016
  end
1906
2017
  end
1907
2018
  end
1908
2019
  end
1909
2020
 
1910
- require "models/post"
1911
- require "models/comment"
1912
2021
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1913
2022
  fixtures :posts
1914
2023
 
@@ -2029,9 +2138,9 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
2029
2138
  test "order: allows valid arguments with COLLATE" do
2030
2139
  collation_name = "Latin1_General_CS_AS_WS"
2031
2140
 
2032
- ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
2141
+ ids_expected = Post.order(Arel.sql(%(author_id, title COLLATE #{collation_name} DESC))).pluck(:id)
2033
2142
 
2034
- ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
2143
+ ids = Post.order(["author_id", %(title COLLATE #{collation_name} DESC)]).pluck(:id)
2035
2144
 
2036
2145
  assert_equal ids_expected, ids
2037
2146
  end
@@ -2105,14 +2214,13 @@ module ActiveRecord
2105
2214
  end
2106
2215
  end
2107
2216
 
2108
- require "models/book"
2109
2217
  class EnumTest < ActiveRecord::TestCase
2110
2218
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2111
2219
  coerce_tests! %r{enums are distinct per class}
2112
2220
  test "enums are distinct per class coerced" do
2113
2221
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2114
2222
 
2115
- send(:'original_enums are distinct per class')
2223
+ send(:"original_enums are distinct per class")
2116
2224
  ensure
2117
2225
  Book.where(author_id: nil, name: nil).delete_all
2118
2226
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2123,7 +2231,7 @@ class EnumTest < ActiveRecord::TestCase
2123
2231
  test "creating new objects with enum scopes coerced" do
2124
2232
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2125
2233
 
2126
- send(:'original_creating new objects with enum scopes')
2234
+ send(:"original_creating new objects with enum scopes")
2127
2235
  ensure
2128
2236
  Book.where(author_id: nil, name: nil).delete_all
2129
2237
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2134,7 +2242,7 @@ class EnumTest < ActiveRecord::TestCase
2134
2242
  test "enums are inheritable coerced" do
2135
2243
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2136
2244
 
2137
- send(:'original_enums are inheritable')
2245
+ send(:"original_enums are inheritable")
2138
2246
  ensure
2139
2247
  Book.where(author_id: nil, name: nil).delete_all
2140
2248
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
@@ -2145,14 +2253,13 @@ class EnumTest < ActiveRecord::TestCase
2145
2253
  test "serializable? with large number label coerced" do
2146
2254
  Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2147
2255
 
2148
- send(:'original_serializable\? with large number label')
2256
+ send(:"original_serializable\\? with large number label")
2149
2257
  ensure
2150
2258
  Book.where(author_id: nil, name: nil).delete_all
2151
2259
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2152
2260
  end
2153
2261
  end
2154
2262
 
2155
- require "models/citation"
2156
2263
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
2157
2264
  fixtures :citations
2158
2265
 
@@ -2187,7 +2294,7 @@ end
2187
2294
  class ReloadModelsTest < ActiveRecord::TestCase
2188
2295
  # Skip test on Windows. The number of arguments passed to `IO.popen` in
2189
2296
  # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
2190
- coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2297
+ coerce_tests! :test_has_one_with_reload if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2191
2298
  end
2192
2299
 
2193
2300
  class MarshalSerializationTest < ActiveRecord::TestCase
@@ -2283,15 +2390,14 @@ end
2283
2390
 
2284
2391
  class MigratorTest < ActiveRecord::TestCase
2285
2392
  # Test fails on Windows AppVeyor CI for unknown reason.
2286
- coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2393
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2287
2394
  end
2288
2395
 
2289
2396
  class MultiDbMigratorTest < ActiveRecord::TestCase
2290
2397
  # Test fails on Windows AppVeyor CI for unknown reason.
2291
- coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
2398
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
2292
2399
  end
2293
2400
 
2294
- require "models/book"
2295
2401
  class FieldOrderedValuesTest < ActiveRecord::TestCase
2296
2402
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2297
2403
  coerce_tests! :test_in_order_of_with_enums_values
@@ -2338,12 +2444,11 @@ class FieldOrderedValuesTest < ActiveRecord::TestCase
2338
2444
  end
2339
2445
  end
2340
2446
 
2341
- require "models/dashboard"
2342
2447
  class QueryLogsTest < ActiveRecord::TestCase
2343
2448
  # Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
2344
2449
  coerce_tests! :test_invalid_encoding_query
2345
2450
  def test_invalid_encoding_query_coerced
2346
- ActiveRecord::QueryLogs.tags = [ :application ]
2451
+ ActiveRecord::QueryLogs.tags = [:application]
2347
2452
  assert_raises ActiveRecord::StatementInvalid do
2348
2453
  ActiveRecord::Base.lease_connection.execute "select 1 as '\xFF'"
2349
2454
  end
@@ -2356,8 +2461,8 @@ class InsertAllTest < ActiveRecord::TestCase
2356
2461
  def test_insert_all_returns_requested_sql_fields_coerced
2357
2462
  skip unless supports_insert_returning?
2358
2463
 
2359
- result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2360
- assert_equal %w[ REWORK ], result.pluck("name")
2464
+ result = Book.insert_all! [{name: "Rework", author_id: 1}], returning: Arel.sql("UPPER(INSERTED.name) as name")
2465
+ assert_equal %w[REWORK], result.pluck("name")
2361
2466
  end
2362
2467
 
2363
2468
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
@@ -2397,7 +2502,7 @@ module ActiveRecord
2397
2502
  undef_method :invalid_add_column_option_exception_message
2398
2503
  def invalid_add_column_option_exception_message(key)
2399
2504
  default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
2400
- default_keys.concat([":is_identity"]) # SQL Server additional valid keys
2505
+ default_keys.concat([":is_identity", ":as", ":stored"]) # SQL Server additional valid keys
2401
2506
 
2402
2507
  "Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
2403
2508
  end
@@ -2405,11 +2510,11 @@ module ActiveRecord
2405
2510
  end
2406
2511
  end
2407
2512
 
2408
-
2409
2513
  # Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
2410
2514
  # can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
2411
2515
  class ActiveRecordMessagePackTest < ActiveRecord::TestCase
2412
2516
  private
2517
+
2413
2518
  undef_method :serializer
2414
2519
  def serializer
2415
2520
  @serializer ||= ::MessagePack::Factory.new.tap do |factory|
@@ -2432,7 +2537,7 @@ end
2432
2537
 
2433
2538
  module ActiveRecord
2434
2539
  module ConnectionAdapters
2435
- class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2540
+ class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
2436
2541
  # Tests are not about a specific adapter.
2437
2542
  coerce_all_tests!
2438
2543
  end
@@ -2556,7 +2661,6 @@ module ActiveRecord
2556
2661
  end
2557
2662
  end
2558
2663
 
2559
-
2560
2664
  module ActiveRecord
2561
2665
  class TableMetadataTest < ActiveSupport::TestCase
2562
2666
  # Adapter returns an object that is subclass of what is expected in the original test.
@@ -2623,7 +2727,7 @@ module ActiveRecord
2623
2727
  relation = Company.with_recursive(
2624
2728
  top_companies_and_children: [
2625
2729
  Company.where(firm_id: nil),
2626
- Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id"),
2730
+ Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id")
2627
2731
  ]
2628
2732
  ).from("top_companies_and_children AS companies")
2629
2733
 
@@ -2633,3 +2737,103 @@ module ActiveRecord
2633
2737
  end
2634
2738
  end
2635
2739
 
2740
+ module ActiveRecord
2741
+ class AdapterConnectionTest < ActiveRecord::TestCase
2742
+ # Original method only handled the core adapters.
2743
+ undef_method :raw_transaction_open?
2744
+ def raw_transaction_open?(connection)
2745
+ transaction_count = connection.instance_variable_get(:@raw_connection).execute("SELECT @@TRANCOUNT AS TRANSACTION_COUNT").first["TRANSACTION_COUNT"]
2746
+ transaction_count > 0
2747
+ rescue
2748
+ false
2749
+ end
2750
+ end
2751
+ end
2752
+
2753
+ class EachTest < ActiveRecord::TestCase
2754
+ # Match SQL Server limit implementation.
2755
+ coerce_tests! :test_in_batches_executes_range_queries_when_unconstrained
2756
+ def test_in_batches_executes_range_queries_when_unconstrained_coerced
2757
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2758
+
2759
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET @\S ROWS FETCH NEXT @\S ROWS ONLY/i, count: 6) do
2760
+ assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 1) do
2761
+ Post.in_batches(of: 2).to_a
2762
+ end
2763
+ end
2764
+
2765
+ assert_queries_match(/WHERE #{quoted_posts_id} > .+ AND #{quoted_posts_id} <= .+/i) do
2766
+ relations.each { |relation| assert_kind_of Post, relation.first }
2767
+ end
2768
+ end
2769
+
2770
+ # Match SQL Server limit implementation.
2771
+ coerce_tests! :test_in_batches_executes_in_queries_when_unconstrained_and_opted_out_of_ranges
2772
+ def test_in_batches_executes_in_queries_when_unconstrained_and_opted_out_of_ranges_coerced
2773
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2774
+
2775
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 6) do
2776
+ Post.in_batches(of: 2, use_ranges: false).to_a
2777
+ end
2778
+
2779
+ assert_queries_match(/#{quoted_posts_id} IN \(.+\)/i) do
2780
+ relations.each { |relation| assert_kind_of Post, relation.first }
2781
+ end
2782
+ end
2783
+
2784
+ # Match SQL Server limit implementation.
2785
+ coerce_tests! :test_in_batches_executes_in_queries_when_constrained
2786
+ def test_in_batches_executes_in_queries_when_constrained_coerced
2787
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2788
+
2789
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 3) do
2790
+ Post.where("id < ?", 5).in_batches(of: 2).to_a
2791
+ end
2792
+
2793
+ assert_queries_match(/#{quoted_posts_id} IN \(.+\)/i) do
2794
+ relations.each { |relation| assert_kind_of Post, relation.first }
2795
+ end
2796
+ end
2797
+
2798
+ # Match SQL Server limit implementation.
2799
+ coerce_tests! :test_in_batches_executes_range_queries_when_constrained_and_opted_in_into_ranges
2800
+ def test_in_batches_executes_range_queries_when_constrained_and_opted_in_into_ranges_coerced
2801
+ quoted_posts_id = Regexp.escape(quote_table_name("posts.id"))
2802
+
2803
+ relations = assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET @\S ROWS FETCH NEXT @\S ROWS ONLY/i, count: 3) do
2804
+ assert_queries_match(/ORDER BY #{quoted_posts_id} ASC OFFSET 0 ROWS FETCH NEXT @\S ROWS ONLY/i, count: 1) do
2805
+ Post.where("id < ?", 5).in_batches(of: 2, use_ranges: true).to_a
2806
+ end
2807
+ end
2808
+
2809
+ assert_queries_match(/#{quoted_posts_id} > .+ AND #{quoted_posts_id} <= .+/i) do
2810
+ relations.each { |relation| assert_kind_of Post, relation.first }
2811
+ end
2812
+ end
2813
+
2814
+ # Match SQL Server SQL format.
2815
+ coerce_tests! :test_in_batches_should_unscope_cursor_after_pluck
2816
+ def test_in_batches_should_unscope_cursor_after_pluck_coerced
2817
+ all_ids = Post.limit(2).pluck(:id)
2818
+ found_ids = []
2819
+ # only a single clause on id (i.e. not 'id IN (?,?) AND id = ?', but only 'id = ?')
2820
+ assert_queries_match(/WHERE #{Regexp.escape(quote_table_name("posts.id"))} = \S+ ORDER BY/) do
2821
+ Post.where(id: all_ids).in_batches(of: 1) do |relation|
2822
+ found_ids << relation.pick(:id)
2823
+ end
2824
+ end
2825
+ assert_equal all_ids.sort, found_ids
2826
+ end
2827
+
2828
+ # Match SQL Server SQL format.
2829
+ coerce_tests! :test_in_batches_loaded_should_unscope_cursor_after_pluck
2830
+ def test_in_batches_loaded_should_unscope_cursor_after_pluck_coerced
2831
+ all_ids = Post.limit(2).pluck(:id)
2832
+ # only a single clause on id (i.e. not 'id IN (?,?) AND id = ?', but only 'id = ?')
2833
+ assert_queries_match(/WHERE #{Regexp.escape(quote_table_name("posts.id"))} = \S+;/) do
2834
+ Post.where(id: all_ids).in_batches(of: 1, load: true) do |relation|
2835
+ relation.delete_all
2836
+ end
2837
+ end
2838
+ end
2839
+ end