activerecord-sqlserver-adapter 6.0.0.rc1 → 6.1.0.0.rc1

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +29 -0
  5. data/CHANGELOG.md +18 -23
  6. data/Gemfile +11 -5
  7. data/Guardfile +9 -8
  8. data/README.md +32 -3
  9. data/RUNNING_UNIT_TESTS.md +1 -1
  10. data/Rakefile +12 -16
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +4 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
  22. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
  23. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
  24. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
  25. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
  26. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
  27. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
  28. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  29. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  30. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
  31. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
  32. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
  33. data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
  34. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
  35. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
  37. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
  38. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
  39. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
  40. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
  41. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
  42. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
  44. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
  48. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
  52. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
  54. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
  55. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
  57. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
  58. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
  59. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
  65. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
  69. data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
  70. data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
  71. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
  72. data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
  73. data/lib/active_record/sqlserver_base.rb +9 -15
  74. data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
  75. data/lib/activerecord-sqlserver-adapter.rb +1 -1
  76. data/lib/arel/visitors/sqlserver.rb +126 -50
  77. data/lib/arel_sqlserver.rb +2 -2
  78. data/test/cases/adapter_test_sqlserver.rb +203 -190
  79. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  80. data/test/cases/change_column_null_test_sqlserver.rb +12 -12
  81. data/test/cases/coerced_tests.rb +656 -318
  82. data/test/cases/column_test_sqlserver.rb +285 -284
  83. data/test/cases/connection_test_sqlserver.rb +15 -20
  84. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  85. data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
  86. data/test/cases/fetch_test_sqlserver.rb +14 -22
  87. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
  88. data/test/cases/helper_sqlserver.rb +13 -15
  89. data/test/cases/in_clause_test_sqlserver.rb +36 -9
  90. data/test/cases/index_test_sqlserver.rb +13 -15
  91. data/test/cases/json_test_sqlserver.rb +23 -25
  92. data/test/cases/lateral_test_sqlserver.rb +35 -0
  93. data/test/cases/migration_test_sqlserver.rb +71 -26
  94. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  95. data/test/cases/order_test_sqlserver.rb +57 -53
  96. data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
  97. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  98. data/test/cases/rake_test_sqlserver.rb +33 -46
  99. data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
  100. data/test/cases/schema_test_sqlserver.rb +18 -26
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -4
  102. data/test/cases/showplan_test_sqlserver.rb +24 -33
  103. data/test/cases/specific_schema_test_sqlserver.rb +66 -65
  104. data/test/cases/transaction_test_sqlserver.rb +16 -19
  105. data/test/cases/trigger_test_sqlserver.rb +12 -12
  106. data/test/cases/utils_test_sqlserver.rb +68 -70
  107. data/test/cases/uuid_test_sqlserver.rb +11 -13
  108. data/test/debug.rb +6 -6
  109. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  110. data/test/migrations/create_clients_and_change_column_null.rb +1 -1
  111. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
  112. data/test/models/sqlserver/booking.rb +1 -1
  113. data/test/models/sqlserver/customers_view.rb +1 -1
  114. data/test/models/sqlserver/dollar_table_name.rb +1 -1
  115. data/test/models/sqlserver/edge_schema.rb +1 -3
  116. data/test/models/sqlserver/fk_has_fk.rb +1 -1
  117. data/test/models/sqlserver/fk_has_pk.rb +1 -1
  118. data/test/models/sqlserver/natural_pk_data.rb +2 -2
  119. data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
  120. data/test/models/sqlserver/no_pk_data.rb +1 -1
  121. data/test/models/sqlserver/object_default.rb +1 -1
  122. data/test/models/sqlserver/quoted_table.rb +2 -2
  123. data/test/models/sqlserver/quoted_view_1.rb +1 -1
  124. data/test/models/sqlserver/quoted_view_2.rb +1 -1
  125. data/test/models/sqlserver/sst_memory.rb +1 -1
  126. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  127. data/test/models/sqlserver/string_default.rb +1 -1
  128. data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
  129. data/test/models/sqlserver/string_defaults_view.rb +1 -1
  130. data/test/models/sqlserver/tinyint_pk.rb +1 -1
  131. data/test/models/sqlserver/trigger.rb +2 -2
  132. data/test/models/sqlserver/trigger_history.rb +1 -1
  133. data/test/models/sqlserver/upper.rb +1 -1
  134. data/test/models/sqlserver/uppered.rb +1 -1
  135. data/test/models/sqlserver/uuid.rb +1 -1
  136. data/test/schema/sqlserver_specific_schema.rb +36 -21
  137. data/test/support/coerceable_test_sqlserver.rb +1 -4
  138. data/test/support/connection_reflection.rb +1 -2
  139. data/test/support/core_ext/query_cache.rb +1 -1
  140. data/test/support/load_schema_sqlserver.rb +3 -5
  141. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  142. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  143. data/test/support/minitest_sqlserver.rb +1 -1
  144. data/test/support/paths_sqlserver.rb +9 -11
  145. data/test/support/rake_helpers.rb +12 -10
  146. data/test/support/sql_counter_sqlserver.rb +14 -16
  147. data/test/support/test_in_memory_oltp.rb +7 -7
  148. metadata +31 -11
  149. data/.travis.yml +0 -23
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "migrations/create_clients_and_change_column_collation"
5
+
6
+ class ChangeColumnCollationTestSqlServer < ActiveRecord::TestCase
7
+ before do
8
+ @old_verbose = ActiveRecord::Migration.verbose
9
+ ActiveRecord::Migration.verbose = false
10
+ CreateClientsAndChangeColumnCollation.new.up
11
+ end
12
+
13
+ after do
14
+ CreateClientsAndChangeColumnCollation.new.down
15
+ ActiveRecord::Migration.verbose = @old_verbose
16
+ end
17
+
18
+ def find_column(table, name)
19
+ table.find { |column| column.name == name }
20
+ end
21
+
22
+ let(:clients_table) { connection.columns("clients") }
23
+ let(:name_column) { find_column(clients_table, "name") }
24
+ let(:code_column) { find_column(clients_table, "code") }
25
+
26
+ it "change column collation to other than default" do
27
+ _(name_column.collation).must_equal "SQL_Latin1_General_CP1_CS_AS"
28
+ end
29
+
30
+ it "change column collation to default" do
31
+ _(code_column.collation).must_equal "SQL_Latin1_General_CP1_CI_AS"
32
+ end
33
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cases/helper_sqlserver'
4
- require 'migrations/create_clients_and_change_column_null'
3
+ require "cases/helper_sqlserver"
4
+ require "migrations/create_clients_and_change_column_null"
5
5
 
6
6
  class ChangeColumnNullTestSqlServer < ActiveRecord::TestCase
7
7
  before do
@@ -19,25 +19,25 @@ class ChangeColumnNullTestSqlServer < ActiveRecord::TestCase
19
19
  table.find { |column| column.name == name }
20
20
  end
21
21
 
22
- let(:clients_table) { connection.columns('clients') }
23
- let(:name_column) { find_column(clients_table, 'name') }
24
- let(:code_column) { find_column(clients_table, 'code') }
25
- let(:value_column) { find_column(clients_table, 'value') }
22
+ let(:clients_table) { connection.columns("clients") }
23
+ let(:name_column) { find_column(clients_table, "name") }
24
+ let(:code_column) { find_column(clients_table, "code") }
25
+ let(:value_column) { find_column(clients_table, "value") }
26
26
 
27
- describe '#change_column_null' do
28
- it 'does not change the column limit' do
27
+ describe "#change_column_null" do
28
+ it "does not change the column limit" do
29
29
  _(name_column.limit).must_equal 15
30
30
  end
31
31
 
32
- it 'does not change the column default' do
33
- _(code_column.default).must_equal 'n/a'
32
+ it "does not change the column default" do
33
+ _(code_column.default).must_equal "n/a"
34
34
  end
35
35
 
36
- it 'does not change the column precision' do
36
+ it "does not change the column precision" do
37
37
  _(value_column.precision).must_equal 32
38
38
  end
39
39
 
40
- it 'does not change the column scale' do
40
+ it "does not change the column scale" do
41
41
  _(value_column.scale).must_equal 8
42
42
  end
43
43
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cases/helper_sqlserver'
3
+ require "cases/helper_sqlserver"
4
4
 
5
-
6
-
7
- require 'models/event'
5
+ require "models/event"
8
6
  class UniquenessValidationTest < ActiveRecord::TestCase
9
7
  # So sp_executesql swallows this exception. Run without prepared to see it.
10
8
  coerce_tests! :test_validate_uniqueness_with_limit
@@ -26,25 +24,35 @@ class UniquenessValidationTest < ActiveRecord::TestCase
26
24
  end
27
25
  end
28
26
 
29
- # Skip the test if database is case-insensitive.
30
- coerce_tests! :test_validate_case_sensitive_uniqueness_by_default
31
- def test_validate_case_sensitive_uniqueness_by_default_coerced
32
- database_collation = connection.select_one("SELECT collation_name FROM sys.databases WHERE name = 'activerecord_unittest'").values.first
33
- skip if database_collation.include?('_CI_')
27
+ # Same as original coerced test except that it handles default SQL Server case-insensitive collation.
28
+ coerce_tests! :test_validate_uniqueness_by_default_database_collation
29
+ def test_validate_uniqueness_by_default_database_collation_coerced
30
+ Topic.validates_uniqueness_of(:author_email_address)
34
31
 
35
- original_test_validate_case_sensitive_uniqueness_by_default_coerced
36
- end
37
- end
32
+ topic1 = Topic.new(author_email_address: "david@loudthinking.com")
33
+ topic2 = Topic.new(author_email_address: "David@loudthinking.com")
34
+
35
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
38
36
 
37
+ assert_not topic1.valid?
38
+ assert_not topic1.save
39
39
 
40
+ # Case insensitive collation (SQL_Latin1_General_CP1_CI_AS) by default.
41
+ # Should not allow "David" if "david" exists.
42
+ assert_not topic2.valid?
43
+ assert_not topic2.save
40
44
 
45
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
46
+ assert_equal 1, Topic.where(author_email_address: "David@loudthinking.com").count
47
+ end
48
+ end
41
49
 
42
- require 'models/event'
50
+ require "models/event"
43
51
  module ActiveRecord
44
52
  class AdapterTest < ActiveRecord::TestCase
45
- # I really don`t think we can support legacy binds.
46
- coerce_tests! :test_select_all_with_legacy_binds
47
- coerce_tests! :test_insert_update_delete_with_legacy_binds
53
+ # Legacy binds are not supported.
54
+ coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
55
+ coerce_tests! :test_select_all_insert_update_delete_with_legacy_binds
48
56
 
49
57
  # As far as I can tell, SQL Server does not support null bytes in strings.
50
58
  coerce_tests! :test_update_prepared_statement
@@ -54,23 +62,70 @@ module ActiveRecord
54
62
  def test_value_limit_violations_are_translated_to_specific_exception_coerced
55
63
  connection.unprepared_statement do
56
64
  error = assert_raises(ActiveRecord::ValueTooLong) do
57
- Event.create(title: 'abcdefgh')
65
+ Event.create(title: "abcdefgh")
58
66
  end
59
67
  assert_not_nil error.cause
60
68
  end
61
69
  end
70
+ end
71
+ end
62
72
 
73
+ module ActiveRecord
74
+ class AdapterPreventWritesTest < ActiveRecord::TestCase
63
75
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
64
76
  coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
65
77
  def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
66
78
  Subscriber.send(:load_schema!)
67
79
  original_test_errors_when_an_insert_query_is_called_while_preventing_writes
68
80
  end
81
+
82
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
83
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
84
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
85
+ Subscriber.send(:load_schema!)
86
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
87
+ end
88
+
89
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
90
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
91
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
92
+ Subscriber.send(:load_schema!)
93
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
94
+ end
69
95
  end
70
96
  end
71
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
72
106
 
107
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
108
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
109
+ def test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes_coerced
110
+ Subscriber.send(:load_schema!)
111
+ original_test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
112
+ end
73
113
 
114
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
115
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
116
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
117
+ Subscriber.send(:load_schema!)
118
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
119
+ end
120
+
121
+ # Fix randomly failing test. The loading of the model's schema was affecting the test.
122
+ coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
123
+ def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
124
+ Subscriber.send(:load_schema!)
125
+ original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
126
+ end
127
+ end
128
+ end
74
129
 
75
130
  module ActiveRecord
76
131
  class AdapterTestWithoutTransaction < ActiveRecord::TestCase
@@ -125,15 +180,12 @@ module ActiveRecord
125
180
  end
126
181
  end
127
182
 
128
-
129
-
130
-
131
- require 'models/topic'
183
+ require "models/topic"
132
184
  class AttributeMethodsTest < ActiveRecord::TestCase
133
185
  # Use IFF for boolean statement in SELECT
134
186
  coerce_tests! %r{typecast attribute from select to false}
135
187
  def test_typecast_attribute_from_select_to_false_coerced
136
- Topic.create(:title => 'Budget')
188
+ Topic.create(:title => "Budget")
137
189
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
138
190
  assert_not_predicate topic, :is_test?
139
191
  end
@@ -141,21 +193,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase
141
193
  # Use IFF for boolean statement in SELECT
142
194
  coerce_tests! %r{typecast attribute from select to true}
143
195
  def test_typecast_attribute_from_select_to_true_coerced
144
- Topic.create(:title => 'Budget')
196
+ Topic.create(:title => "Budget")
145
197
  topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
146
198
  assert_predicate topic, :is_test?
147
199
  end
148
200
  end
149
201
 
150
-
151
-
152
-
153
202
  class BasicsTest < ActiveRecord::TestCase
154
203
  # Use square brackets as SQL Server escaped character
155
204
  coerce_tests! :test_column_names_are_escaped
156
205
  def test_column_names_are_escaped_coerced
157
206
  conn = ActiveRecord::Base.connection
158
- assert_equal '[t]]]', conn.quote_column_name('t]')
207
+ assert_equal "[t]]]", conn.quote_column_name("t]")
159
208
  end
160
209
 
161
210
  # Just like PostgreSQLAdapter does.
@@ -175,7 +224,7 @@ class BasicsTest < ActiveRecord::TestCase
175
224
  end
176
225
 
177
226
  def test_update_date_time_attributes_with_default_timezone_local
178
- with_env_tz 'America/New_York' do
227
+ with_env_tz "America/New_York" do
179
228
  with_timezone_config default: :local do
180
229
  Time.use_zone("Eastern Time (US & Canada)") do
181
230
  topic = Topic.find(1)
@@ -190,7 +239,7 @@ class BasicsTest < ActiveRecord::TestCase
190
239
  # SQL Server does not have query for release_savepoint
191
240
  coerce_tests! %r{an empty transaction does not raise if preventing writes}
192
241
  test "an empty transaction does not raise if preventing writes coerced" do
193
- ActiveRecord::Base.connection_handler.while_preventing_writes do
242
+ ActiveRecord::Base.while_preventing_writes do
194
243
  assert_queries(1, ignore_none: true) do
195
244
  Bird.transaction do
196
245
  ActiveRecord::Base.connection.materialize_transactions
@@ -200,9 +249,6 @@ class BasicsTest < ActiveRecord::TestCase
200
249
  end
201
250
  end
202
251
 
203
-
204
-
205
-
206
252
  class BelongsToAssociationsTest < ActiveRecord::TestCase
207
253
  # Since @client.firm is a single first/top, and we use FETCH the order clause is used.
208
254
  coerce_tests! :test_belongs_to_does_not_use_order_by
@@ -227,9 +273,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
227
273
  end
228
274
  end
229
275
 
230
-
231
-
232
-
233
276
  module ActiveRecord
234
277
  class BindParameterTest < ActiveRecord::TestCase
235
278
  # Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
@@ -254,11 +297,57 @@ module ActiveRecord
254
297
  coerce_tests! :test_statement_cache_with_find_by
255
298
  coerce_tests! :test_statement_cache_with_in_clause
256
299
  coerce_tests! :test_statement_cache_with_sql_string_literal
257
- end
258
- end
259
300
 
301
+ # Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
302
+ coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
303
+ def test_bind_params_to_sql_with_prepared_statements_coerced
304
+ assert_bind_params_to_sql_coerced(prepared: true)
305
+ end
260
306
 
307
+ def test_bind_params_to_sql_with_unprepared_statements_coerced
308
+ @connection.unprepared_statement do
309
+ assert_bind_params_to_sql_coerced(prepared: false)
310
+ end
311
+ end
261
312
 
313
+ private
314
+
315
+ def assert_bind_params_to_sql_coerced(prepared:)
316
+ table = Author.quoted_table_name
317
+ pk = "#{table}.#{Author.quoted_primary_key}"
318
+
319
+ # prepared_statements: true
320
+ #
321
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
322
+ #
323
+ # prepared_statements: false
324
+ #
325
+ # SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
326
+ #
327
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
328
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
329
+
330
+ authors = Author.where(id: [1, 2, 3, nil])
331
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
332
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
333
+
334
+ # prepared_statements: true
335
+ #
336
+ # EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
337
+ #
338
+ # prepared_statements: false
339
+ #
340
+ # SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
341
+ #
342
+ sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
343
+ sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
344
+
345
+ authors = Author.where(id: [1, 2, 3, 9223372036854775808])
346
+ assert_equal sql_unprepared, @connection.to_sql(authors.arel)
347
+ assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
348
+ end
349
+ end
350
+ end
262
351
 
263
352
  module ActiveRecord
264
353
  class InstrumentationTest < ActiveRecord::TestCase
@@ -271,9 +360,6 @@ module ActiveRecord
271
360
  end
272
361
  end
273
362
 
274
-
275
-
276
-
277
363
  class CalculationsTest < ActiveRecord::TestCase
278
364
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
279
365
  coerce_tests! :test_offset_is_kept
@@ -282,11 +368,112 @@ class CalculationsTest < ActiveRecord::TestCase
282
368
  original_test_offset_is_kept
283
369
  end
284
370
 
285
- # Are decimal, not integer.
371
+ # The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
286
372
  coerce_tests! :test_should_return_decimal_average_of_integer_field
287
373
  def test_should_return_decimal_average_of_integer_field_coerced
288
374
  value = Account.average(:id)
289
- assert_equal BigDecimal('3.0').to_s, BigDecimal(value).to_s
375
+ assert_equal 3, value
376
+ end
377
+
378
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
379
+ # Match SQL Server limit implementation.
380
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
381
+ def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
382
+ rails_core = companies(:rails_core)
383
+
384
+ sql = <<~SQL
385
+ SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
386
+ FROM accounts
387
+ WHERE firm_id = ?
388
+ GROUP BY firm_id
389
+ ORDER BY firm_id
390
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
391
+ SQL
392
+
393
+ account = Account.find_by_sql([sql, rails_core]).first
394
+
395
+ # id was not selected, so it should be nil
396
+ # (cannot select id because it wasn't used in the GROUP BY clause)
397
+ assert_nil account.id
398
+
399
+ # firm_id was explicitly selected, so it should be present
400
+ assert_equal(rails_core, account.firm)
401
+
402
+ # avg_credit_limit should be present as a virtual attribute
403
+ assert_equal(52.5, account.avg_credit_limit)
404
+ end
405
+
406
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
407
+ # Order column must be in the GROUP clause.
408
+ coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
409
+ def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
410
+ rails_core = companies(:rails_core)
411
+
412
+ account = Account
413
+ .select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
414
+ .where(firm: rails_core)
415
+ .group(:firm_id)
416
+ .order(:firm_id)
417
+ .take!
418
+
419
+ # id was not selected, so it should be nil
420
+ # (cannot select id because it wasn't used in the GROUP BY clause)
421
+ assert_nil account.id
422
+
423
+ # firm_id was explicitly selected, so it should be present
424
+ assert_equal(rails_core, account.firm)
425
+
426
+ # avg_credit_limit should be present as a virtual attribute
427
+ assert_equal(52.5, account.avg_credit_limit)
428
+ end
429
+
430
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
431
+ # SELECT columns must be in the GROUP clause.
432
+ # Match SQL Server limit implementation.
433
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
434
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
435
+ rails_core = companies(:rails_core)
436
+
437
+ sql = <<~SQL
438
+ SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
439
+ FROM companies
440
+ INNER JOIN accounts ON companies.id = accounts.firm_id
441
+ WHERE companies.id = ?
442
+ GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description
443
+ ORDER BY companies.id
444
+ OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
445
+ SQL
446
+
447
+ firm = DependentFirm.find_by_sql([sql, rails_core]).first
448
+
449
+ # all the DependentFirm attributes should be present
450
+ assert_equal rails_core, firm
451
+ assert_equal rails_core.name, firm.name
452
+
453
+ # avg_credit_limit should be present as a virtual attribute
454
+ assert_equal(52.5, firm.avg_credit_limit)
455
+ end
456
+
457
+
458
+ # In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
459
+ # SELECT columns must be in the GROUP clause.
460
+ coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
461
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
462
+ rails_core = companies(:rails_core)
463
+
464
+ firm = DependentFirm
465
+ .select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
466
+ .where(id: rails_core)
467
+ .joins(:account)
468
+ .group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description)
469
+ .take!
470
+
471
+ # all the DependentFirm attributes should be present
472
+ assert_equal rails_core, firm
473
+ assert_equal rails_core.name, firm.name
474
+
475
+ # avg_credit_limit should be present as a virtual attribute
476
+ assert_equal(52.5, firm.avg_credit_limit)
290
477
  end
291
478
 
292
479
  # Match SQL Server limit implementation
@@ -317,9 +504,6 @@ class CalculationsTest < ActiveRecord::TestCase
317
504
  coerce_tests! :test_having_with_strong_parameters
318
505
  end
319
506
 
320
-
321
-
322
-
323
507
  module ActiveRecord
324
508
  class Migration
325
509
  class ChangeSchemaTest < ActiveRecord::TestCase
@@ -351,9 +535,6 @@ module ActiveRecord
351
535
  end
352
536
  end
353
537
 
354
-
355
-
356
-
357
538
  module ActiveRecord
358
539
  module ConnectionAdapters
359
540
  class QuoteARBaseTest < ActiveRecord::TestCase
@@ -361,22 +542,23 @@ module ActiveRecord
361
542
  coerce_tests! :test_quote_ar_object
362
543
  def test_quote_ar_object_coerced
363
544
  value = DatetimePrimaryKey.new(id: @time)
364
- assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
545
+ assert_deprecated do
546
+ assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
547
+ end
365
548
  end
366
549
 
367
550
  # Use our date format.
368
551
  coerce_tests! :test_type_cast_ar_object
369
552
  def test_type_cast_ar_object_coerced
370
553
  value = DatetimePrimaryKey.new(id: @time)
371
- assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
554
+ assert_deprecated do
555
+ assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
556
+ end
372
557
  end
373
558
  end
374
559
  end
375
560
  end
376
561
 
377
-
378
-
379
-
380
562
  module ActiveRecord
381
563
  class Migration
382
564
  class ColumnAttributesTest < ActiveRecord::TestCase
@@ -391,16 +573,13 @@ module ActiveRecord
391
573
  end
392
574
  end
393
575
 
394
-
395
-
396
-
397
576
  module ActiveRecord
398
577
  class Migration
399
578
  class ColumnsTest < ActiveRecord::TestCase
400
579
  # Our defaults are real 70000 integers vs '70000' strings.
401
580
  coerce_tests! :test_rename_column_preserves_default_value_not_null
402
581
  def test_rename_column_preserves_default_value_not_null_coerced
403
- add_column 'test_models', 'salary', :integer, :default => 70000
582
+ add_column "test_models", "salary", :integer, :default => 70000
404
583
  default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
405
584
  assert_equal 70000, default_before
406
585
  rename_column "test_models", "salary", "annual_salary"
@@ -416,9 +595,9 @@ module ActiveRecord
416
595
  add_column "test_models", :hat_size, :integer
417
596
  add_column "test_models", :hat_style, :string, :limit => 100
418
597
  add_index "test_models", ["hat_style", "hat_size"], :unique => true
419
- assert_equal 1, connection.indexes('test_models').size
598
+ assert_equal 1, connection.indexes("test_models").size
420
599
  remove_column("test_models", "hat_size")
421
- assert_equal [], connection.indexes('test_models').map(&:name)
600
+ assert_equal [], connection.indexes("test_models").map(&:name)
422
601
  end
423
602
 
424
603
  # Choose `StatementInvalid` vs `ActiveRecordError`.
@@ -433,51 +612,12 @@ module ActiveRecord
433
612
  end
434
613
  end
435
614
 
436
-
437
-
438
-
439
615
  class MigrationTest < ActiveRecord::TestCase
440
- # We do not have do the DecimalWithoutScale type.
441
- coerce_tests! :test_add_table_with_decimals
442
- def test_add_table_with_decimals_coerced
443
- Person.connection.drop_table :big_numbers rescue nil
444
- assert !BigNumber.table_exists?
445
- GiveMeBigNumbers.up
446
- BigNumber.reset_column_information
447
- assert BigNumber.create(
448
- :bank_balance => 1586.43,
449
- :big_bank_balance => BigDecimal("1000234000567.95"),
450
- :world_population => 6000000000,
451
- :my_house_population => 3,
452
- :value_of_e => BigDecimal("2.7182818284590452353602875")
453
- )
454
- b = BigNumber.first
455
- assert_not_nil b
456
- assert_not_nil b.bank_balance
457
- assert_not_nil b.big_bank_balance
458
- assert_not_nil b.world_population
459
- assert_not_nil b.my_house_population
460
- assert_not_nil b.value_of_e
461
- assert_kind_of BigDecimal, b.world_population
462
- assert_equal '6000000000.0', b.world_population.to_s
463
- assert_kind_of Integer, b.my_house_population
464
- assert_equal 3, b.my_house_population
465
- assert_kind_of BigDecimal, b.bank_balance
466
- assert_equal BigDecimal("1586.43"), b.bank_balance
467
- assert_kind_of BigDecimal, b.big_bank_balance
468
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
469
- GiveMeBigNumbers.down
470
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
471
- end
472
-
473
616
  # For some reason our tests set Rails.@_env which breaks test env switching.
474
617
  coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
475
618
  coerce_tests! :test_internal_metadata_stores_environment
476
619
  end
477
620
 
478
-
479
-
480
-
481
621
  class CoreTest < ActiveRecord::TestCase
482
622
  # I think fixtures are using the wrong time zone and the `:first`
483
623
  # `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
@@ -485,9 +625,6 @@ class CoreTest < ActiveRecord::TestCase
485
625
  coerce_tests! :test_pretty_print_persisted
486
626
  end
487
627
 
488
-
489
-
490
-
491
628
  module ActiveRecord
492
629
  module ConnectionAdapters
493
630
  # Just like PostgreSQLAdapter does.
@@ -501,23 +638,26 @@ module ActiveRecord
501
638
  end
502
639
  end
503
640
 
504
-
505
-
506
-
507
641
  module ActiveRecord
508
642
  # The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
509
643
  module DatabaseTasksSetupper
510
644
  def setup
511
645
  @sqlserver_tasks =
512
- Class.new do
513
- def create; end
514
- def drop; end
515
- def purge; end
516
- def charset; end
517
- def collation; end
518
- def structure_dump(*); end
519
- def structure_load(*); end
520
- end.new
646
+ Class.new do
647
+ def create; end
648
+
649
+ def drop; end
650
+
651
+ def purge; end
652
+
653
+ def charset; end
654
+
655
+ def collation; end
656
+
657
+ def structure_dump(*); end
658
+
659
+ def structure_load(*); end
660
+ end.new
521
661
 
522
662
  $stdout, @original_stdout = StringIO.new, $stdout
523
663
  $stderr, @original_stderr = StringIO.new, $stderr
@@ -580,7 +720,6 @@ module ActiveRecord
580
720
  end
581
721
  end
582
722
  end
583
-
584
723
  end
585
724
 
586
725
  class DatabaseTasksCollationTest < ActiveRecord::TestCase
@@ -603,8 +742,8 @@ module ActiveRecord
603
742
  def test_sqlserver_structure_dump
604
743
  with_stubbed_new do
605
744
  assert_called_with(
606
- eval("@sqlserver_tasks"), :structure_dump,
607
- ["awesome-file.sql", nil]
745
+ eval("@sqlserver_tasks"), :structure_dump,
746
+ ["awesome-file.sql", nil]
608
747
  ) do
609
748
  ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
610
749
  end
@@ -619,9 +758,9 @@ module ActiveRecord
619
758
  def test_sqlserver_structure_load
620
759
  with_stubbed_new do
621
760
  assert_called_with(
622
- eval("@sqlserver_tasks"),
623
- :structure_load,
624
- ["awesome-file.sql", nil]
761
+ eval("@sqlserver_tasks"),
762
+ :structure_load,
763
+ ["awesome-file.sql", nil]
625
764
  ) do
626
765
  ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
627
766
  end
@@ -629,11 +768,6 @@ module ActiveRecord
629
768
  end
630
769
  end
631
770
 
632
- class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
633
- # Skip this test with /tmp/my_schema_cache.yml path on Windows.
634
- coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
635
- end
636
-
637
771
  class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
638
772
  # We extend `local_database?` so that common VM IPs can be used.
639
773
  coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
@@ -645,9 +779,6 @@ module ActiveRecord
645
779
  end
646
780
  end
647
781
 
648
-
649
-
650
-
651
782
  class DefaultScopingTest < ActiveRecord::TestCase
652
783
  # We are not doing order duplicate removal anymore.
653
784
  coerce_tests! :test_order_in_default_scope_should_not_prevail
@@ -661,16 +792,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
661
792
  end
662
793
  end
663
794
 
664
-
665
-
666
-
667
- require 'models/post'
668
- require 'models/subscriber'
795
+ require "models/post"
796
+ require "models/subscriber"
669
797
  class EachTest < ActiveRecord::TestCase
670
798
  # Quoting in tests does not cope with bracket quoting.
671
799
  coerce_tests! :test_find_in_batches_should_quote_batch_order
672
800
  def test_find_in_batches_should_quote_batch_order_coerced
673
- c = Post.connection
801
+ Post.connection
674
802
  assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
675
803
  Post.find_in_batches(:batch_size => 1) do |batch|
676
804
  assert_kind_of Array, batch
@@ -682,7 +810,7 @@ class EachTest < ActiveRecord::TestCase
682
810
  # Quoting in tests does not cope with bracket quoting.
683
811
  coerce_tests! :test_in_batches_should_quote_batch_order
684
812
  def test_in_batches_should_quote_batch_order_coerced
685
- c = Post.connection
813
+ Post.connection
686
814
  assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
687
815
  Post.in_batches(of: 1) do |relation|
688
816
  assert_kind_of ActiveRecord::Relation, relation
@@ -692,9 +820,6 @@ class EachTest < ActiveRecord::TestCase
692
820
  end
693
821
  end
694
822
 
695
-
696
-
697
-
698
823
  class EagerAssociationTest < ActiveRecord::TestCase
699
824
  # Use LEN() vs length() function.
700
825
  coerce_tests! :test_count_with_include
@@ -706,11 +831,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
706
831
  coerce_tests! %r{including association based on sql condition and no database column}
707
832
  end
708
833
 
709
-
710
-
711
-
712
- require 'models/topic'
834
+ require "models/topic"
835
+ require "models/customer"
836
+ require "models/non_primary_key"
713
837
  class FinderTest < ActiveRecord::TestCase
838
+ fixtures :customers, :topics, :authors
839
+
714
840
  # We have implicit ordering, via FETCH.
715
841
  coerce_tests! %r{doesn't have implicit ordering},
716
842
  :test_find_doesnt_have_implicit_ordering
@@ -737,7 +863,7 @@ class FinderTest < ActiveRecord::TestCase
737
863
  # Can not use array condition due to not finding right type and hence fractional second quoting.
738
864
  coerce_tests! :test_condition_utc_time_interpolation_with_default_timezone_local
739
865
  def test_condition_utc_time_interpolation_with_default_timezone_local_coerced
740
- with_env_tz 'America/New_York' do
866
+ with_env_tz "America/New_York" do
741
867
  with_timezone_config default: :local do
742
868
  topic = Topic.first
743
869
  assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
@@ -748,17 +874,95 @@ class FinderTest < ActiveRecord::TestCase
748
874
  # Can not use array condition due to not finding right type and hence fractional second quoting.
749
875
  coerce_tests! :test_condition_local_time_interpolation_with_default_timezone_utc
750
876
  def test_condition_local_time_interpolation_with_default_timezone_utc_coerced
751
- with_env_tz 'America/New_York' do
877
+ with_env_tz "America/New_York" do
752
878
  with_timezone_config default: :utc do
753
879
  topic = Topic.first
754
880
  assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
755
881
  end
756
882
  end
757
883
  end
758
- end
759
884
 
885
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
886
+ coerce_tests! :test_include_on_unloaded_relation_with_match
887
+ def test_include_on_unloaded_relation_with_match_coerced
888
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
889
+ assert_equal true, Customer.where(name: "David").include?(customers(:david))
890
+ end
891
+ end
892
+
893
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
894
+ coerce_tests! :test_include_on_unloaded_relation_without_match
895
+ def test_include_on_unloaded_relation_without_match_coerced
896
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
897
+ assert_equal false, Customer.where(name: "David").include?(customers(:mary))
898
+ end
899
+ end
900
+
901
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
902
+ coerce_tests! :test_member_on_unloaded_relation_with_match
903
+ def test_member_on_unloaded_relation_with_match_coerced
904
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
905
+ assert_equal true, Customer.where(name: "David").member?(customers(:david))
906
+ end
907
+ end
908
+
909
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
910
+ coerce_tests! :test_member_on_unloaded_relation_without_match
911
+ def test_member_on_unloaded_relation_without_match_coerced
912
+ assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
913
+ assert_equal false, Customer.where(name: "David").member?(customers(:mary))
914
+ end
915
+ end
916
+
917
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
918
+ coerce_tests! :test_implicit_order_column_is_configurable
919
+ def test_implicit_order_column_is_configurable_coerced
920
+ old_implicit_order_column = Topic.implicit_order_column
921
+ Topic.implicit_order_column = "title"
760
922
 
923
+ assert_equal topics(:fifth), Topic.first
924
+ assert_equal topics(:third), Topic.last
761
925
 
926
+ c = Topic.connection
927
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
928
+ Topic.last
929
+ }
930
+ ensure
931
+ Topic.implicit_order_column = old_implicit_order_column
932
+ end
933
+
934
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
935
+ coerce_tests! :test_implicit_order_set_to_primary_key
936
+ def test_implicit_order_set_to_primary_key_coerced
937
+ old_implicit_order_column = Topic.implicit_order_column
938
+ Topic.implicit_order_column = "id"
939
+
940
+ c = Topic.connection
941
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
942
+ Topic.last
943
+ }
944
+ ensure
945
+ Topic.implicit_order_column = old_implicit_order_column
946
+ end
947
+
948
+ # Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
949
+ coerce_tests! :test_implicit_order_for_model_without_primary_key
950
+ def test_implicit_order_for_model_without_primary_key_coerced
951
+ old_implicit_order_column = NonPrimaryKey.implicit_order_column
952
+ NonPrimaryKey.implicit_order_column = "created_at"
953
+
954
+ c = NonPrimaryKey.connection
955
+
956
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
957
+ NonPrimaryKey.last
958
+ }
959
+ ensure
960
+ NonPrimaryKey.implicit_order_column = old_implicit_order_column
961
+ end
962
+
963
+ # SQL Server is unable to use aliased SELECT in the HAVING clause.
964
+ coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
965
+ end
762
966
 
763
967
  module ActiveRecord
764
968
  class Migration
@@ -777,9 +981,6 @@ module ActiveRecord
777
981
  end
778
982
  end
779
983
 
780
-
781
-
782
-
783
984
  class HasOneAssociationsTest < ActiveRecord::TestCase
784
985
  # We use OFFSET/FETCH vs TOP. So we always have an order.
785
986
  coerce_tests! :test_has_one_does_not_use_order_by
@@ -796,9 +997,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
796
997
  end
797
998
  end
798
999
 
799
-
800
-
801
-
802
1000
  class HasOneThroughAssociationsTest < ActiveRecord::TestCase
803
1001
  # Asserted SQL to get one row different from original test.
804
1002
  coerce_tests! :test_has_one_through_executes_limited_query
@@ -810,16 +1008,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
810
1008
  end
811
1009
  end
812
1010
 
813
-
814
-
815
-
816
-
817
- require 'models/company'
1011
+ require "models/company"
818
1012
  class InheritanceTest < ActiveRecord::TestCase
819
1013
  # Rails test required inserting to a identity column.
820
1014
  coerce_tests! :test_a_bad_type_column
821
1015
  def test_a_bad_type_column_coerced
822
- Company.connection.with_identity_insert_enabled('companies') do
1016
+ Company.connection.with_identity_insert_enabled("companies") do
823
1017
  Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
824
1018
  end
825
1019
  assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
@@ -828,45 +1022,36 @@ class InheritanceTest < ActiveRecord::TestCase
828
1022
  # Use Square brackets around column name
829
1023
  coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
830
1024
  def test_eager_load_belongs_to_primary_key_quoting_coerced
831
- con = Account.connection
1025
+ Account.connection
832
1026
  assert_sql(/\[companies\]\.\[id\] = @0.* @0 = 1/) do
833
1027
  Account.all.merge!(:includes => :firm).find(1)
834
1028
  end
835
1029
  end
836
1030
  end
837
1031
 
838
-
839
-
840
-
841
1032
  class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
842
1033
  # Uses || operator in SQL. Just trust core gets value out of this test.
843
1034
  coerce_tests! :test_does_not_override_select
844
1035
  end
845
1036
 
846
-
847
-
848
-
849
- require 'models/developer'
850
- require 'models/computer'
1037
+ require "models/developer"
1038
+ require "models/computer"
851
1039
  class NestedRelationScopingTest < ActiveRecord::TestCase
852
1040
  # Assert SQL Server limit implementation
853
1041
  coerce_tests! :test_merge_options
854
1042
  def test_merge_options_coerced
855
- Developer.where('salary = 80000').scoping do
1043
+ Developer.where("salary = 80000").scoping do
856
1044
  Developer.limit(10).scoping do
857
1045
  devs = Developer.all
858
1046
  sql = devs.to_sql
859
- assert_match '(salary = 80000)', sql
860
- assert_match 'FETCH NEXT 10 ROWS ONLY', sql
1047
+ assert_match "(salary = 80000)", sql
1048
+ assert_match "FETCH NEXT 10 ROWS ONLY", sql
861
1049
  end
862
1050
  end
863
1051
  end
864
1052
  end
865
1053
 
866
-
867
-
868
-
869
- require 'models/topic'
1054
+ require "models/topic"
870
1055
  class PersistenceTest < ActiveRecord::TestCase
871
1056
  # Rails test required updating a identity column.
872
1057
  coerce_tests! :test_update_columns_changing_id
@@ -890,10 +1075,7 @@ class PersistenceTest < ActiveRecord::TestCase
890
1075
  end
891
1076
  end
892
1077
 
893
-
894
-
895
-
896
- require 'models/author'
1078
+ require "models/author"
897
1079
  class UpdateAllTest < ActiveRecord::TestCase
898
1080
  # Rails test required updating a identity column.
899
1081
  coerce_tests! :test_update_all_doesnt_ignore_order
@@ -903,17 +1085,14 @@ class UpdateAllTest < ActiveRecord::TestCase
903
1085
  _(mary.id).must_equal 2
904
1086
  _(david.name).wont_equal mary.name
905
1087
  assert_sql(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
906
- Author.where('[id] > 1').order(:id).update_all(name: 'Test')
1088
+ Author.where("[id] > 1").order(:id).update_all(name: "Test")
907
1089
  end
908
- _(david.reload.name).must_equal 'David'
909
- _(mary.reload.name).must_equal 'Test'
1090
+ _(david.reload.name).must_equal "David"
1091
+ _(mary.reload.name).must_equal "Test"
910
1092
  end
911
1093
  end
912
1094
 
913
-
914
-
915
-
916
- require 'models/topic'
1095
+ require "models/topic"
917
1096
  module ActiveRecord
918
1097
  class PredicateBuilderTest < ActiveRecord::TestCase
919
1098
  # Same as original test except string has `N` prefix to indicate unicode string.
@@ -930,9 +1109,6 @@ module ActiveRecord
930
1109
  end
931
1110
  end
932
1111
 
933
-
934
-
935
-
936
1112
  class PrimaryKeysTest < ActiveRecord::TestCase
937
1113
  # SQL Server does not have query for release_savepoint
938
1114
  coerce_tests! :test_create_without_primary_key_no_extra_query
@@ -945,10 +1121,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
945
1121
  end
946
1122
  end
947
1123
 
948
-
949
-
950
-
951
- require 'models/task'
1124
+ require "models/task"
952
1125
  class QueryCacheTest < ActiveRecord::TestCase
953
1126
  # SQL Server adapter not in list of supported adapters in original test.
954
1127
  coerce_tests! :test_cache_does_not_wrap_results_in_arrays
@@ -980,15 +1153,12 @@ class QueryCacheTest < ActiveRecord::TestCase
980
1153
  Task.find(1)
981
1154
  end
982
1155
 
983
- assert_includes ActiveRecord::SQLCounter.log_all.first , "TC.CONSTRAINT_TYPE = N''PRIMARY KEY''"
1156
+ assert_includes ActiveRecord::SQLCounter.log_all.first, "TC.CONSTRAINT_TYPE = N''PRIMARY KEY''"
984
1157
  end
985
1158
  end
986
1159
  end
987
1160
 
988
-
989
-
990
-
991
- require 'models/post'
1161
+ require "models/post"
992
1162
  class RelationTest < ActiveRecord::TestCase
993
1163
  # Use LEN vs LENGTH function.
994
1164
  coerce_tests! :test_reverse_order_with_function
@@ -1023,13 +1193,19 @@ class RelationTest < ActiveRecord::TestCase
1023
1193
  coerce_tests! :test_reorder_with_first
1024
1194
  def test_reorder_with_first_coerced
1025
1195
  sql_log = capture_sql do
1026
- assert Post.order(:title).reorder(nil).first
1196
+ message = <<~MSG.squish
1197
+ `.reorder(nil)` with `.first` / `.first!` no longer
1198
+ takes non-deterministic result in Rails 6.2.
1199
+ To continue taking non-deterministic result, use `.take` / `.take!` instead.
1200
+ MSG
1201
+ assert_deprecated(message) do
1202
+ assert Post.order(:title).reorder(nil).first
1203
+ end
1027
1204
  end
1028
1205
  assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
1029
1206
  assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
1030
1207
  end
1031
1208
 
1032
-
1033
1209
  # We are not doing order duplicate removal anymore.
1034
1210
  coerce_tests! :test_order_using_scoping
1035
1211
 
@@ -1055,7 +1231,7 @@ class RelationTest < ActiveRecord::TestCase
1055
1231
 
1056
1232
  # Can't apply offset without ORDER
1057
1233
  coerce_tests! %r{using a custom table affects the wheres}
1058
- test 'using a custom table affects the wheres coerced' do
1234
+ test "using a custom table affects the wheres coerced" do
1059
1235
  post = posts(:welcome)
1060
1236
 
1061
1237
  assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
@@ -1063,7 +1239,7 @@ class RelationTest < ActiveRecord::TestCase
1063
1239
 
1064
1240
  # Can't apply offset without ORDER
1065
1241
  coerce_tests! %r{using a custom table with joins affects the joins}
1066
- test 'using a custom table with joins affects the joins coerced' do
1242
+ test "using a custom table with joins affects the joins coerced" do
1067
1243
  post = posts(:welcome)
1068
1244
 
1069
1245
  assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
@@ -1077,10 +1253,32 @@ class RelationTest < ActiveRecord::TestCase
1077
1253
  end
1078
1254
  end
1079
1255
 
1256
+ module ActiveRecord
1257
+ class RelationTest < ActiveRecord::TestCase
1258
+ # Skipping this test. SQL Server doesn't support optimizer hint as comments
1259
+ coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
1260
+
1261
+ coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
1262
+ def test_does_not_duplicate_optimizer_hints_on_merge_coerced
1263
+ escaped_table = Post.connection.quote_table_name("posts")
1264
+ expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
1265
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
1266
+ assert_equal expected, query
1267
+ end
1080
1268
 
1269
+ # Original Rails test fails on Windows CI because the dump file was not being binary read.
1270
+ coerce_tests! :test_marshal_load_legacy_relation
1271
+ def test_marshal_load_legacy_relation_coerced
1272
+ path = File.expand_path(
1273
+ "support/marshal_compatibility_fixtures/legacy_relation.dump",
1274
+ ARTest::SQLServer.root_activerecord_test
1275
+ )
1276
+ assert_equal 11, Marshal.load(File.binread(path)).size
1277
+ end
1278
+ end
1279
+ end
1081
1280
 
1082
-
1083
- require 'models/post'
1281
+ require "models/post"
1084
1282
  class SanitizeTest < ActiveRecord::TestCase
1085
1283
  # Use nvarchar string (N'') in assert
1086
1284
  coerce_tests! :test_sanitize_sql_like_example_use_case
@@ -1090,7 +1288,7 @@ class SanitizeTest < ActiveRecord::TestCase
1090
1288
  where("title LIKE ?", sanitize_sql_like(term, "!"))
1091
1289
  end
1092
1290
 
1093
- scope :search_as_scope, -> (term) {
1291
+ scope :search_as_scope, ->(term) {
1094
1292
  where("title LIKE ?", sanitize_sql_like(term, "!"))
1095
1293
  }
1096
1294
  end
@@ -1105,9 +1303,6 @@ class SanitizeTest < ActiveRecord::TestCase
1105
1303
  end
1106
1304
  end
1107
1305
 
1108
-
1109
-
1110
-
1111
1306
  class SchemaDumperTest < ActiveRecord::TestCase
1112
1307
  # We have precision to 38.
1113
1308
  coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
@@ -1134,26 +1329,17 @@ class SchemaDumperTest < ActiveRecord::TestCase
1134
1329
  end
1135
1330
  end
1136
1331
 
1137
-
1138
-
1139
-
1140
1332
  class SchemaDumperDefaultsTest < ActiveRecord::TestCase
1141
1333
  # These date formats do not match ours. We got these covered in our dumper tests.
1142
1334
  coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
1143
1335
  end
1144
1336
 
1145
-
1146
-
1147
-
1148
1337
  class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
1149
1338
  # We trust Rails on this since we do not want to install mysql.
1150
1339
  coerce_tests! %r{inspect on Model class does not raise}
1151
1340
  end
1152
1341
 
1153
-
1154
-
1155
-
1156
- require 'models/topic'
1342
+ require "models/topic"
1157
1343
  class TransactionTest < ActiveRecord::TestCase
1158
1344
  # SQL Server does not have query for release_savepoint
1159
1345
  coerce_tests! :test_releasing_named_savepoints
@@ -1167,10 +1353,7 @@ class TransactionTest < ActiveRecord::TestCase
1167
1353
  end
1168
1354
  end
1169
1355
 
1170
-
1171
-
1172
-
1173
- require 'models/tag'
1356
+ require "models/tag"
1174
1357
  class TransactionIsolationTest < ActiveRecord::TestCase
1175
1358
  # SQL Server will lock the table for counts even when both
1176
1359
  # connections are `READ COMMITTED`. So we bypass with `READPAST`.
@@ -1180,7 +1363,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase
1180
1363
  assert_equal 0, Tag.count
1181
1364
  Tag2.transaction do
1182
1365
  Tag2.create
1183
- assert_equal 0, Tag.lock('WITH(READPAST)').count
1366
+ assert_equal 0, Tag.lock("WITH(READPAST)").count
1184
1367
  end
1185
1368
  end
1186
1369
  assert_equal 1, Tag.count
@@ -1190,10 +1373,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase
1190
1373
  coerce_tests! %r{repeatable read}
1191
1374
  end
1192
1375
 
1193
-
1194
-
1195
-
1196
- require 'models/book'
1376
+ require "models/book"
1197
1377
  class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
1198
1378
  # We have a few view tables. use includes vs equality.
1199
1379
  coerce_tests! :test_views
@@ -1205,13 +1385,10 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
1205
1385
  coerce_tests! :test_does_not_assume_id_column_as_primary_key
1206
1386
  def test_does_not_assume_id_column_as_primary_key_coerced
1207
1387
  model = Class.new(ActiveRecord::Base) { self.table_name = "ebooks" }
1208
- assert_equal 'id', model.primary_key
1388
+ assert_equal "id", model.primary_key
1209
1389
  end
1210
1390
  end
1211
1391
 
1212
-
1213
-
1214
-
1215
1392
  class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
1216
1393
  # We have a few view tables. use includes vs equality.
1217
1394
  coerce_tests! :test_views
@@ -1220,23 +1397,17 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
1220
1397
  end
1221
1398
  end
1222
1399
 
1223
-
1224
-
1225
-
1226
- require 'models/author'
1400
+ require "models/author"
1227
1401
  class YamlSerializationTest < ActiveRecord::TestCase
1228
1402
  coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
1229
1403
  def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
1230
- author = Author.select('authors.*, 5 as posts_count').first
1404
+ author = Author.select("authors.*, 5 as posts_count").first
1231
1405
  dumped = YAML.load(YAML.dump(author))
1232
1406
  assert_equal 5, author.posts_count
1233
1407
  assert_equal 5, dumped.posts_count
1234
1408
  end
1235
1409
  end
1236
1410
 
1237
-
1238
-
1239
-
1240
1411
  class DateTimePrecisionTest < ActiveRecord::TestCase
1241
1412
  # Original test had `7` which we support vs `8` which we use.
1242
1413
  coerce_tests! :test_invalid_datetime_precision_raises_error
@@ -1252,7 +1423,7 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
1252
1423
  coerce_tests! :test_datetime_precision_is_truncated_on_assignment
1253
1424
  def test_datetime_precision_is_truncated_on_assignment_coerced
1254
1425
  @connection.create_table(:foos, force: true)
1255
- @connection.add_column :foos, :created_at, :datetime, precision: 0
1426
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
1256
1427
  @connection.add_column :foos, :updated_at, :datetime, precision: 6
1257
1428
 
1258
1429
  time = ::Time.now.change(nsec: 123456789)
@@ -1269,9 +1440,6 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
1269
1440
  end
1270
1441
  end
1271
1442
 
1272
-
1273
-
1274
-
1275
1443
  class TimePrecisionTest < ActiveRecord::TestCase
1276
1444
  # datetime is rounded to increments of .000, .003, or .007 seconds
1277
1445
  coerce_tests! :test_time_precision_is_truncated_on_assignment
@@ -1297,9 +1465,6 @@ class TimePrecisionTest < ActiveRecord::TestCase
1297
1465
  coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
1298
1466
  end
1299
1467
 
1300
-
1301
-
1302
-
1303
1468
  class DefaultNumbersTest < ActiveRecord::TestCase
1304
1469
  # We do better with native types and do not return strings for everything.
1305
1470
  coerce_tests! :test_default_positive_integer
@@ -1326,9 +1491,6 @@ class DefaultNumbersTest < ActiveRecord::TestCase
1326
1491
  end
1327
1492
  end
1328
1493
 
1329
-
1330
-
1331
-
1332
1494
  module ActiveRecord
1333
1495
  class CollectionCacheKeyTest < ActiveRecord::TestCase
1334
1496
  # Will trust rails has this sorted since you cant offset without a limit.
@@ -1336,9 +1498,6 @@ module ActiveRecord
1336
1498
  end
1337
1499
  end
1338
1500
 
1339
-
1340
-
1341
-
1342
1501
  module ActiveRecord
1343
1502
  class CacheKeyTest < ActiveRecord::TestCase
1344
1503
  # Like Mysql2 and PostgreSQL, SQL Server doesn't return a string value for updated_at. In the Rails tests
@@ -1349,9 +1508,6 @@ module ActiveRecord
1349
1508
  end
1350
1509
  end
1351
1510
 
1352
-
1353
-
1354
-
1355
1511
  require "models/book"
1356
1512
  module ActiveRecord
1357
1513
  class StatementCacheTest < ActiveRecord::TestCase
@@ -1370,41 +1526,74 @@ module ActiveRecord
1370
1526
  end
1371
1527
  end
1372
1528
 
1373
-
1374
-
1375
-
1376
1529
  module ActiveRecord
1377
1530
  module ConnectionAdapters
1378
1531
  class SchemaCacheTest < ActiveRecord::TestCase
1532
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1533
+ coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1534
+
1535
+ # Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
1536
+ coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
1537
+
1538
+ # Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
1539
+ unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1540
+ def test_marshal_dump_and_load_with_gzip_coerced
1541
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
1542
+ end
1543
+ def test_marshal_dump_and_load_via_disk_coerced
1544
+ with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
1545
+ end
1546
+ end
1547
+
1379
1548
  private
1549
+
1550
+ def with_marshable_time_defaults
1551
+ # Detect problems
1552
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
1553
+ column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
1554
+ current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
1555
+ end
1556
+
1557
+ # Correct problems
1558
+ if current_default.present?
1559
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
1560
+ end
1561
+
1562
+ # Run original test
1563
+ yield
1564
+ ensure
1565
+ # Revert changes
1566
+ @connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
1567
+ end
1568
+
1380
1569
  # We need to give the full path for this to work.
1381
1570
  def schema_dump_path
1382
- File.join ARTest::SQLServer.root_activerecord, 'test/assets/schema_dump_5_1.yml'
1571
+ File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
1383
1572
  end
1384
1573
  end
1385
1574
  end
1386
1575
  end
1387
1576
 
1388
-
1389
-
1390
-
1577
+ require "models/post"
1578
+ require "models/comment"
1391
1579
  class UnsafeRawSqlTest < ActiveRecord::TestCase
1392
- # Use LEN() vs length() function.
1580
+ fixtures :posts
1581
+
1582
+ # Use LEN() vs length() function.
1393
1583
  coerce_tests! %r{order: always allows Arel}
1394
- test 'order: always allows Arel' do
1395
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1396
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
1584
+ test "order: always allows Arel" do
1585
+ titles = Post.order(Arel.sql("len(title)")).pluck(:title)
1397
1586
 
1398
- assert_equal ids_depr, ids_disabled
1587
+ assert_not_empty titles
1399
1588
  end
1400
1589
 
1401
1590
  # Use LEN() vs length() function.
1402
1591
  coerce_tests! %r{pluck: always allows Arel}
1403
1592
  test "pluck: always allows Arel" do
1404
- values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1405
- values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1593
+ excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
1594
+ values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
1406
1595
 
1407
- assert_equal values_depr, values_disabled
1596
+ assert_equal excepted_values, values
1408
1597
  end
1409
1598
 
1410
1599
  # Use LEN() vs length() function.
@@ -1412,16 +1601,75 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
1412
1601
  test "order: allows valid Array arguments" do
1413
1602
  ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1414
1603
 
1415
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "len(title)"]).pluck(:id) }
1416
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "len(title)"]).pluck(:id) }
1604
+ ids = Post.order(["author_id", "len(title)"]).pluck(:id)
1417
1605
 
1418
- assert_equal ids_expected, ids_depr
1419
- assert_equal ids_expected, ids_disabled
1606
+ assert_equal ids_expected, ids
1420
1607
  end
1421
- end
1422
1608
 
1609
+ test "order: allows string column names that are quoted" do
1610
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1611
+
1612
+ ids = Post.order("[id]").pluck(:id)
1613
+
1614
+ assert_equal ids_expected, ids
1615
+ end
1616
+
1617
+ test "order: allows string column names that are quoted with table" do
1618
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1619
+
1620
+ ids = Post.order("[posts].[id]").pluck(:id)
1621
+
1622
+ assert_equal ids_expected, ids
1623
+ end
1624
+
1625
+ test "order: allows string column names that are quoted with table and user" do
1626
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1627
+
1628
+ ids = Post.order("[dbo].[posts].[id]").pluck(:id)
1629
+
1630
+ assert_equal ids_expected, ids
1631
+ end
1632
+
1633
+ test "order: allows string column names that are quoted with table, user and database" do
1634
+ ids_expected = Post.order(Arel.sql("id")).pluck(:id)
1635
+
1636
+ ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
1637
+
1638
+ assert_equal ids_expected, ids
1639
+ end
1640
+
1641
+ test "pluck: allows string column name that are quoted" do
1642
+ titles_expected = Post.pluck(Arel.sql("title"))
1643
+
1644
+ titles = Post.pluck("[title]")
1645
+
1646
+ assert_equal titles_expected, titles
1647
+ end
1423
1648
 
1649
+ test "pluck: allows string column name that are quoted with table" do
1650
+ titles_expected = Post.pluck(Arel.sql("title"))
1424
1651
 
1652
+ titles = Post.pluck("[posts].[title]")
1653
+
1654
+ assert_equal titles_expected, titles
1655
+ end
1656
+
1657
+ test "pluck: allows string column name that are quoted with table and user" do
1658
+ titles_expected = Post.pluck(Arel.sql("title"))
1659
+
1660
+ titles = Post.pluck("[dbo].[posts].[title]")
1661
+
1662
+ assert_equal titles_expected, titles
1663
+ end
1664
+
1665
+ test "pluck: allows string column name that are quoted with table, user and database" do
1666
+ titles_expected = Post.pluck(Arel.sql("title"))
1667
+
1668
+ titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
1669
+
1670
+ assert_equal titles_expected, titles
1671
+ end
1672
+ end
1425
1673
 
1426
1674
  class ReservedWordTest < ActiveRecord::TestCase
1427
1675
  coerce_tests! :test_change_columns
@@ -1433,33 +1681,27 @@ class ReservedWordTest < ActiveRecord::TestCase
1433
1681
  end
1434
1682
  end
1435
1683
 
1436
-
1437
-
1438
-
1439
1684
  class OptimisticLockingTest < ActiveRecord::TestCase
1440
1685
  # We do not allow updating identities, but we can test using a non-identity key
1441
1686
  coerce_tests! :test_update_with_dirty_primary_key
1442
1687
  def test_update_with_dirty_primary_key_coerced
1443
1688
  assert_raises(ActiveRecord::RecordNotUnique) do
1444
- record = StringKeyObject.find('record1')
1445
- record.id = 'record2'
1689
+ record = StringKeyObject.find("record1")
1690
+ record.id = "record2"
1446
1691
  record.save!
1447
1692
  end
1448
1693
 
1449
- record = StringKeyObject.find('record1')
1450
- record.id = 'record42'
1694
+ record = StringKeyObject.find("record1")
1695
+ record.id = "record42"
1451
1696
  record.save!
1452
1697
 
1453
- assert StringKeyObject.find('record42')
1698
+ assert StringKeyObject.find("record42")
1454
1699
  assert_raises(ActiveRecord::RecordNotFound) do
1455
- StringKeyObject.find('record1')
1700
+ StringKeyObject.find("record1")
1456
1701
  end
1457
1702
  end
1458
1703
  end
1459
1704
 
1460
-
1461
-
1462
-
1463
1705
  class RelationMergingTest < ActiveRecord::TestCase
1464
1706
  # Use nvarchar string (N'') in assert
1465
1707
  coerce_tests! :test_merging_with_order_with_binds
@@ -1467,10 +1709,26 @@ class RelationMergingTest < ActiveRecord::TestCase
1467
1709
  relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1468
1710
  assert_equal ["title LIKE N'%suffix'"], relation.order_values
1469
1711
  end
1470
- end
1471
1712
 
1713
+ # Same as original but change first regexp to match sp_executesql binding syntax
1714
+ coerce_tests! :test_merge_doesnt_duplicate_same_clauses
1715
+ def test_merge_doesnt_duplicate_same_clauses_coerced
1716
+ david, mary, bob = authors(:david, :mary, :bob)
1717
+
1718
+ non_mary_and_bob = Author.where.not(id: [mary, bob])
1472
1719
 
1720
+ author_id = Author.connection.quote_table_name("authors.id")
1721
+ assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
1722
+ assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
1723
+ end
1473
1724
 
1725
+ only_david = Author.where("#{author_id} IN (?)", david)
1726
+
1727
+ assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
1728
+ assert_equal [david], only_david.merge(only_david)
1729
+ end
1730
+ end
1731
+ end
1474
1732
 
1475
1733
  module ActiveRecord
1476
1734
  class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
@@ -1481,9 +1739,6 @@ module ActiveRecord
1481
1739
  end
1482
1740
  end
1483
1741
 
1484
-
1485
-
1486
-
1487
1742
  require "models/book"
1488
1743
  class EnumTest < ActiveRecord::TestCase
1489
1744
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
@@ -1527,13 +1782,8 @@ class EnumTest < ActiveRecord::TestCase
1527
1782
  end
1528
1783
  end
1529
1784
 
1530
-
1531
-
1532
-
1533
-
1534
- require 'models/task'
1785
+ require "models/task"
1535
1786
  class QueryCacheExpiryTest < ActiveRecord::TestCase
1536
-
1537
1787
  # SQL Server does not support skipping or upserting duplicates.
1538
1788
  coerce_tests! :test_insert_all
1539
1789
  def test_insert_all_coerced
@@ -1563,10 +1813,10 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
1563
1813
  end
1564
1814
  end
1565
1815
 
1566
-
1567
-
1568
- require 'models/citation'
1816
+ require "models/citation"
1569
1817
  class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1818
+ fixtures :citations
1819
+
1570
1820
  # Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
1571
1821
  # could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
1572
1822
  # (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
@@ -1574,22 +1824,19 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1574
1824
  # unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
1575
1825
  # still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
1576
1826
  # adapter is 2,098.
1577
- coerce_tests! :test_eager_loading_too_may_ids
1578
- def test_eager_loading_too_may_ids_coerced
1827
+ coerce_tests! :test_eager_loading_too_many_ids
1828
+ def test_eager_loading_too_many_ids_coerced
1579
1829
  # Remove excess records.
1580
1830
  Citation.limit(32768).order(id: :desc).delete_all
1581
1831
 
1582
1832
  # Perform test
1583
1833
  citation_count = Citation.count
1584
- assert_sql(/WHERE \(\[citations\]\.\[id\] IN \(0, 1/) do
1834
+ assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
1585
1835
  assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
1586
1836
  end
1587
1837
  end
1588
1838
  end
1589
1839
 
1590
-
1591
-
1592
-
1593
1840
  class LogSubscriberTest < ActiveRecord::TestCase
1594
1841
  # Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
1595
1842
  coerce_tests! :test_vebose_query_logs
@@ -1598,9 +1845,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
1598
1845
  end
1599
1846
  end
1600
1847
 
1601
-
1602
-
1603
-
1604
1848
  class ActiveRecordSchemaTest < ActiveRecord::TestCase
1605
1849
  # Workaround for randomly failing test.
1606
1850
  coerce_tests! :test_has_primary_key
@@ -1610,31 +1854,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
1610
1854
  end
1611
1855
  end
1612
1856
 
1857
+ class ReloadModelsTest < ActiveRecord::TestCase
1858
+ # Skip test on Windows. The number of arguments passed to `IO.popen` in
1859
+ # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1860
+ coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1861
+ end
1613
1862
 
1863
+ require "models/post"
1864
+ class AnnotateTest < ActiveRecord::TestCase
1865
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1866
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1867
+ coerce_tests! :test_annotate_wraps_content_in_an_inline_comment
1868
+ def test_annotate_wraps_content_in_an_inline_comment_coerced
1869
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1870
+
1871
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1872
+ posts = Post.select(:id).annotate("foo")
1873
+ assert posts.first
1874
+ end
1875
+ end
1614
1876
 
1877
+ # Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
1878
+ # TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
1879
+ coerce_tests! :test_annotate_is_sanitized
1880
+ def test_annotate_is_sanitized_coerced
1881
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
1615
1882
 
1616
- module ActiveRecord
1617
- module ConnectionAdapters
1618
- class ReaperTest < ActiveRecord::TestCase
1619
- # Coerce can be removed if Rails version > 6.0.3
1620
- coerce_tests! :test_connection_pool_starts_reaper_in_fork unless Process.respond_to?(:fork)
1883
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1884
+ posts = Post.select(:id).annotate("*/foo/*")
1885
+ assert posts.first
1886
+ end
1887
+
1888
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
1889
+ posts = Post.select(:id).annotate("**//foo//**")
1890
+ assert posts.first
1891
+ end
1892
+
1893
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
1894
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
1895
+ assert posts.first
1896
+ end
1897
+
1898
+ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
1899
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
1900
+ assert posts.first
1621
1901
  end
1622
1902
  end
1623
1903
  end
1624
1904
 
1905
+ class MarshalSerializationTest < ActiveRecord::TestCase
1906
+ private
1907
+
1908
+ def marshal_fixture_path(file_name)
1909
+ File.expand_path(
1910
+ "support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
1911
+ ARTest::SQLServer.test_root_sqlserver
1912
+ )
1913
+ end
1914
+ end
1625
1915
 
1916
+ class NestedThroughAssociationsTest < ActiveRecord::TestCase
1917
+ # Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
1918
+ # "A column has been specified more than once in the order by list"
1919
+ # Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
1920
+ coerce_tests! :test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins, :test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
1921
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
1922
+ # preload table schemas
1923
+ Author.joins(:category_post_comments).first
1924
+
1925
+ assert_includes_and_joins_equal(
1926
+ Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
1927
+ [authors(:david), authors(:mary)], :category_post_comments
1928
+ )
1929
+ end
1626
1930
 
1931
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
1932
+ # preload table schemas
1933
+ Category.joins(:post_comments).first
1627
1934
 
1628
- class FixturesTest < ActiveRecord::TestCase
1629
- # Skip test on Windows. Skip can be removed when Rails PR https://github.com/rails/rails/pull/39234 has been merged.
1630
- coerce_tests! :test_binary_in_fixtures if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
1935
+ assert_includes_and_joins_equal(
1936
+ Category.where("comments.id" => comments(:more_greetings).id).order(:id),
1937
+ [categories(:general), categories(:technology)], :post_comments
1938
+ )
1939
+ end
1631
1940
  end
1632
1941
 
1942
+ class BasePreventWritesTest < ActiveRecord::TestCase
1943
+ # SQL Server does not have query for release_savepoint
1944
+ coerce_tests! %r{an empty transaction does not raise if preventing writes}
1945
+ test "an empty transaction does not raise if preventing writes coerced" do
1946
+ ActiveRecord::Base.while_preventing_writes do
1947
+ assert_queries(1, ignore_none: true) do
1948
+ Bird.transaction do
1949
+ ActiveRecord::Base.connection.materialize_transactions
1950
+ end
1951
+ end
1952
+ end
1953
+ end
1633
1954
 
1955
+ class BasePreventWritesLegacyTest < 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.connection_handler.while_preventing_writes do
1960
+ assert_queries(1, ignore_none: true) do
1961
+ Bird.transaction do
1962
+ ActiveRecord::Base.connection.materialize_transactions
1963
+ end
1964
+ end
1965
+ end
1966
+ end
1967
+ end
1968
+ end
1634
1969
 
1970
+ class MigratorTest < ActiveRecord::TestCase
1971
+ # Test fails on Windows AppVeyor CI for unknown reason.
1972
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1973
+ end
1635
1974
 
1636
- class ReloadModelsTest < ActiveRecord::TestCase
1637
- # Skip test on Windows. The number of arguements passed to `IO.popen` in
1638
- # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
1639
- coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
1975
+ class MultiDbMigratorTest < ActiveRecord::TestCase
1976
+ # Test fails on Windows AppVeyor CI for unknown reason.
1977
+ coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
1640
1978
  end