activerecord-sqlserver-adapter-odbc-extended 8.0.10
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.
- checksums.yaml +7 -0
- data/.github/issue_template.md +22 -0
- data/.github/workflows/ci.yml +32 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile.ci +14 -0
- data/Gemfile +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/RUNNING_UNIT_TESTS.md +38 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
- data/compose.ci.yaml +15 -0
- data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
- data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +648 -0
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +44 -0
- data/test/cases/coerced_tests.rb +2796 -0
- data/test/cases/column_test_sqlserver.rb +848 -0
- data/test/cases/connection_test_sqlserver.rb +138 -0
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +42 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
- data/test/cases/fetch_test_sqlserver.rb +88 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
- data/test/cases/helper_sqlserver.rb +61 -0
- data/test/cases/migration_test_sqlserver.rb +144 -0
- data/test/cases/order_test_sqlserver.rb +153 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +198 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
- data/test/cases/schema_test_sqlserver.rb +111 -0
- data/test/cases/trigger_test_sqlserver.rb +51 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +54 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +38 -0
- data/test/debug.rb +16 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/booking.rb +5 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/customers_view.rb +5 -0
- data/test/models/sqlserver/datatype.rb +5 -0
- data/test/models/sqlserver/datatype_migration.rb +10 -0
- data/test/models/sqlserver/dollar_table_name.rb +5 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +5 -0
- data/test/models/sqlserver/fk_has_pk.rb +5 -0
- data/test/models/sqlserver/natural_pk_data.rb +6 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
- data/test/models/sqlserver/no_pk_data.rb +5 -0
- data/test/models/sqlserver/object_default.rb +5 -0
- data/test/models/sqlserver/quoted_table.rb +9 -0
- data/test/models/sqlserver/quoted_view_1.rb +5 -0
- data/test/models/sqlserver/quoted_view_2.rb +5 -0
- data/test/models/sqlserver/sst_memory.rb +5 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +5 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
- data/test/models/sqlserver/string_defaults_view.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/tinyint_pk.rb +5 -0
- data/test/models/sqlserver/trigger.rb +17 -0
- data/test/models/sqlserver/trigger_history.rb +5 -0
- data/test/models/sqlserver/upper.rb +5 -0
- data/test/models/sqlserver/uppered.rb +5 -0
- data/test/models/sqlserver/uuid.rb +5 -0
- data/test/schema/datatypes/2012.sql +56 -0
- data/test/schema/enable-in-memory-oltp.sql +81 -0
- data/test/schema/sqlserver_specific_schema.rb +363 -0
- data/test/support/coerceable_test_sqlserver.rb +55 -0
- data/test/support/connection_reflection.rb +32 -0
- data/test/support/core_ext/query_cache.rb +38 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +46 -0
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +17 -0
- metadata +240 -0
|
@@ -0,0 +1,2796 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
require "models/event"
|
|
6
|
+
class UniquenessValidationTest < ActiveRecord::TestCase
|
|
7
|
+
# So sp_executesql swallows this exception. Run without prepared to see it.
|
|
8
|
+
coerce_tests! :test_validate_uniqueness_with_limit
|
|
9
|
+
def test_validate_uniqueness_with_limit_coerced
|
|
10
|
+
connection.unprepared_statement do
|
|
11
|
+
assert_raise(ActiveRecord::ValueTooLong) do
|
|
12
|
+
Event.create(title: "abcdefgh")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# So sp_executesql swallows this exception. Run without prepared to see it.
|
|
18
|
+
coerce_tests! :test_validate_uniqueness_with_limit_and_utf8
|
|
19
|
+
def test_validate_uniqueness_with_limit_and_utf8_coerced
|
|
20
|
+
connection.unprepared_statement do
|
|
21
|
+
assert_raise(ActiveRecord::ValueTooLong) do
|
|
22
|
+
Event.create(title: "一二三四五六七八")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
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)
|
|
31
|
+
|
|
32
|
+
topic1 = Topic.new(author_email_address: "david@loudthinking.com")
|
|
33
|
+
topic2 = Topic.new(author_email_address: "David@loudthinking.com")
|
|
34
|
+
|
|
35
|
+
assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
|
|
36
|
+
|
|
37
|
+
assert_not topic1.valid?
|
|
38
|
+
assert_not topic1.save
|
|
39
|
+
|
|
40
|
+
# Case insensitive collation (SQL_Latin1_General_CP1_CI_AS) by default.
|
|
41
|
+
# Should not allow "David" if "david" exists.
|
|
42
|
+
assert_not topic2.valid?
|
|
43
|
+
assert_not topic2.save
|
|
44
|
+
|
|
45
|
+
assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
|
|
46
|
+
assert_equal 1, Topic.where(author_email_address: "David@loudthinking.com").count
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class UniquenessValidationWithIndexTest < ActiveRecord::TestCase
|
|
51
|
+
# Need to explicitly set the WHERE clause to truthy.
|
|
52
|
+
coerce_tests! :test_partial_index
|
|
53
|
+
def test_partial_index_coerced
|
|
54
|
+
Topic.validates_uniqueness_of(:title)
|
|
55
|
+
@connection.add_index(:topics, :title, unique: true, where: "approved=1", name: :topics_index)
|
|
56
|
+
|
|
57
|
+
t = Topic.create!(title: "abc")
|
|
58
|
+
t.author_name = "John"
|
|
59
|
+
assert_queries_count(1) do
|
|
60
|
+
t.valid?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
require "models/event"
|
|
66
|
+
module ActiveRecord
|
|
67
|
+
class AdapterTest < ActiveRecord::TestCase
|
|
68
|
+
# Legacy binds are not supported.
|
|
69
|
+
coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
|
|
70
|
+
|
|
71
|
+
# As far as I can tell, SQL Server does not support null bytes in strings.
|
|
72
|
+
coerce_tests! :test_update_prepared_statement
|
|
73
|
+
|
|
74
|
+
# So sp_executesql swallows this exception. Run without prepared to see it.
|
|
75
|
+
coerce_tests! :test_value_limit_violations_are_translated_to_specific_exception
|
|
76
|
+
def test_value_limit_violations_are_translated_to_specific_exception_coerced
|
|
77
|
+
connection.unprepared_statement do
|
|
78
|
+
error = assert_raises(ActiveRecord::ValueTooLong) do
|
|
79
|
+
Event.create(title: "abcdefgh")
|
|
80
|
+
end
|
|
81
|
+
assert_not_nil error.cause
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module ActiveRecord
|
|
88
|
+
class AdapterPreventWritesTest < ActiveRecord::TestCase
|
|
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_is_called_while_preventing_writes
|
|
91
|
+
def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
|
|
92
|
+
Subscriber.send(:load_schema!)
|
|
93
|
+
original_test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
97
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
98
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
|
|
99
|
+
Subscriber.send(:load_schema!)
|
|
100
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
104
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
105
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
|
|
106
|
+
Subscriber.send(:load_schema!)
|
|
107
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
|
|
111
|
+
coerce_tests! :test_doesnt_error_when_a_select_query_has_encoding_errors
|
|
112
|
+
def test_doesnt_error_when_a_select_query_has_encoding_errors_coerced
|
|
113
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
114
|
+
# TinyTDS fail on encoding errors.
|
|
115
|
+
# But at least we can assert it fails in the client and not before when trying to match the query.
|
|
116
|
+
assert_raises ActiveRecord::StatementInvalid do
|
|
117
|
+
@connection.select_all("SELECT '\xC8'")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
module ActiveRecord
|
|
125
|
+
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
|
|
126
|
+
# SQL Server does not allow truncation of tables that are referenced by foreign key
|
|
127
|
+
# constraints. So manually remove/add foreign keys in test.
|
|
128
|
+
coerce_tests! :test_truncate_tables
|
|
129
|
+
def test_truncate_tables_coerced
|
|
130
|
+
# Remove foreign key constraint to allow truncation.
|
|
131
|
+
@connection.remove_foreign_key :authors, :author_addresses
|
|
132
|
+
|
|
133
|
+
assert_operator Post.count, :>, 0
|
|
134
|
+
assert_operator Author.count, :>, 0
|
|
135
|
+
assert_operator AuthorAddress.count, :>, 0
|
|
136
|
+
|
|
137
|
+
@connection.truncate_tables("author_addresses", "authors", "posts")
|
|
138
|
+
|
|
139
|
+
assert_equal 0, Post.count
|
|
140
|
+
assert_equal 0, Author.count
|
|
141
|
+
assert_equal 0, AuthorAddress.count
|
|
142
|
+
ensure
|
|
143
|
+
reset_fixtures("posts", "authors", "author_addresses")
|
|
144
|
+
|
|
145
|
+
# Restore foreign key constraint.
|
|
146
|
+
@connection.add_foreign_key :authors, :author_addresses
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# SQL Server does not allow truncation of tables that are referenced by foreign key
|
|
150
|
+
# constraints. So manually remove/add foreign keys in test.
|
|
151
|
+
coerce_tests! :test_truncate_tables_with_query_cache
|
|
152
|
+
def test_truncate_tables_with_query_cache
|
|
153
|
+
# Remove foreign key constraint to allow truncation.
|
|
154
|
+
@connection.remove_foreign_key :authors, :author_addresses
|
|
155
|
+
|
|
156
|
+
@connection.enable_query_cache!
|
|
157
|
+
|
|
158
|
+
assert_operator Post.count, :>, 0
|
|
159
|
+
assert_operator Author.count, :>, 0
|
|
160
|
+
assert_operator AuthorAddress.count, :>, 0
|
|
161
|
+
|
|
162
|
+
@connection.truncate_tables("author_addresses", "authors", "posts")
|
|
163
|
+
|
|
164
|
+
assert_equal 0, Post.count
|
|
165
|
+
assert_equal 0, Author.count
|
|
166
|
+
assert_equal 0, AuthorAddress.count
|
|
167
|
+
ensure
|
|
168
|
+
reset_fixtures("posts", "authors", "author_addresses")
|
|
169
|
+
@connection.disable_query_cache!
|
|
170
|
+
|
|
171
|
+
# Restore foreign key constraint.
|
|
172
|
+
@connection.add_foreign_key :authors, :author_addresses
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
require "models/topic"
|
|
178
|
+
class AttributeMethodsTest < ActiveRecord::TestCase
|
|
179
|
+
# Use IFF for boolean statement in SELECT
|
|
180
|
+
coerce_tests! %r{typecast attribute from select to false}
|
|
181
|
+
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
|
|
184
|
+
assert_not_predicate topic, :is_test?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Use IFF for boolean statement in SELECT
|
|
188
|
+
coerce_tests! %r{typecast attribute from select to true}
|
|
189
|
+
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
|
|
192
|
+
assert_predicate topic, :is_test?
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
class BasicsTest < ActiveRecord::TestCase
|
|
197
|
+
# Use square brackets as SQL Server escaped character
|
|
198
|
+
coerce_tests! :test_column_names_are_escaped
|
|
199
|
+
def test_column_names_are_escaped_coerced
|
|
200
|
+
conn = ActiveRecord::Base.lease_connection
|
|
201
|
+
assert_equal "[t]]]", conn.quote_column_name("t]")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Just like PostgreSQLAdapter does.
|
|
205
|
+
coerce_tests! :test_respect_internal_encoding
|
|
206
|
+
|
|
207
|
+
# Caused in Rails v4.2.5 by adding `firm_id` column in this http://git.io/vBfMs
|
|
208
|
+
# commit. Trust Rails has this covered.
|
|
209
|
+
coerce_tests! :test_find_keeps_multiple_group_values
|
|
210
|
+
|
|
211
|
+
def test_update_date_time_attributes
|
|
212
|
+
Time.use_zone("Eastern Time (US & Canada)") do
|
|
213
|
+
topic = Topic.find(1)
|
|
214
|
+
time = Time.zone.parse("2017-07-17 10:56")
|
|
215
|
+
topic.update!(written_on: time)
|
|
216
|
+
assert_equal(time, topic.written_on)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_update_date_time_attributes_with_default_timezone_local
|
|
221
|
+
with_env_tz "America/New_York" do
|
|
222
|
+
with_timezone_config default: :local do
|
|
223
|
+
Time.use_zone("Eastern Time (US & Canada)") do
|
|
224
|
+
topic = Topic.find(1)
|
|
225
|
+
time = Time.zone.parse("2017-07-17 10:56")
|
|
226
|
+
topic.update!(written_on: time)
|
|
227
|
+
assert_equal(time, topic.written_on)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|
235
|
+
# Since @client.firm is a single first/top, and we use FETCH the order clause is used.
|
|
236
|
+
coerce_tests! :test_belongs_to_does_not_use_order_by
|
|
237
|
+
|
|
238
|
+
# Square brackets around column name
|
|
239
|
+
coerce_tests! :test_belongs_to_with_primary_key_joins_on_correct_column
|
|
240
|
+
def test_belongs_to_with_primary_key_joins_on_correct_column_coerced
|
|
241
|
+
sql = Client.joins(:firm_with_primary_key).to_sql
|
|
242
|
+
assert_no_match(/\[firm_with_primary_keys_companies\]\.\[id\]/, sql)
|
|
243
|
+
assert_match(/\[firm_with_primary_keys_companies\]\.\[name\]/, sql)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Asserted SQL to get one row different from original test.
|
|
247
|
+
coerce_tests! :test_belongs_to
|
|
248
|
+
def test_belongs_to_coerced
|
|
249
|
+
client = Client.find(3)
|
|
250
|
+
first_firm = companies(:first_firm)
|
|
251
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
|
|
252
|
+
assert_equal first_firm, client.firm
|
|
253
|
+
assert_equal first_firm.name, client.firm.name
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
module ActiveRecord
|
|
259
|
+
class BindParameterTest < ActiveRecord::TestCase
|
|
260
|
+
# Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
|
|
261
|
+
coerce_tests! :test_binds_are_logged
|
|
262
|
+
def test_binds_are_logged_coerced
|
|
263
|
+
sub = Arel::Nodes::BindParam.new(1)
|
|
264
|
+
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
|
|
265
|
+
sql = "select * from topics where id = #{sub.to_sql}"
|
|
266
|
+
|
|
267
|
+
@connection.exec_query(sql, "SQL", binds)
|
|
268
|
+
|
|
269
|
+
logged_sql = "EXEC sp_executesql N'#{sql}', N'#{sub.to_sql} int', #{sub.to_sql} = 1"
|
|
270
|
+
message = @subscriber.calls.find { |args| args[4][:sql] == logged_sql }
|
|
271
|
+
|
|
272
|
+
assert_equal binds, message[4][:binds]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
|
|
276
|
+
coerce_tests! :test_statement_cache
|
|
277
|
+
coerce_tests! :test_statement_cache_with_query_cache
|
|
278
|
+
coerce_tests! :test_statement_cache_with_find
|
|
279
|
+
coerce_tests! :test_statement_cache_with_find_by
|
|
280
|
+
coerce_tests! :test_statement_cache_with_in_clause
|
|
281
|
+
coerce_tests! :test_statement_cache_with_sql_string_literal
|
|
282
|
+
|
|
283
|
+
# Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
|
|
284
|
+
coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
|
|
285
|
+
def test_bind_params_to_sql_with_prepared_statements_coerced
|
|
286
|
+
assert_bind_params_to_sql_coerced(prepared: true)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def test_bind_params_to_sql_with_unprepared_statements_coerced
|
|
290
|
+
@connection.unprepared_statement do
|
|
291
|
+
assert_bind_params_to_sql_coerced(prepared: false)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
private
|
|
296
|
+
|
|
297
|
+
def assert_bind_params_to_sql_coerced(prepared:)
|
|
298
|
+
table = Author.quoted_table_name
|
|
299
|
+
pk = "#{table}.#{Author.quoted_primary_key}"
|
|
300
|
+
|
|
301
|
+
# prepared_statements: true
|
|
302
|
+
#
|
|
303
|
+
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
304
|
+
#
|
|
305
|
+
# prepared_statements: false
|
|
306
|
+
#
|
|
307
|
+
# SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
|
|
308
|
+
#
|
|
309
|
+
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
|
|
310
|
+
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
311
|
+
|
|
312
|
+
authors = Author.where(id: [1, 2, 3, nil])
|
|
313
|
+
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
314
|
+
assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
315
|
+
|
|
316
|
+
# prepared_statements: true
|
|
317
|
+
#
|
|
318
|
+
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
319
|
+
#
|
|
320
|
+
# prepared_statements: false
|
|
321
|
+
#
|
|
322
|
+
# SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
|
|
323
|
+
#
|
|
324
|
+
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
|
|
325
|
+
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
326
|
+
|
|
327
|
+
authors = Author.where(id: [1, 2, 3, 9223372036854775808])
|
|
328
|
+
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
329
|
+
assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
module ActiveRecord
|
|
335
|
+
class InstrumentationTest < ActiveRecord::TestCase
|
|
336
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
337
|
+
coerce_tests! :test_payload_name_on_load
|
|
338
|
+
def test_payload_name_on_load_coerced
|
|
339
|
+
Book.send(:load_schema!)
|
|
340
|
+
original_test_payload_name_on_load
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
344
|
+
coerce_tests! :test_payload_row_count_on_select_all
|
|
345
|
+
def test_payload_row_count_on_select_all_coerced
|
|
346
|
+
connection.remove_index(:books, column: [:author_id, :name])
|
|
347
|
+
|
|
348
|
+
original_test_payload_row_count_on_select_all
|
|
349
|
+
ensure
|
|
350
|
+
Book.where(author_id: nil, name: 'row count book 1').delete_all
|
|
351
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
355
|
+
coerce_tests! :test_payload_row_count_on_pluck
|
|
356
|
+
def test_payload_row_count_on_pluck_coerced
|
|
357
|
+
connection.remove_index(:books, column: [:author_id, :name])
|
|
358
|
+
|
|
359
|
+
original_test_payload_row_count_on_pluck
|
|
360
|
+
ensure
|
|
361
|
+
Book.where(author_id: nil, name: 'row count book 2').delete_all
|
|
362
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
366
|
+
coerce_tests! :test_payload_row_count_on_raw_sql
|
|
367
|
+
def test_payload_row_count_on_raw_sql_coerced
|
|
368
|
+
connection.remove_index(:books, column: [:author_id, :name])
|
|
369
|
+
|
|
370
|
+
original_test_payload_row_count_on_raw_sql
|
|
371
|
+
ensure
|
|
372
|
+
Book.where(author_id: nil, name: 'row count book 3').delete_all
|
|
373
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
class CalculationsTest < ActiveRecord::TestCase
|
|
379
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
380
|
+
coerce_tests! :test_offset_is_kept
|
|
381
|
+
def test_offset_is_kept_coerced
|
|
382
|
+
Account.send(:load_schema!)
|
|
383
|
+
original_test_offset_is_kept
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
|
|
387
|
+
coerce_tests! :test_should_return_decimal_average_of_integer_field
|
|
388
|
+
def test_should_return_decimal_average_of_integer_field_coerced
|
|
389
|
+
value = Account.average(:id)
|
|
390
|
+
assert_equal 3, value
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
394
|
+
# Match SQL Server limit implementation.
|
|
395
|
+
coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
|
|
396
|
+
def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
|
|
397
|
+
rails_core = companies(:rails_core)
|
|
398
|
+
|
|
399
|
+
sql = <<~SQL
|
|
400
|
+
SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
|
|
401
|
+
FROM accounts
|
|
402
|
+
WHERE firm_id = ?
|
|
403
|
+
GROUP BY firm_id
|
|
404
|
+
ORDER BY firm_id
|
|
405
|
+
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
|
406
|
+
SQL
|
|
407
|
+
|
|
408
|
+
account = Account.find_by_sql([sql, rails_core]).first
|
|
409
|
+
|
|
410
|
+
# id was not selected, so it should be nil
|
|
411
|
+
# (cannot select id because it wasn't used in the GROUP BY clause)
|
|
412
|
+
assert_nil account.id
|
|
413
|
+
|
|
414
|
+
# firm_id was explicitly selected, so it should be present
|
|
415
|
+
assert_equal(rails_core, account.firm)
|
|
416
|
+
|
|
417
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
418
|
+
assert_equal(52.5, account.avg_credit_limit)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
422
|
+
# Order column must be in the GROUP clause.
|
|
423
|
+
coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
|
|
424
|
+
def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
|
|
425
|
+
rails_core = companies(:rails_core)
|
|
426
|
+
|
|
427
|
+
account = Account
|
|
428
|
+
.select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
|
|
429
|
+
.where(firm: rails_core)
|
|
430
|
+
.group(:firm_id)
|
|
431
|
+
.order(:firm_id)
|
|
432
|
+
.take!
|
|
433
|
+
|
|
434
|
+
# id was not selected, so it should be nil
|
|
435
|
+
# (cannot select id because it wasn't used in the GROUP BY clause)
|
|
436
|
+
assert_nil account.id
|
|
437
|
+
|
|
438
|
+
# firm_id was explicitly selected, so it should be present
|
|
439
|
+
assert_equal(rails_core, account.firm)
|
|
440
|
+
|
|
441
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
442
|
+
assert_equal(52.5, account.avg_credit_limit)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
446
|
+
# SELECT columns must be in the GROUP clause.
|
|
447
|
+
# Match SQL Server limit implementation.
|
|
448
|
+
coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
|
|
449
|
+
def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
|
|
450
|
+
rails_core = companies(:rails_core)
|
|
451
|
+
|
|
452
|
+
sql = <<~SQL
|
|
453
|
+
SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
|
|
454
|
+
FROM companies
|
|
455
|
+
INNER JOIN accounts ON companies.id = accounts.firm_id
|
|
456
|
+
WHERE companies.id = ?
|
|
457
|
+
GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description, companies.status
|
|
458
|
+
ORDER BY companies.id
|
|
459
|
+
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
|
460
|
+
SQL
|
|
461
|
+
|
|
462
|
+
firm = DependentFirm.find_by_sql([sql, rails_core]).first
|
|
463
|
+
|
|
464
|
+
# all the DependentFirm attributes should be present
|
|
465
|
+
assert_equal rails_core, firm
|
|
466
|
+
assert_equal rails_core.name, firm.name
|
|
467
|
+
|
|
468
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
469
|
+
assert_equal(52.5, firm.avg_credit_limit)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
474
|
+
# SELECT columns must be in the GROUP clause.
|
|
475
|
+
coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
|
|
476
|
+
def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
|
|
477
|
+
rails_core = companies(:rails_core)
|
|
478
|
+
|
|
479
|
+
firm = DependentFirm
|
|
480
|
+
.select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
|
|
481
|
+
.where(id: rails_core)
|
|
482
|
+
.joins(:account)
|
|
483
|
+
.group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description, :status)
|
|
484
|
+
.take!
|
|
485
|
+
|
|
486
|
+
# all the DependentFirm attributes should be present
|
|
487
|
+
assert_equal rails_core, firm
|
|
488
|
+
assert_equal rails_core.name, firm.name
|
|
489
|
+
|
|
490
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
491
|
+
assert_equal(52.5, firm.avg_credit_limit)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Match SQL Server limit implementation
|
|
495
|
+
coerce_tests! :test_limit_is_kept
|
|
496
|
+
def test_limit_is_kept_coerced
|
|
497
|
+
queries = capture_sql { Account.limit(1).count }
|
|
498
|
+
assert_equal 1, queries.length
|
|
499
|
+
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/, queries.first)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Match SQL Server limit implementation
|
|
503
|
+
coerce_tests! :test_limit_with_offset_is_kept
|
|
504
|
+
def test_limit_with_offset_is_kept_coerced
|
|
505
|
+
queries = capture_sql { Account.limit(1).offset(1).count }
|
|
506
|
+
assert_equal 1, queries.length
|
|
507
|
+
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1/, queries.first)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# SQL Server needs an alias for the calculated column
|
|
511
|
+
coerce_tests! :test_distinct_count_all_with_custom_select_and_order
|
|
512
|
+
def test_distinct_count_all_with_custom_select_and_order_coerced
|
|
513
|
+
accounts = Account.distinct.select("credit_limit % 10 AS the_limit").order(Arel.sql("credit_limit % 10"))
|
|
514
|
+
assert_queries_count(1) { assert_equal 3, accounts.count(:all) }
|
|
515
|
+
assert_queries_count(1) { assert_equal 3, accounts.load.size }
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Leave it up to users to format selects/functions so HAVING works correctly.
|
|
519
|
+
coerce_tests! :test_having_with_strong_parameters
|
|
520
|
+
|
|
521
|
+
# SELECT columns must be in the GROUP clause. Since since `ids` only selects the primary key you cannot perform this query in SQL Server.
|
|
522
|
+
coerce_tests! :test_ids_with_includes_and_non_primary_key_order
|
|
523
|
+
|
|
524
|
+
# To limit the results in SQL Server we use `FETCH NEXT @0 ROWS ONLY` instead of `LIMIT @0`. To use `FETCH NEXT` an order must be provided.
|
|
525
|
+
coerce_tests! :test_no_order_by_when_counting_all
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
module ActiveRecord
|
|
529
|
+
class Migration
|
|
530
|
+
class ChangeSchemaTest < ActiveRecord::TestCase
|
|
531
|
+
# Integer.default is a number and not a string
|
|
532
|
+
coerce_tests! :test_create_table_with_defaults
|
|
533
|
+
def test_create_table_with_defaults_coerce
|
|
534
|
+
connection.create_table :testings do |t|
|
|
535
|
+
t.column :one, :string, default: "hello"
|
|
536
|
+
t.column :two, :boolean, default: true
|
|
537
|
+
t.column :three, :boolean, default: false
|
|
538
|
+
t.column :four, :integer, default: 1
|
|
539
|
+
t.column :five, :text, default: "hello"
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
columns = connection.columns(:testings)
|
|
543
|
+
one = columns.detect { |c| c.name == "one" }
|
|
544
|
+
two = columns.detect { |c| c.name == "two" }
|
|
545
|
+
three = columns.detect { |c| c.name == "three" }
|
|
546
|
+
four = columns.detect { |c| c.name == "four" }
|
|
547
|
+
five = columns.detect { |c| c.name == "five" }
|
|
548
|
+
|
|
549
|
+
assert_equal "hello", one.default
|
|
550
|
+
assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
|
|
551
|
+
assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
|
|
552
|
+
assert_equal 1, four.default
|
|
553
|
+
assert_equal "hello", five.default
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
|
|
557
|
+
coerce_tests! :test_add_column_with_postgresql_datetime_type
|
|
558
|
+
def test_add_column_with_postgresql_datetime_type_coerced
|
|
559
|
+
connection.create_table :testings do |t|
|
|
560
|
+
t.column :foo, :datetime
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
column = connection.columns(:testings).find { |c| c.name == "foo" }
|
|
564
|
+
|
|
565
|
+
assert_equal :datetime, column.type
|
|
566
|
+
assert_equal "datetime2(6)", column.sql_type
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
|
|
570
|
+
coerce_tests! :test_change_column_with_timestamp_type
|
|
571
|
+
def test_change_column_with_timestamp_type_coerced
|
|
572
|
+
connection.create_table :testings do |t|
|
|
573
|
+
t.column :foo, :datetime, null: false
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
connection.change_column :testings, :foo, :timestamp
|
|
577
|
+
|
|
578
|
+
column = connection.columns(:testings).find { |c| c.name == "foo" }
|
|
579
|
+
|
|
580
|
+
assert_equal :datetime, column.type
|
|
581
|
+
assert_equal "datetime2(6)", column.sql_type
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Use precision 6 by default for datetime/timestamp columns. SQL Server uses `datetime2` for date-times with precision.
|
|
585
|
+
coerce_tests! :test_add_column_with_timestamp_type
|
|
586
|
+
def test_add_column_with_timestamp_type_coerced
|
|
587
|
+
connection.create_table :testings do |t|
|
|
588
|
+
t.column :foo, :timestamp
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
column = connection.columns(:testings).find { |c| c.name == "foo" }
|
|
592
|
+
|
|
593
|
+
assert_equal :datetime, column.type
|
|
594
|
+
assert_equal "datetime2(6)", column.sql_type
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
module ActiveRecord
|
|
601
|
+
class Migration
|
|
602
|
+
class ColumnAttributesTest < ActiveRecord::TestCase
|
|
603
|
+
# We have a default 4000 varying character limit.
|
|
604
|
+
coerce_tests! :test_add_column_without_limit
|
|
605
|
+
def test_add_column_without_limit_coerced
|
|
606
|
+
add_column :test_models, :description, :string, limit: nil
|
|
607
|
+
TestModel.reset_column_information
|
|
608
|
+
_(TestModel.columns_hash["description"].limit).must_equal 4000
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
module ActiveRecord
|
|
615
|
+
class Migration
|
|
616
|
+
class ColumnsTest < ActiveRecord::TestCase
|
|
617
|
+
# Our defaults are real 70000 integers vs '70000' strings.
|
|
618
|
+
coerce_tests! :test_rename_column_preserves_default_value_not_null
|
|
619
|
+
def test_rename_column_preserves_default_value_not_null_coerced
|
|
620
|
+
add_column "test_models", "salary", :integer, :default => 70000
|
|
621
|
+
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
|
|
622
|
+
assert_equal 70000, default_before
|
|
623
|
+
rename_column "test_models", "salary", "annual_salary"
|
|
624
|
+
TestModel.reset_column_information
|
|
625
|
+
assert TestModel.column_names.include?("annual_salary")
|
|
626
|
+
default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
|
|
627
|
+
assert_equal 70000, default_after
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# Dropping the column removes the single index.
|
|
631
|
+
coerce_tests! :test_remove_column_with_multi_column_index
|
|
632
|
+
def test_remove_column_with_multi_column_index_coerced
|
|
633
|
+
add_column "test_models", :hat_size, :integer
|
|
634
|
+
add_column "test_models", :hat_style, :string, :limit => 100
|
|
635
|
+
add_index "test_models", ["hat_style", "hat_size"], :unique => true
|
|
636
|
+
assert_equal 1, connection.indexes("test_models").size
|
|
637
|
+
remove_column("test_models", "hat_size")
|
|
638
|
+
assert_equal [], connection.indexes("test_models").map(&:name)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Choose `StatementInvalid` vs `ActiveRecordError`.
|
|
642
|
+
coerce_tests! :test_rename_nonexistent_column
|
|
643
|
+
def test_rename_nonexistent_column_coerced
|
|
644
|
+
exception = ActiveRecord::StatementInvalid
|
|
645
|
+
assert_raise(exception) do
|
|
646
|
+
rename_column "test_models", "nonexistent", "should_fail"
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
class MigrationTest < ActiveRecord::TestCase
|
|
654
|
+
# For some reason our tests set Rails.@_env which breaks test env switching.
|
|
655
|
+
coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
|
|
656
|
+
coerce_tests! :test_internal_metadata_stores_environment
|
|
657
|
+
|
|
658
|
+
# Same as original but using binary type instead of blob
|
|
659
|
+
coerce_tests! :test_add_column_with_casted_type_if_not_exists_set_to_true
|
|
660
|
+
def test_add_column_with_casted_type_if_not_exists_set_to_true_coerced
|
|
661
|
+
migration_a = Class.new(ActiveRecord::Migration::Current) {
|
|
662
|
+
def version; 100 end
|
|
663
|
+
def migrate(x)
|
|
664
|
+
add_column "people", "last_name", :binary
|
|
665
|
+
end
|
|
666
|
+
}.new
|
|
667
|
+
|
|
668
|
+
migration_b = Class.new(ActiveRecord::Migration::Current) {
|
|
669
|
+
def version; 101 end
|
|
670
|
+
def migrate(x)
|
|
671
|
+
add_column "people", "last_name", :binary, if_not_exists: true
|
|
672
|
+
end
|
|
673
|
+
}.new
|
|
674
|
+
|
|
675
|
+
ActiveRecord::Migrator.new(:up, [migration_a], @schema_migration, @internal_metadata, 100).migrate
|
|
676
|
+
assert_column Person, :last_name, "migration_a should have created the last_name column on people"
|
|
677
|
+
|
|
678
|
+
assert_nothing_raised do
|
|
679
|
+
ActiveRecord::Migrator.new(:up, [migration_b], @schema_migration, @internal_metadata, 101).migrate
|
|
680
|
+
end
|
|
681
|
+
ensure
|
|
682
|
+
Person.reset_column_information
|
|
683
|
+
if Person.column_names.include?("last_name")
|
|
684
|
+
Person.lease_connection.remove_column("people", "last_name")
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
module ActiveRecord
|
|
690
|
+
class Migration
|
|
691
|
+
class CompatibilityTest < ActiveRecord::TestCase
|
|
692
|
+
# Error message depends on the database adapter.
|
|
693
|
+
coerce_tests! :test_create_table_on_7_0
|
|
694
|
+
def test_create_table_on_7_0_coerced
|
|
695
|
+
long_table_name = "a" * (connection.table_name_length + 1)
|
|
696
|
+
migration = Class.new(ActiveRecord::Migration[7.0]) {
|
|
697
|
+
@@long_table_name = long_table_name
|
|
698
|
+
def version; 100 end
|
|
699
|
+
def migrate(x)
|
|
700
|
+
create_table @@long_table_name
|
|
701
|
+
end
|
|
702
|
+
}.new
|
|
703
|
+
|
|
704
|
+
error = assert_raises(StandardError) do
|
|
705
|
+
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
|
706
|
+
end
|
|
707
|
+
assert_match(/The identifier that starts with '#{long_table_name[0...-1]}' is too long/i, error.message)
|
|
708
|
+
ensure
|
|
709
|
+
connection.drop_table(long_table_name) rescue nil
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# 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).
|
|
713
|
+
coerce_tests! :test_rename_table_on_7_0
|
|
714
|
+
def test_rename_table_on_7_0_coerced
|
|
715
|
+
long_table_name = "a" * (connection.table_name_length + 1)
|
|
716
|
+
connection.create_table(:more_testings)
|
|
717
|
+
|
|
718
|
+
migration = Class.new(ActiveRecord::Migration[7.0]) {
|
|
719
|
+
@@long_table_name = long_table_name
|
|
720
|
+
def version; 100 end
|
|
721
|
+
def migrate(x)
|
|
722
|
+
rename_table :more_testings, @@long_table_name
|
|
723
|
+
end
|
|
724
|
+
}.new
|
|
725
|
+
|
|
726
|
+
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
|
727
|
+
assert connection.table_exists?(long_table_name[0...-1])
|
|
728
|
+
assert_not connection.table_exists?(:more_testings)
|
|
729
|
+
assert connection.table_exists?(long_table_name[0...-1])
|
|
730
|
+
ensure
|
|
731
|
+
connection.drop_table(:more_testings) rescue nil
|
|
732
|
+
connection.drop_table(long_table_name[0...-1]) rescue nil
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# SQL Server has a different maximum index name length.
|
|
736
|
+
coerce_tests! :test_add_index_errors_on_too_long_name_7_0
|
|
737
|
+
def test_add_index_errors_on_too_long_name_7_0_coerced
|
|
738
|
+
long_index_name = 'a' * (connection.index_name_length + 1)
|
|
739
|
+
|
|
740
|
+
migration = Class.new(ActiveRecord::Migration[7.0]) {
|
|
741
|
+
@@long_index_name = long_index_name
|
|
742
|
+
def migrate(x)
|
|
743
|
+
add_column :testings, :very_long_column_name_to_test_with, :string
|
|
744
|
+
add_index :testings, [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
|
|
745
|
+
end
|
|
746
|
+
}.new
|
|
747
|
+
|
|
748
|
+
error = assert_raises(StandardError) do
|
|
749
|
+
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
|
750
|
+
end
|
|
751
|
+
assert_match(/Index name \'#{long_index_name}\' on table \'testings\' is too long/i, error.message)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# SQL Server has a different maximum index name length.
|
|
755
|
+
coerce_tests! :test_create_table_add_index_errors_on_too_long_name_7_0
|
|
756
|
+
def test_create_table_add_index_errors_on_too_long_name_7_0_coerced
|
|
757
|
+
long_index_name = 'a' * (connection.index_name_length + 1)
|
|
758
|
+
|
|
759
|
+
migration = Class.new(ActiveRecord::Migration[7.0]) {
|
|
760
|
+
@@long_index_name = long_index_name
|
|
761
|
+
def migrate(x)
|
|
762
|
+
create_table :more_testings do |t|
|
|
763
|
+
t.integer :foo
|
|
764
|
+
t.integer :bar
|
|
765
|
+
t.integer :very_long_column_name_to_test_with
|
|
766
|
+
t.index [:foo, :bar, :very_long_column_name_to_test_with], name: @@long_index_name
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
}.new
|
|
770
|
+
|
|
771
|
+
error = assert_raises(StandardError) do
|
|
772
|
+
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
|
773
|
+
end
|
|
774
|
+
assert_match(/Index name \'#{long_index_name}\' on table \'more_testings\' is too long/i, error.message)
|
|
775
|
+
ensure
|
|
776
|
+
connection.drop_table :more_testings rescue nil
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
class CoreTest < ActiveRecord::TestCase
|
|
783
|
+
# I think fixtures are using the wrong time zone and the `:first`
|
|
784
|
+
# `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
|
|
785
|
+
# getting local EST time for me and set to "09:28:00.0000000".
|
|
786
|
+
coerce_tests! :test_pretty_print_persisted
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
module ActiveRecord
|
|
790
|
+
module ConnectionAdapters
|
|
791
|
+
# Just like PostgreSQLAdapter does.
|
|
792
|
+
TypeLookupTest.coerce_all_tests! if defined?(TypeLookupTest)
|
|
793
|
+
|
|
794
|
+
# All sorts of errors due to how we test. Even setting ENV['RAILS_ENV'] to
|
|
795
|
+
# a value of 'default_env' will still show tests failing. Just ignoring all
|
|
796
|
+
# of them since we have no monkey in this circus.
|
|
797
|
+
MergeAndResolveDefaultUrlConfigTest.coerce_all_tests! if defined?(MergeAndResolveDefaultUrlConfigTest)
|
|
798
|
+
ConnectionHandlerTest.coerce_all_tests! if defined?(ConnectionHandlerTest)
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
module ActiveRecord
|
|
803
|
+
# The original module is hardcoded for PostgreSQL/SQLite/MySQL tests.
|
|
804
|
+
module DatabaseTasksSetupper
|
|
805
|
+
undef_method :setup
|
|
806
|
+
def setup
|
|
807
|
+
@sqlserver_tasks =
|
|
808
|
+
Class.new do
|
|
809
|
+
def create; end
|
|
810
|
+
|
|
811
|
+
def drop; end
|
|
812
|
+
|
|
813
|
+
def purge; end
|
|
814
|
+
|
|
815
|
+
def charset; end
|
|
816
|
+
|
|
817
|
+
def collation; end
|
|
818
|
+
|
|
819
|
+
def structure_dump(*); end
|
|
820
|
+
|
|
821
|
+
def structure_load(*); end
|
|
822
|
+
end.new
|
|
823
|
+
|
|
824
|
+
$stdout, @original_stdout = StringIO.new, $stdout
|
|
825
|
+
$stderr, @original_stderr = StringIO.new, $stderr
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
undef_method :with_stubbed_new
|
|
829
|
+
def with_stubbed_new
|
|
830
|
+
ActiveRecord::Tasks::SQLServerDatabaseTasks.stub(:new, @sqlserver_tasks) do
|
|
831
|
+
yield
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
class DatabaseTasksCreateTest < ActiveRecord::TestCase
|
|
837
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
838
|
+
coerce_all_tests!
|
|
839
|
+
|
|
840
|
+
def test_sqlserver_create
|
|
841
|
+
with_stubbed_new do
|
|
842
|
+
assert_called(eval("@sqlserver_tasks"), :create) do
|
|
843
|
+
ActiveRecord::Tasks::DatabaseTasks.create "adapter" => :sqlserver
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
class DatabaseTasksDropTest < ActiveRecord::TestCase
|
|
850
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
851
|
+
coerce_all_tests!
|
|
852
|
+
|
|
853
|
+
def test_sqlserver_drop
|
|
854
|
+
with_stubbed_new do
|
|
855
|
+
assert_called(eval("@sqlserver_tasks"), :drop) do
|
|
856
|
+
ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => :sqlserver
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
class DatabaseTasksPurgeTest < ActiveRecord::TestCase
|
|
863
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
864
|
+
coerce_all_tests!
|
|
865
|
+
|
|
866
|
+
def test_sqlserver_purge
|
|
867
|
+
with_stubbed_new do
|
|
868
|
+
assert_called(eval("@sqlserver_tasks"), :purge) do
|
|
869
|
+
ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => :sqlserver
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
|
|
876
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
877
|
+
coerce_all_tests!
|
|
878
|
+
|
|
879
|
+
def test_sqlserver_charset
|
|
880
|
+
with_stubbed_new do
|
|
881
|
+
assert_called(eval("@sqlserver_tasks"), :charset) do
|
|
882
|
+
ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => :sqlserver
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
class DatabaseTasksCollationTest < ActiveRecord::TestCase
|
|
889
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
890
|
+
coerce_all_tests!
|
|
891
|
+
|
|
892
|
+
def test_sqlserver_collation
|
|
893
|
+
with_stubbed_new do
|
|
894
|
+
assert_called(eval("@sqlserver_tasks"), :collation) do
|
|
895
|
+
ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => :sqlserver
|
|
896
|
+
end
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
|
|
902
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
903
|
+
coerce_all_tests!
|
|
904
|
+
|
|
905
|
+
def test_sqlserver_structure_dump
|
|
906
|
+
with_stubbed_new do
|
|
907
|
+
assert_called_with(
|
|
908
|
+
eval("@sqlserver_tasks"), :structure_dump,
|
|
909
|
+
["awesome-file.sql", nil]
|
|
910
|
+
) do
|
|
911
|
+
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :sqlserver }, "awesome-file.sql")
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
|
|
918
|
+
# Coerce PostgreSQL/SQLite/MySQL tests.
|
|
919
|
+
coerce_all_tests!
|
|
920
|
+
|
|
921
|
+
def test_sqlserver_structure_load
|
|
922
|
+
with_stubbed_new do
|
|
923
|
+
assert_called_with(
|
|
924
|
+
eval("@sqlserver_tasks"),
|
|
925
|
+
:structure_load,
|
|
926
|
+
["awesome-file.sql", nil]
|
|
927
|
+
) do
|
|
928
|
+
ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => :sqlserver }, "awesome-file.sql")
|
|
929
|
+
end
|
|
930
|
+
end
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
|
|
935
|
+
# We extend `local_database?` so that common VM IPs can be used.
|
|
936
|
+
coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
class DatabaseTasksDropAllTest < ActiveRecord::TestCase
|
|
940
|
+
# We extend `local_database?` so that common VM IPs can be used.
|
|
941
|
+
coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
class DefaultScopingTest < ActiveRecord::TestCase
|
|
946
|
+
# We are not doing order duplicate removal anymore.
|
|
947
|
+
coerce_tests! :test_order_in_default_scope_should_not_prevail
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
class EagerAssociationTest < ActiveRecord::TestCase
|
|
951
|
+
# Use LEN() instead of LENGTH() function.
|
|
952
|
+
coerce_tests! :test_count_with_include
|
|
953
|
+
def test_count_with_include_coerced
|
|
954
|
+
assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
# The raw SQL in the scope uses `limit 1`.
|
|
958
|
+
coerce_tests! %r{including association based on sql condition and no database column}
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
require "models/topic"
|
|
962
|
+
require "models/customer"
|
|
963
|
+
require "models/non_primary_key"
|
|
964
|
+
class FinderTest < ActiveRecord::TestCase
|
|
965
|
+
fixtures :customers, :topics, :authors
|
|
966
|
+
|
|
967
|
+
# We have implicit ordering, via FETCH.
|
|
968
|
+
coerce_tests! %r{doesn't have implicit ordering},
|
|
969
|
+
:test_find_doesnt_have_implicit_ordering
|
|
970
|
+
|
|
971
|
+
# Assert SQL Server limit implementation
|
|
972
|
+
coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
|
|
973
|
+
def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
|
|
974
|
+
assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 3/) { Topic.take(3).entries }
|
|
975
|
+
assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 2/) { Topic.first(2).entries }
|
|
976
|
+
assert_queries_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.* @0 = 5/) { Topic.last(5).entries }
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
# This fails only when run in the full test suite task. Just taking it out of the mix.
|
|
980
|
+
coerce_tests! :test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
|
|
981
|
+
|
|
982
|
+
# Can not use array condition due to not finding right type and hence fractional second quoting.
|
|
983
|
+
coerce_tests! :test_condition_utc_time_interpolation_with_default_timezone_local
|
|
984
|
+
def test_condition_utc_time_interpolation_with_default_timezone_local_coerced
|
|
985
|
+
with_env_tz "America/New_York" do
|
|
986
|
+
with_timezone_config default: :local do
|
|
987
|
+
topic = Topic.first
|
|
988
|
+
assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
# Can not use array condition due to not finding right type and hence fractional second quoting.
|
|
994
|
+
coerce_tests! :test_condition_local_time_interpolation_with_default_timezone_utc
|
|
995
|
+
def test_condition_local_time_interpolation_with_default_timezone_utc_coerced
|
|
996
|
+
with_env_tz "America/New_York" do
|
|
997
|
+
with_timezone_config default: :utc do
|
|
998
|
+
topic = Topic.first
|
|
999
|
+
assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1005
|
+
coerce_tests! :test_include_on_unloaded_relation_with_match
|
|
1006
|
+
def test_include_on_unloaded_relation_with_match_coerced
|
|
1007
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
1008
|
+
assert_equal true, Customer.where(name: "David").include?(customers(:david))
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1013
|
+
coerce_tests! :test_include_on_unloaded_relation_without_match
|
|
1014
|
+
def test_include_on_unloaded_relation_without_match_coerced
|
|
1015
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
1016
|
+
assert_equal false, Customer.where(name: "David").include?(customers(:mary))
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1021
|
+
coerce_tests! :test_member_on_unloaded_relation_with_match
|
|
1022
|
+
def test_member_on_unloaded_relation_with_match_coerced
|
|
1023
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
1024
|
+
assert_equal true, Customer.where(name: "David").member?(customers(:david))
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1029
|
+
coerce_tests! :test_member_on_unloaded_relation_without_match
|
|
1030
|
+
def test_member_on_unloaded_relation_without_match_coerced
|
|
1031
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
1032
|
+
assert_equal false, Customer.where(name: "David").member?(customers(:mary))
|
|
1033
|
+
end
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1037
|
+
coerce_tests! :test_implicit_order_column_is_configurable
|
|
1038
|
+
def test_implicit_order_column_is_configurable_coerced
|
|
1039
|
+
old_implicit_order_column = Topic.implicit_order_column
|
|
1040
|
+
Topic.implicit_order_column = "title"
|
|
1041
|
+
|
|
1042
|
+
assert_equal topics(:fifth), Topic.first
|
|
1043
|
+
assert_equal topics(:third), Topic.last
|
|
1044
|
+
|
|
1045
|
+
c = Topic.lease_connection
|
|
1046
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
1047
|
+
Topic.last
|
|
1048
|
+
}
|
|
1049
|
+
ensure
|
|
1050
|
+
Topic.implicit_order_column = old_implicit_order_column
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1054
|
+
coerce_tests! :test_implicit_order_set_to_primary_key
|
|
1055
|
+
def test_implicit_order_set_to_primary_key_coerced
|
|
1056
|
+
old_implicit_order_column = Topic.implicit_order_column
|
|
1057
|
+
Topic.implicit_order_column = "id"
|
|
1058
|
+
|
|
1059
|
+
c = Topic.lease_connection
|
|
1060
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
1061
|
+
Topic.last
|
|
1062
|
+
}
|
|
1063
|
+
ensure
|
|
1064
|
+
Topic.implicit_order_column = old_implicit_order_column
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1068
|
+
coerce_tests! :test_implicit_order_for_model_without_primary_key
|
|
1069
|
+
def test_implicit_order_for_model_without_primary_key_coerced
|
|
1070
|
+
old_implicit_order_column = NonPrimaryKey.implicit_order_column
|
|
1071
|
+
NonPrimaryKey.implicit_order_column = "created_at"
|
|
1072
|
+
|
|
1073
|
+
c = NonPrimaryKey.lease_connection
|
|
1074
|
+
|
|
1075
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
1076
|
+
NonPrimaryKey.last
|
|
1077
|
+
}
|
|
1078
|
+
ensure
|
|
1079
|
+
NonPrimaryKey.implicit_order_column = old_implicit_order_column
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1083
|
+
coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
|
|
1084
|
+
def test_member_on_unloaded_relation_with_composite_primary_key_coerced
|
|
1085
|
+
assert_queries_match(/1 AS one.* FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
|
|
1086
|
+
book = cpk_books(:cpk_great_author_first_book)
|
|
1087
|
+
assert Cpk::Book.where(title: "The first book").member?(book)
|
|
1088
|
+
end
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1092
|
+
coerce_tests! :test_implicit_order_column_prepends_query_constraints
|
|
1093
|
+
def test_implicit_order_column_prepends_query_constraints_coerced
|
|
1094
|
+
c = ClothingItem.lease_connection
|
|
1095
|
+
ClothingItem.implicit_order_column = "description"
|
|
1096
|
+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1097
|
+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1098
|
+
quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
|
|
1099
|
+
|
|
1100
|
+
assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
|
|
1101
|
+
assert_kind_of ClothingItem, ClothingItem.first
|
|
1102
|
+
end
|
|
1103
|
+
ensure
|
|
1104
|
+
ClothingItem.implicit_order_column = nil
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1108
|
+
coerce_tests! %r{#last for a model with composite query constraints}
|
|
1109
|
+
test "#last for a model with composite query constraints coerced" do
|
|
1110
|
+
c = ClothingItem.lease_connection
|
|
1111
|
+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1112
|
+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1113
|
+
|
|
1114
|
+
assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
|
|
1115
|
+
assert_kind_of ClothingItem, ClothingItem.last
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1120
|
+
coerce_tests! %r{#first for a model with composite query constraints}
|
|
1121
|
+
test "#first for a model with composite query constraints coerced" do
|
|
1122
|
+
c = ClothingItem.lease_connection
|
|
1123
|
+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1124
|
+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1125
|
+
|
|
1126
|
+
assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
|
|
1127
|
+
assert_kind_of ClothingItem, ClothingItem.first
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1132
|
+
coerce_tests! :test_implicit_order_column_reorders_query_constraints
|
|
1133
|
+
def test_implicit_order_column_reorders_query_constraints_coerced
|
|
1134
|
+
c = ClothingItem.lease_connection
|
|
1135
|
+
ClothingItem.implicit_order_column = "color"
|
|
1136
|
+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1137
|
+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1138
|
+
|
|
1139
|
+
assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/i) do
|
|
1140
|
+
assert_kind_of ClothingItem, ClothingItem.first
|
|
1141
|
+
end
|
|
1142
|
+
ensure
|
|
1143
|
+
ClothingItem.implicit_order_column = nil
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1147
|
+
coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
|
|
1148
|
+
def test_include_on_unloaded_relation_with_composite_primary_key_coerced
|
|
1149
|
+
assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1/) do
|
|
1150
|
+
book = cpk_books(:cpk_great_author_first_book)
|
|
1151
|
+
assert Cpk::Book.where(title: "The first book").include?(book)
|
|
1152
|
+
end
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1156
|
+
coerce_tests! :test_nth_to_last_with_order_uses_limit
|
|
1157
|
+
def test_nth_to_last_with_order_uses_limit_coerced
|
|
1158
|
+
c = Topic.lease_connection
|
|
1159
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
|
|
1160
|
+
Topic.second_to_last
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY.*@\1 = 1.*@\2 = 1/i) do
|
|
1164
|
+
Topic.order(:updated_at).second_to_last
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
# SQL Server is unable to use aliased SELECT in the HAVING clause.
|
|
1169
|
+
coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
module ActiveRecord
|
|
1173
|
+
class Migration
|
|
1174
|
+
class ForeignKeyTest < ActiveRecord::TestCase
|
|
1175
|
+
# SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
|
|
1176
|
+
coerce_tests! :test_add_on_delete_restrict_foreign_key
|
|
1177
|
+
def test_add_on_delete_restrict_foreign_key_coerced
|
|
1178
|
+
assert_raises ArgumentError do
|
|
1179
|
+
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict
|
|
1180
|
+
end
|
|
1181
|
+
assert_raises ArgumentError do
|
|
1182
|
+
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :restrict
|
|
1183
|
+
end
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
# SQL Server does not support 'restrict' for 'on_update' or 'on_delete'.
|
|
1187
|
+
coerce_tests! :test_remove_foreign_key_with_restrict_action
|
|
1188
|
+
|
|
1189
|
+
# Error message depends on the database adapter.
|
|
1190
|
+
coerce_tests! :test_add_foreign_key_with_if_not_exists_not_set
|
|
1191
|
+
def test_add_foreign_key_with_if_not_exists_not_set_coerced
|
|
1192
|
+
@connection.add_foreign_key :astronauts, :rockets
|
|
1193
|
+
assert_equal 1, @connection.foreign_keys("astronauts").size
|
|
1194
|
+
|
|
1195
|
+
error = assert_raises do
|
|
1196
|
+
@connection.add_foreign_key :astronauts, :rockets
|
|
1197
|
+
end
|
|
1198
|
+
|
|
1199
|
+
assert_match(/TinyTds::Error: There is already an object named '.*' in the database/, error.message)
|
|
1200
|
+
end
|
|
1201
|
+
end
|
|
1202
|
+
end
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
1206
|
+
# We use OFFSET/FETCH vs TOP. So we always have an order.
|
|
1207
|
+
coerce_tests! :test_has_one_does_not_use_order_by
|
|
1208
|
+
|
|
1209
|
+
# Asserted SQL to get one row different from original test.
|
|
1210
|
+
coerce_tests! :test_has_one
|
|
1211
|
+
def test_has_one_coerced
|
|
1212
|
+
firm = companies(:first_firm)
|
|
1213
|
+
first_account = Account.find(1)
|
|
1214
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
|
|
1215
|
+
assert_equal first_account, firm.account
|
|
1216
|
+
assert_equal first_account.credit_limit, firm.account.credit_limit
|
|
1217
|
+
end
|
|
1218
|
+
end
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|
1222
|
+
# Asserted SQL to get one row different from original test.
|
|
1223
|
+
coerce_tests! :test_has_one_through_executes_limited_query
|
|
1224
|
+
def test_has_one_through_executes_limited_query_coerced
|
|
1225
|
+
boring_club = clubs(:boring_club)
|
|
1226
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY(.)*@\1 = 1/) do
|
|
1227
|
+
assert_equal boring_club, @member.general_club
|
|
1228
|
+
end
|
|
1229
|
+
end
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
|
|
1233
|
+
# Uses || operator in SQL. Just trust core gets value out of this test.
|
|
1234
|
+
coerce_tests! :test_does_not_override_select
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
require "models/developer"
|
|
1238
|
+
require "models/computer"
|
|
1239
|
+
class NestedRelationScopingTest < ActiveRecord::TestCase
|
|
1240
|
+
# Assert SQL Server limit implementation
|
|
1241
|
+
coerce_tests! :test_merge_options
|
|
1242
|
+
def test_merge_options_coerced
|
|
1243
|
+
Developer.where("salary = 80000").scoping do
|
|
1244
|
+
Developer.limit(10).scoping do
|
|
1245
|
+
devs = Developer.all
|
|
1246
|
+
sql = devs.to_sql
|
|
1247
|
+
assert_match "(salary = 80000)", sql
|
|
1248
|
+
assert_match "FETCH NEXT 10 ROWS ONLY", sql
|
|
1249
|
+
end
|
|
1250
|
+
end
|
|
1251
|
+
end
|
|
1252
|
+
end
|
|
1253
|
+
|
|
1254
|
+
require "models/topic"
|
|
1255
|
+
class PersistenceTest < ActiveRecord::TestCase
|
|
1256
|
+
# Rails test required updating a identity column.
|
|
1257
|
+
coerce_tests! :test_update_columns_changing_id
|
|
1258
|
+
|
|
1259
|
+
# Rails test required updating a identity column.
|
|
1260
|
+
coerce_tests! :test_update
|
|
1261
|
+
def test_update_coerced
|
|
1262
|
+
topic = Topic.find(1)
|
|
1263
|
+
assert_not_predicate topic, :approved?
|
|
1264
|
+
assert_equal "The First Topic", topic.title
|
|
1265
|
+
|
|
1266
|
+
topic.update("approved" => true, "title" => "The First Topic Updated")
|
|
1267
|
+
topic.reload
|
|
1268
|
+
assert_predicate topic, :approved?
|
|
1269
|
+
assert_equal "The First Topic Updated", topic.title
|
|
1270
|
+
|
|
1271
|
+
topic.update(approved: false, title: "The First Topic")
|
|
1272
|
+
topic.reload
|
|
1273
|
+
assert_not_predicate topic, :approved?
|
|
1274
|
+
assert_equal "The First Topic", topic.title
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
# In SQL Server it's not possible to set the primary key column using a trigger and to get it then to return.
|
|
1278
|
+
coerce_tests! :test_model_with_no_auto_populated_fields_still_returns_primary_key_after_insert
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
require "models/author"
|
|
1282
|
+
class UpdateAllTest < ActiveRecord::TestCase
|
|
1283
|
+
# Rails test required updating a identity column.
|
|
1284
|
+
coerce_tests! :test_update_all_doesnt_ignore_order
|
|
1285
|
+
def test_update_all_doesnt_ignore_order_coerced
|
|
1286
|
+
david, mary = authors(:david), authors(:mary)
|
|
1287
|
+
_(david.id).must_equal 1
|
|
1288
|
+
_(mary.id).must_equal 2
|
|
1289
|
+
_(david.name).wont_equal mary.name
|
|
1290
|
+
assert_queries_match(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
|
|
1291
|
+
Author.where("[id] > 1").order(:id).update_all(name: "Test")
|
|
1292
|
+
end
|
|
1293
|
+
_(david.reload.name).must_equal "David"
|
|
1294
|
+
_(mary.reload.name).must_equal "Test"
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
# SELECT columns must be in the GROUP clause.
|
|
1298
|
+
coerce_tests! :test_update_all_with_group_by
|
|
1299
|
+
def test_update_all_with_group_by_coerced
|
|
1300
|
+
minimum_comments_count = 2
|
|
1301
|
+
|
|
1302
|
+
Post.most_commented(minimum_comments_count).update_all(title: "ig")
|
|
1303
|
+
posts = Post.select(:id, :title).group(:title).most_commented(minimum_comments_count).all.to_a
|
|
1304
|
+
|
|
1305
|
+
assert_operator posts.length, :>, 0
|
|
1306
|
+
assert posts.all? { |post| post.comments.length >= minimum_comments_count }
|
|
1307
|
+
assert posts.all? { |post| "ig" == post.title }
|
|
1308
|
+
|
|
1309
|
+
post = Post.select(:id, :title).group(:title).joins(:comments).group("posts.id").having("count(comments.id) < #{minimum_comments_count}").first
|
|
1310
|
+
assert_not_equal "ig", post.title
|
|
1311
|
+
end
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
class DeleteAllTest < ActiveRecord::TestCase
|
|
1315
|
+
# SELECT columns must be in the GROUP clause.
|
|
1316
|
+
coerce_tests! :test_delete_all_with_group_by_and_having
|
|
1317
|
+
def test_delete_all_with_group_by_and_having_coerced
|
|
1318
|
+
minimum_comments_count = 2
|
|
1319
|
+
posts_to_be_deleted = Post.select(:id).most_commented(minimum_comments_count).all.to_a
|
|
1320
|
+
assert_operator posts_to_be_deleted.length, :>, 0
|
|
1321
|
+
|
|
1322
|
+
assert_difference("Post.count", -posts_to_be_deleted.length) do
|
|
1323
|
+
Post.most_commented(minimum_comments_count).delete_all
|
|
1324
|
+
end
|
|
1325
|
+
|
|
1326
|
+
posts_to_be_deleted.each do |deleted_post|
|
|
1327
|
+
assert_raise(ActiveRecord::RecordNotFound) { deleted_post.reload }
|
|
1328
|
+
end
|
|
1329
|
+
end
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
require "models/topic"
|
|
1333
|
+
module ActiveRecord
|
|
1334
|
+
class PredicateBuilderTest < ActiveRecord::TestCase
|
|
1335
|
+
# Same as original test except string has `N` prefix to indicate unicode string.
|
|
1336
|
+
coerce_tests! :test_registering_new_handlers
|
|
1337
|
+
def test_registering_new_handlers_coerced
|
|
1338
|
+
assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Topic.where(title: /rails/).to_sql
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# Same as original test except string has `N` prefix to indicate unicode string.
|
|
1342
|
+
coerce_tests! :test_registering_new_handlers_for_association
|
|
1343
|
+
def test_registering_new_handlers_for_association_coerced
|
|
1344
|
+
assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
|
|
1345
|
+
end
|
|
1346
|
+
|
|
1347
|
+
# Same as original test except string has `N` prefix to indicate unicode string.
|
|
1348
|
+
coerce_tests! :test_registering_new_handlers_for_joins
|
|
1349
|
+
def test_registering_new_handlers_for_joins_coerced
|
|
1350
|
+
Reply.belongs_to :regexp_topic, -> { where(title: /rails/) }, class_name: "Topic", foreign_key: "parent_id"
|
|
1351
|
+
|
|
1352
|
+
assert_match %r{#{Regexp.escape(quote_table_name("regexp_topic.title"))} ~ N'rails'}i, Reply.joins(:regexp_topic).references(Arel.sql("regexp_topic")).to_sql
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
private
|
|
1356
|
+
|
|
1357
|
+
def topic_title
|
|
1358
|
+
Topic.lease_connection.quote_table_name("topics.title")
|
|
1359
|
+
end
|
|
1360
|
+
end
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
require "models/task"
|
|
1364
|
+
class QueryCacheTest < ActiveRecord::TestCase
|
|
1365
|
+
# SQL Server adapter not in list of supported adapters in original test.
|
|
1366
|
+
coerce_tests! :test_cache_does_not_wrap_results_in_arrays
|
|
1367
|
+
def test_cache_does_not_wrap_results_in_arrays_coerced
|
|
1368
|
+
Task.cache do
|
|
1369
|
+
assert_equal 2, Task.lease_connection.select_value("SELECT count(*) AS count_all FROM tasks")
|
|
1370
|
+
end
|
|
1371
|
+
end
|
|
1372
|
+
end
|
|
1373
|
+
|
|
1374
|
+
require "models/post"
|
|
1375
|
+
class RelationTest < ActiveRecord::TestCase
|
|
1376
|
+
# Use LEN() instead of LENGTH() function.
|
|
1377
|
+
coerce_tests! :test_reverse_order_with_function
|
|
1378
|
+
def test_reverse_order_with_function_coerced
|
|
1379
|
+
topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
|
|
1380
|
+
assert_equal topics(:second).title, topics.first.title
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
# Use LEN() instead of LENGTH() function.
|
|
1384
|
+
coerce_tests! :test_reverse_order_with_function_other_predicates
|
|
1385
|
+
def test_reverse_order_with_function_other_predicates_coerced
|
|
1386
|
+
topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
|
|
1387
|
+
assert_equal topics(:second).title, topics.first.title
|
|
1388
|
+
topics = Topic.order(Arel.sql("LEN(author_name), id, LEN(title)")).reverse_order
|
|
1389
|
+
assert_equal topics(:fifth).title, topics.first.title
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
# We have implicit ordering, via FETCH.
|
|
1393
|
+
coerce_tests! %r{doesn't have implicit ordering}
|
|
1394
|
+
|
|
1395
|
+
# We have implicit ordering, via FETCH.
|
|
1396
|
+
coerce_tests! :test_reorder_with_take
|
|
1397
|
+
def test_reorder_with_take_coerced
|
|
1398
|
+
sql_log = capture_sql do
|
|
1399
|
+
assert Post.order(:title).reorder(nil).take
|
|
1400
|
+
end
|
|
1401
|
+
assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
|
|
1402
|
+
assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
# We have implicit ordering, via FETCH.
|
|
1406
|
+
coerce_tests! :test_reorder_with_first
|
|
1407
|
+
def test_reorder_with_first_coerced
|
|
1408
|
+
post = nil
|
|
1409
|
+
sql_log = capture_sql do
|
|
1410
|
+
post = Post.order(:title).reorder(nil).first
|
|
1411
|
+
end
|
|
1412
|
+
assert_equal posts(:welcome), post
|
|
1413
|
+
assert sql_log.none? { |sql| /order by \[posts\]\.\[title\]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
|
|
1414
|
+
assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1417
|
+
# We are not doing order duplicate removal anymore.
|
|
1418
|
+
coerce_tests! :test_order_using_scoping
|
|
1419
|
+
|
|
1420
|
+
# We are not doing order duplicate removal anymore.
|
|
1421
|
+
coerce_tests! :test_default_scope_order_with_scope_order
|
|
1422
|
+
|
|
1423
|
+
# Order column must be in the GROUP clause.
|
|
1424
|
+
coerce_tests! :test_multiple_where_and_having_clauses
|
|
1425
|
+
def test_multiple_where_and_having_clauses_coerced
|
|
1426
|
+
post = Post.first
|
|
1427
|
+
having_then_where = Post.having(id: post.id).where(title: post.title)
|
|
1428
|
+
.having(id: post.id).where(title: post.title).group(:id).select(:id)
|
|
1429
|
+
|
|
1430
|
+
assert_equal [post], having_then_where
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1433
|
+
# Order column must be in the GROUP clause.
|
|
1434
|
+
coerce_tests! :test_having_with_binds_for_both_where_and_having
|
|
1435
|
+
def test_having_with_binds_for_both_where_and_having
|
|
1436
|
+
post = Post.first
|
|
1437
|
+
having_then_where = Post.having(id: post.id).where(title: post.title).group(:id).select(:id)
|
|
1438
|
+
where_then_having = Post.where(title: post.title).having(id: post.id).group(:id).select(:id)
|
|
1439
|
+
|
|
1440
|
+
assert_equal [post], having_then_where
|
|
1441
|
+
assert_equal [post], where_then_having
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
# Find any limit via our expression.
|
|
1445
|
+
coerce_tests! %r{relations don't load all records in #inspect}
|
|
1446
|
+
def test_relations_dont_load_all_records_in_inspect_coerced
|
|
1447
|
+
assert_queries_match(/NEXT @0 ROWS.*@0 = \d+/) do
|
|
1448
|
+
Post.all.inspect
|
|
1449
|
+
end
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1452
|
+
# Find any limit via our expression.
|
|
1453
|
+
coerce_tests! %r{relations don't load all records in #pretty_print}
|
|
1454
|
+
def test_relations_dont_load_all_records_in_pretty_print_coerced
|
|
1455
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
|
|
1456
|
+
PP.pp Post.all, StringIO.new # avoid outputting.
|
|
1457
|
+
end
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
# Order column must be in the GROUP clause.
|
|
1461
|
+
coerce_tests! :test_empty_complex_chained_relations
|
|
1462
|
+
def test_empty_complex_chained_relations_coerced
|
|
1463
|
+
posts = Post.select("comments_count").where("id is not null").group("author_id", "id").where("legacy_comments_count > 0")
|
|
1464
|
+
|
|
1465
|
+
assert_queries_count(1) { assert_equal false, posts.empty? }
|
|
1466
|
+
assert_not_predicate posts, :loaded?
|
|
1467
|
+
|
|
1468
|
+
no_posts = posts.where(title: "")
|
|
1469
|
+
assert_queries_count(1) { assert_equal true, no_posts.empty? }
|
|
1470
|
+
assert_not_predicate no_posts, :loaded?
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# Can't apply offset without ORDER
|
|
1474
|
+
coerce_tests! %r{using a custom table affects the wheres}
|
|
1475
|
+
test "using a custom table affects the wheres coerced" do
|
|
1476
|
+
post = posts(:welcome)
|
|
1477
|
+
|
|
1478
|
+
assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
|
|
1479
|
+
end
|
|
1480
|
+
|
|
1481
|
+
# Can't apply offset without ORDER
|
|
1482
|
+
coerce_tests! %r{using a custom table with joins affects the joins}
|
|
1483
|
+
test "using a custom table with joins affects the joins coerced" do
|
|
1484
|
+
post = posts(:welcome)
|
|
1485
|
+
|
|
1486
|
+
assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
# Use LEN() instead of LENGTH() function.
|
|
1490
|
+
coerce_tests! :test_reverse_arel_assoc_order_with_function
|
|
1491
|
+
def test_reverse_arel_assoc_order_with_function_coerced
|
|
1492
|
+
topics = Topic.order(Arel.sql("LEN(title)") => :asc).reverse_order
|
|
1493
|
+
assert_equal topics(:second).title, topics.first.title
|
|
1494
|
+
end
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
module ActiveRecord
|
|
1498
|
+
class RelationTest < ActiveRecord::TestCase
|
|
1499
|
+
# Skipping this test. SQL Server doesn't support optimizer hint as comments
|
|
1500
|
+
coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
|
|
1501
|
+
|
|
1502
|
+
coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
|
|
1503
|
+
def test_does_not_duplicate_optimizer_hints_on_merge_coerced
|
|
1504
|
+
escaped_table = Post.lease_connection.quote_table_name("posts")
|
|
1505
|
+
expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
|
|
1506
|
+
query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
|
|
1507
|
+
assert_equal expected, query
|
|
1508
|
+
end
|
|
1509
|
+
end
|
|
1510
|
+
end
|
|
1511
|
+
|
|
1512
|
+
require "models/post"
|
|
1513
|
+
class SanitizeTest < ActiveRecord::TestCase
|
|
1514
|
+
# Use nvarchar string (N'') in assert
|
|
1515
|
+
coerce_tests! :test_sanitize_sql_like_example_use_case
|
|
1516
|
+
def test_sanitize_sql_like_example_use_case_coerced
|
|
1517
|
+
searchable_post = Class.new(Post) do
|
|
1518
|
+
def self.search_as_method(term)
|
|
1519
|
+
where("title LIKE ?", sanitize_sql_like(term, "!"))
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
scope :search_as_scope, ->(term) {
|
|
1523
|
+
where("title LIKE ?", sanitize_sql_like(term, "!"))
|
|
1524
|
+
}
|
|
1525
|
+
end
|
|
1526
|
+
|
|
1527
|
+
assert_queries_match(/LIKE @0/) do
|
|
1528
|
+
searchable_post.search_as_method("20% _reduction_!").to_a
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
assert_queries_match(/LIKE @0/) do
|
|
1532
|
+
searchable_post.search_as_scope("20% _reduction_!").to_a
|
|
1533
|
+
end
|
|
1534
|
+
end
|
|
1535
|
+
|
|
1536
|
+
# Use nvarchar string (N'') in assert
|
|
1537
|
+
coerce_tests! :test_named_bind_with_literal_colons
|
|
1538
|
+
def test_named_bind_with_literal_colons_coerced
|
|
1539
|
+
assert_equal "TO_TIMESTAMP(N'2017/08/02 10:59:00', 'YYYY/MM/DD HH12:MI:SS')", bind("TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "2017/08/02 10:59:00")
|
|
1540
|
+
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
|
|
1541
|
+
end
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
class SchemaDumperTest < ActiveRecord::TestCase
|
|
1545
|
+
# Use nvarchar string (N'') in assert
|
|
1546
|
+
coerce_tests! :test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order
|
|
1547
|
+
def test_dump_schema_information_outputs_lexically_reverse_ordered_versions_regardless_of_database_order_coerced
|
|
1548
|
+
versions = %w{ 20100101010101 20100201010101 20100301010101 }
|
|
1549
|
+
versions.shuffle.each do |v|
|
|
1550
|
+
@schema_migration.create_version(v)
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
|
|
1554
|
+
expected = <<~STR
|
|
1555
|
+
INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
|
|
1556
|
+
(N'20100301010101'),
|
|
1557
|
+
(N'20100201010101'),
|
|
1558
|
+
(N'20100101010101');
|
|
1559
|
+
STR
|
|
1560
|
+
assert_equal expected.strip, schema_info
|
|
1561
|
+
ensure
|
|
1562
|
+
@schema_migration.delete_all_versions
|
|
1563
|
+
end
|
|
1564
|
+
|
|
1565
|
+
# We have precision to 38.
|
|
1566
|
+
coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
|
|
1567
|
+
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
|
|
1568
|
+
output = standard_dump
|
|
1569
|
+
assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 38}, output
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
# This is a poorly written test and really does not catch the bottom'ness it is meant to. Ours throw it off.
|
|
1573
|
+
coerce_tests! :test_foreign_keys_are_dumped_at_the_bottom_to_circumvent_dependency_issues
|
|
1574
|
+
|
|
1575
|
+
# Fall through false positive with no filter.
|
|
1576
|
+
coerce_tests! :test_schema_dumps_partial_indices
|
|
1577
|
+
def test_schema_dumps_partial_indices_coerced
|
|
1578
|
+
index_definition = standard_dump.split(/\n/).grep(/t.index.*company_partial_index/).first.strip
|
|
1579
|
+
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "([rating]>(10))"', index_definition
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
# We do not quote the 2.78 string default.
|
|
1583
|
+
coerce_tests! :test_schema_dump_includes_decimal_options
|
|
1584
|
+
def test_schema_dump_includes_decimal_options_coerced
|
|
1585
|
+
output = dump_all_table_schema([/^[^n]/])
|
|
1586
|
+
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
# Tests are not about a specific adapter.
|
|
1590
|
+
coerce_tests! :test_do_not_dump_foreign_keys_when_bypassed_by_config
|
|
1591
|
+
|
|
1592
|
+
# SQL Server formats the check constraint expression differently.
|
|
1593
|
+
coerce_tests! :test_schema_dumps_check_constraints
|
|
1594
|
+
def test_schema_dumps_check_constraints_coerced
|
|
1595
|
+
constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
|
|
1596
|
+
assert_equal 't.check_constraint "[price]>[discounted_price]", name: "products_price_check"', constraint_definition
|
|
1597
|
+
end
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
class SchemaDumperDefaultsTest < ActiveRecord::TestCase
|
|
1601
|
+
# These date formats do not match ours. We got these covered in our dumper tests.
|
|
1602
|
+
coerce_tests! :test_schema_dump_defaults_with_universally_supported_types
|
|
1603
|
+
|
|
1604
|
+
# SQL Server uses different method to generate a UUID than Rails test uses. Reimplemented the
|
|
1605
|
+
# test in 'SchemaDumperDefaultsCoerceTest'.
|
|
1606
|
+
coerce_tests! :test_schema_dump_with_text_column
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
class SchemaDumperDefaultsCoerceTest < ActiveRecord::TestCase
|
|
1610
|
+
include SchemaDumpingHelper
|
|
1611
|
+
|
|
1612
|
+
setup do
|
|
1613
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
1614
|
+
@connection.create_table :dump_defaults, force: true do |t|
|
|
1615
|
+
t.string :string_with_default, default: "Hello!"
|
|
1616
|
+
t.date :date_with_default, default: "2014-06-05"
|
|
1617
|
+
t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
|
|
1618
|
+
t.time :time_with_default, default: "07:17:04"
|
|
1619
|
+
t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
|
|
1620
|
+
|
|
1621
|
+
t.text :text_with_default, default: "John' Doe"
|
|
1622
|
+
t.text :uuid, default: -> { "newid()" }
|
|
1623
|
+
end
|
|
1624
|
+
end
|
|
1625
|
+
|
|
1626
|
+
def test_schema_dump_with_text_column_coerced
|
|
1627
|
+
output = dump_table_schema("dump_defaults")
|
|
1628
|
+
|
|
1629
|
+
assert_match %r{t\.text\s+"text_with_default",.*?default: "John' Doe"}, output
|
|
1630
|
+
assert_match %r{t\.text\s+"uuid",.*?default: -> \{ "newid\(\)" \}}, output
|
|
1631
|
+
end
|
|
1632
|
+
end
|
|
1633
|
+
|
|
1634
|
+
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
|
|
1635
|
+
# We trust Rails on this since we do not want to install mysql.
|
|
1636
|
+
coerce_tests! %r{inspect on Model class does not raise}
|
|
1637
|
+
end
|
|
1638
|
+
|
|
1639
|
+
require "models/topic"
|
|
1640
|
+
class TransactionTest < ActiveRecord::TestCase
|
|
1641
|
+
# SQL Server does not have query for release_savepoint.
|
|
1642
|
+
coerce_tests! :test_releasing_named_savepoints
|
|
1643
|
+
def test_releasing_named_savepoints_coerced
|
|
1644
|
+
Topic.transaction do
|
|
1645
|
+
Topic.lease_connection.materialize_transactions
|
|
1646
|
+
|
|
1647
|
+
Topic.lease_connection.create_savepoint("another")
|
|
1648
|
+
Topic.lease_connection.release_savepoint("another")
|
|
1649
|
+
|
|
1650
|
+
# We do not have a notion of releasing, so this does nothing and doesn't raise an error.
|
|
1651
|
+
assert_nothing_raised do
|
|
1652
|
+
Topic.lease_connection.release_savepoint("another")
|
|
1653
|
+
end
|
|
1654
|
+
end
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
# SQL Server does not have query for release_savepoint.
|
|
1658
|
+
coerce_tests! :test_nested_transactions_after_disable_lazy_transactions
|
|
1659
|
+
def test_nested_transactions_after_disable_lazy_transactions_coerced
|
|
1660
|
+
Topic.lease_connection.disable_lazy_transactions!
|
|
1661
|
+
|
|
1662
|
+
actual_queries = capture_sql(include_schema: true) do
|
|
1663
|
+
# RealTransaction (begin..commit)
|
|
1664
|
+
Topic.transaction(requires_new: true) do
|
|
1665
|
+
# ResetParentTransaction (no queries)
|
|
1666
|
+
Topic.transaction(requires_new: true) do
|
|
1667
|
+
Topic.delete_all
|
|
1668
|
+
# SavepointTransaction (savepoint..release)
|
|
1669
|
+
Topic.transaction(requires_new: true) do
|
|
1670
|
+
# ResetParentTransaction (no queries)
|
|
1671
|
+
Topic.transaction(requires_new: true) do
|
|
1672
|
+
# no-op
|
|
1673
|
+
end
|
|
1674
|
+
end
|
|
1675
|
+
end
|
|
1676
|
+
Topic.delete_all
|
|
1677
|
+
end
|
|
1678
|
+
end
|
|
1679
|
+
|
|
1680
|
+
expected_queries = [
|
|
1681
|
+
/BEGIN/i,
|
|
1682
|
+
/DELETE/i,
|
|
1683
|
+
/^SAVE TRANSACTION/i,
|
|
1684
|
+
/DELETE/i,
|
|
1685
|
+
/COMMIT/i,
|
|
1686
|
+
]
|
|
1687
|
+
|
|
1688
|
+
assert_equal expected_queries.size, actual_queries.size
|
|
1689
|
+
expected_queries.zip(actual_queries) do |expected, actual|
|
|
1690
|
+
assert_match expected, actual
|
|
1691
|
+
end
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
# SQL Server does not have query for release_savepoint.
|
|
1695
|
+
coerce_tests! :test_nested_transactions_skip_excess_savepoints
|
|
1696
|
+
def test_nested_transactions_skip_excess_savepoints_coerced
|
|
1697
|
+
actual_queries = capture_sql(include_schema: true) do
|
|
1698
|
+
# RealTransaction (begin..commit)
|
|
1699
|
+
Topic.transaction(requires_new: true) do
|
|
1700
|
+
# ResetParentTransaction (no queries)
|
|
1701
|
+
Topic.transaction(requires_new: true) do
|
|
1702
|
+
Topic.delete_all
|
|
1703
|
+
# SavepointTransaction (savepoint..release)
|
|
1704
|
+
Topic.transaction(requires_new: true) do
|
|
1705
|
+
# ResetParentTransaction (no queries)
|
|
1706
|
+
Topic.transaction(requires_new: true) do
|
|
1707
|
+
Topic.delete_all
|
|
1708
|
+
end
|
|
1709
|
+
end
|
|
1710
|
+
end
|
|
1711
|
+
Topic.delete_all
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
expected_queries = [
|
|
1716
|
+
/BEGIN/i,
|
|
1717
|
+
/DELETE/i,
|
|
1718
|
+
/^SAVE TRANSACTION/i,
|
|
1719
|
+
/DELETE/i,
|
|
1720
|
+
/DELETE/i,
|
|
1721
|
+
/COMMIT/i,
|
|
1722
|
+
]
|
|
1723
|
+
|
|
1724
|
+
assert_equal expected_queries.size, actual_queries.size
|
|
1725
|
+
expected_queries.zip(actual_queries) do |expected, actual|
|
|
1726
|
+
assert_match expected, actual
|
|
1727
|
+
end
|
|
1728
|
+
end
|
|
1729
|
+
end
|
|
1730
|
+
|
|
1731
|
+
require "models/tag"
|
|
1732
|
+
class TransactionIsolationTest < ActiveRecord::TestCase
|
|
1733
|
+
# SQL Server will lock the table for counts even when both
|
|
1734
|
+
# connections are `READ COMMITTED`. So we bypass with `READPAST`.
|
|
1735
|
+
coerce_tests! %r{read committed}
|
|
1736
|
+
test "read committed coerced" do
|
|
1737
|
+
Tag.transaction(isolation: :read_committed) do
|
|
1738
|
+
assert_equal 0, Tag.count
|
|
1739
|
+
Tag2.transaction do
|
|
1740
|
+
Tag2.create
|
|
1741
|
+
assert_equal 0, Tag.lock("WITH(READPAST)").count
|
|
1742
|
+
end
|
|
1743
|
+
end
|
|
1744
|
+
assert_equal 1, Tag.count
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
# I really need some help understanding this one.
|
|
1748
|
+
coerce_tests! %r{repeatable read}
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
require "models/book"
|
|
1752
|
+
class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
|
|
1753
|
+
# We have a few view tables. use includes vs equality.
|
|
1754
|
+
coerce_tests! :test_views
|
|
1755
|
+
def test_views_coerced
|
|
1756
|
+
assert_includes @connection.views, Ebook.table_name
|
|
1757
|
+
end
|
|
1758
|
+
|
|
1759
|
+
# We do better than ActiveRecord and find the views PK.
|
|
1760
|
+
coerce_tests! :test_does_not_assume_id_column_as_primary_key
|
|
1761
|
+
def test_does_not_assume_id_column_as_primary_key_coerced
|
|
1762
|
+
model = Class.new(ActiveRecord::Base) { self.table_name = "ebooks" }
|
|
1763
|
+
assert_equal "id", model.primary_key
|
|
1764
|
+
end
|
|
1765
|
+
end
|
|
1766
|
+
|
|
1767
|
+
class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
|
|
1768
|
+
# We have a few view tables. use includes vs equality.
|
|
1769
|
+
coerce_tests! :test_views
|
|
1770
|
+
def test_views_coerced
|
|
1771
|
+
assert_includes @connection.views, Paperback.table_name
|
|
1772
|
+
end
|
|
1773
|
+
end
|
|
1774
|
+
|
|
1775
|
+
require "models/author"
|
|
1776
|
+
class YamlSerializationTest < ActiveRecord::TestCase
|
|
1777
|
+
coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
|
|
1778
|
+
def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
|
|
1779
|
+
author = Author.select("authors.*, 5 as posts_count").first
|
|
1780
|
+
dumped_author = YAML.dump(author)
|
|
1781
|
+
dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
|
|
1782
|
+
assert_equal 5, author.posts_count
|
|
1783
|
+
assert_equal 5, dumped.posts_count
|
|
1784
|
+
end
|
|
1785
|
+
end
|
|
1786
|
+
|
|
1787
|
+
class DateTimePrecisionTest < ActiveRecord::TestCase
|
|
1788
|
+
# Original test had `7` which we support vs `8` which we use.
|
|
1789
|
+
coerce_tests! :test_invalid_datetime_precision_raises_error
|
|
1790
|
+
def test_invalid_datetime_precision_raises_error_coerced
|
|
1791
|
+
assert_raises ActiveRecord::ActiveRecordError do
|
|
1792
|
+
@connection.create_table(:foos, force: true) do |t|
|
|
1793
|
+
t.timestamps precision: 8
|
|
1794
|
+
end
|
|
1795
|
+
end
|
|
1796
|
+
end
|
|
1797
|
+
|
|
1798
|
+
# datetime is rounded to increments of .000, .003, or .007 seconds
|
|
1799
|
+
coerce_tests! :test_datetime_precision_is_truncated_on_assignment
|
|
1800
|
+
def test_datetime_precision_is_truncated_on_assignment_coerced
|
|
1801
|
+
@connection.create_table(:foos, force: true)
|
|
1802
|
+
@connection.add_column :foos, :created_at, :datetime, precision: 0
|
|
1803
|
+
@connection.add_column :foos, :updated_at, :datetime, precision: 6
|
|
1804
|
+
|
|
1805
|
+
time = ::Time.now.change(nsec: 123456789)
|
|
1806
|
+
foo = Foo.new(created_at: time, updated_at: time)
|
|
1807
|
+
|
|
1808
|
+
assert_equal 0, foo.created_at.nsec
|
|
1809
|
+
assert_equal 123457000, foo.updated_at.nsec
|
|
1810
|
+
|
|
1811
|
+
foo.save!
|
|
1812
|
+
foo.reload
|
|
1813
|
+
|
|
1814
|
+
assert_equal 0, foo.created_at.nsec
|
|
1815
|
+
assert_equal 123457000, foo.updated_at.nsec
|
|
1816
|
+
end
|
|
1817
|
+
end
|
|
1818
|
+
|
|
1819
|
+
class TimePrecisionTest < ActiveRecord::TestCase
|
|
1820
|
+
# datetime is rounded to increments of .000, .003, or .007 seconds
|
|
1821
|
+
coerce_tests! :test_time_precision_is_truncated_on_assignment
|
|
1822
|
+
def test_time_precision_is_truncated_on_assignment_coerced
|
|
1823
|
+
@connection.create_table(:foos, force: true)
|
|
1824
|
+
@connection.add_column :foos, :start, :time, precision: 0
|
|
1825
|
+
@connection.add_column :foos, :finish, :time, precision: 6
|
|
1826
|
+
|
|
1827
|
+
time = ::Time.now.change(nsec: 123456789)
|
|
1828
|
+
foo = Foo.new(start: time, finish: time)
|
|
1829
|
+
|
|
1830
|
+
assert_equal 0, foo.start.nsec
|
|
1831
|
+
assert_equal 123457000, foo.finish.nsec
|
|
1832
|
+
|
|
1833
|
+
foo.save!
|
|
1834
|
+
foo.reload
|
|
1835
|
+
|
|
1836
|
+
assert_equal 0, foo.start.nsec
|
|
1837
|
+
assert_equal 123457000, foo.finish.nsec
|
|
1838
|
+
end
|
|
1839
|
+
|
|
1840
|
+
# SQL Server uses default precision for time.
|
|
1841
|
+
coerce_tests! :test_no_time_precision_isnt_truncated_on_assignment
|
|
1842
|
+
|
|
1843
|
+
# SQL Server accepts precision of 7 for time.
|
|
1844
|
+
coerce_tests! :test_invalid_time_precision_raises_error
|
|
1845
|
+
end
|
|
1846
|
+
|
|
1847
|
+
class DefaultNumbersTest < ActiveRecord::TestCase
|
|
1848
|
+
# We do better with native types and do not return strings for everything.
|
|
1849
|
+
coerce_tests! :test_default_positive_integer
|
|
1850
|
+
def test_default_positive_integer_coerced
|
|
1851
|
+
record = DefaultNumber.new
|
|
1852
|
+
assert_equal 7, record.positive_integer
|
|
1853
|
+
assert_equal 7, record.positive_integer_before_type_cast
|
|
1854
|
+
end
|
|
1855
|
+
|
|
1856
|
+
# We do better with native types and do not return strings for everything.
|
|
1857
|
+
coerce_tests! :test_default_negative_integer
|
|
1858
|
+
def test_default_negative_integer_coerced
|
|
1859
|
+
record = DefaultNumber.new
|
|
1860
|
+
assert_equal (-5), record.negative_integer
|
|
1861
|
+
assert_equal (-5), record.negative_integer_before_type_cast
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
# We do better with native types and do not return strings for everything.
|
|
1865
|
+
coerce_tests! :test_default_decimal_number
|
|
1866
|
+
def test_default_decimal_number_coerced
|
|
1867
|
+
record = DefaultNumber.new
|
|
1868
|
+
assert_equal BigDecimal("2.78"), record.decimal_number
|
|
1869
|
+
assert_equal 2.78, record.decimal_number_before_type_cast
|
|
1870
|
+
end
|
|
1871
|
+
end
|
|
1872
|
+
|
|
1873
|
+
module ActiveRecord
|
|
1874
|
+
class CollectionCacheKeyTest < ActiveRecord::TestCase
|
|
1875
|
+
# Will trust rails has this sorted since you cant offset without a limit.
|
|
1876
|
+
coerce_tests! %r{with offset which return 0 rows}
|
|
1877
|
+
end
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
module ActiveRecord
|
|
1881
|
+
class CacheKeyTest < ActiveRecord::TestCase
|
|
1882
|
+
# Like Mysql2 and PostgreSQL, SQL Server doesn't return a string value for updated_at. In the Rails tests
|
|
1883
|
+
# the tests are skipped if adapter is Mysql2 or PostgreSQL.
|
|
1884
|
+
coerce_tests! %r{cache_version is the same when it comes from the DB or from the user}
|
|
1885
|
+
coerce_tests! %r{cache_version does NOT call updated_at when value is from the database}
|
|
1886
|
+
coerce_tests! %r{cache_version does not truncate zeros when timestamp ends in zeros}
|
|
1887
|
+
end
|
|
1888
|
+
end
|
|
1889
|
+
|
|
1890
|
+
require "models/book"
|
|
1891
|
+
module ActiveRecord
|
|
1892
|
+
class StatementCacheTest < ActiveRecord::TestCase
|
|
1893
|
+
# Getting random failures.
|
|
1894
|
+
coerce_tests! :test_find_does_not_use_statement_cache_if_table_name_is_changed
|
|
1895
|
+
|
|
1896
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
1897
|
+
coerce_tests! :test_statement_cache_values_differ
|
|
1898
|
+
def test_statement_cache_values_differ_coerced
|
|
1899
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
1900
|
+
|
|
1901
|
+
original_test_statement_cache_values_differ
|
|
1902
|
+
ensure
|
|
1903
|
+
Book.where(author_id: nil, name: 'my book').delete_all
|
|
1904
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1905
|
+
end
|
|
1906
|
+
end
|
|
1907
|
+
end
|
|
1908
|
+
|
|
1909
|
+
module ActiveRecord
|
|
1910
|
+
module ConnectionAdapters
|
|
1911
|
+
class SchemaCacheTest < ActiveRecord::TestCase
|
|
1912
|
+
# Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
|
|
1913
|
+
coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1914
|
+
|
|
1915
|
+
# Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
|
|
1916
|
+
coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
|
|
1917
|
+
|
|
1918
|
+
# Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
|
|
1919
|
+
unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1920
|
+
def test_marshal_dump_and_load_with_gzip_coerced
|
|
1921
|
+
with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
|
|
1922
|
+
end
|
|
1923
|
+
def test_marshal_dump_and_load_via_disk_coerced
|
|
1924
|
+
with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
|
|
1925
|
+
end
|
|
1926
|
+
end
|
|
1927
|
+
|
|
1928
|
+
private
|
|
1929
|
+
|
|
1930
|
+
def with_marshable_time_defaults
|
|
1931
|
+
# Detect problems
|
|
1932
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
|
|
1933
|
+
column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
|
|
1934
|
+
current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
# Correct problems
|
|
1938
|
+
if current_default.present?
|
|
1939
|
+
@connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
|
|
1940
|
+
end
|
|
1941
|
+
|
|
1942
|
+
# Run original test
|
|
1943
|
+
yield
|
|
1944
|
+
ensure
|
|
1945
|
+
# Revert changes
|
|
1946
|
+
@connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
|
|
1947
|
+
end
|
|
1948
|
+
|
|
1949
|
+
# We need to give the full path for this to work.
|
|
1950
|
+
undef_method :schema_dump_path
|
|
1951
|
+
def schema_dump_path
|
|
1952
|
+
File.join(ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml")
|
|
1953
|
+
end
|
|
1954
|
+
end
|
|
1955
|
+
end
|
|
1956
|
+
end
|
|
1957
|
+
|
|
1958
|
+
require "models/post"
|
|
1959
|
+
require "models/comment"
|
|
1960
|
+
class UnsafeRawSqlTest < ActiveRecord::TestCase
|
|
1961
|
+
fixtures :posts
|
|
1962
|
+
|
|
1963
|
+
# Use LEN() instead of LENGTH() function.
|
|
1964
|
+
coerce_tests! %r{order: always allows Arel}
|
|
1965
|
+
test "order: always allows Arel" do
|
|
1966
|
+
titles = Post.order(Arel.sql("len(title)")).pluck(:title)
|
|
1967
|
+
|
|
1968
|
+
assert_not_empty titles
|
|
1969
|
+
end
|
|
1970
|
+
|
|
1971
|
+
# Use LEN() instead of LENGTH() function.
|
|
1972
|
+
coerce_tests! %r{pluck: always allows Arel}
|
|
1973
|
+
test "pluck: always allows Arel" do
|
|
1974
|
+
excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
|
|
1975
|
+
values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
|
|
1976
|
+
|
|
1977
|
+
assert_equal excepted_values, values
|
|
1978
|
+
end
|
|
1979
|
+
|
|
1980
|
+
# Use LEN() instead of LENGTH() function.
|
|
1981
|
+
coerce_tests! %r{order: allows valid Array arguments}
|
|
1982
|
+
test "order: allows valid Array arguments" do
|
|
1983
|
+
ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
|
|
1984
|
+
|
|
1985
|
+
ids = Post.order(["author_id", "len(title)"]).pluck(:id)
|
|
1986
|
+
|
|
1987
|
+
assert_equal ids_expected, ids
|
|
1988
|
+
end
|
|
1989
|
+
|
|
1990
|
+
# Use LEN() instead of LENGTH() function.
|
|
1991
|
+
coerce_tests! %r{order: allows nested functions}
|
|
1992
|
+
test "order: allows nested functions" do
|
|
1993
|
+
ids_expected = Post.order(Arel.sql("author_id, len(trim(title))")).pluck(:id)
|
|
1994
|
+
|
|
1995
|
+
# $DEBUG = true
|
|
1996
|
+
ids = Post.order("author_id, len(trim(title))").pluck(:id)
|
|
1997
|
+
|
|
1998
|
+
assert_equal ids_expected, ids
|
|
1999
|
+
end
|
|
2000
|
+
|
|
2001
|
+
# Use LEN() instead of LENGTH() function.
|
|
2002
|
+
coerce_tests! %r{pluck: allows nested functions}
|
|
2003
|
+
test "pluck: allows nested functions" do
|
|
2004
|
+
title_lengths_expected = Post.pluck(Arel.sql("len(trim(title))"))
|
|
2005
|
+
|
|
2006
|
+
title_lengths = Post.pluck("len(trim(title))")
|
|
2007
|
+
|
|
2008
|
+
assert_equal title_lengths_expected, title_lengths
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
test "order: allows string column names that are quoted" do
|
|
2012
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
2013
|
+
|
|
2014
|
+
ids = Post.order("[id]").pluck(:id)
|
|
2015
|
+
|
|
2016
|
+
assert_equal ids_expected, ids
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
test "order: allows string column names that are quoted with table" do
|
|
2020
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
2021
|
+
|
|
2022
|
+
ids = Post.order("[posts].[id]").pluck(:id)
|
|
2023
|
+
|
|
2024
|
+
assert_equal ids_expected, ids
|
|
2025
|
+
end
|
|
2026
|
+
|
|
2027
|
+
test "order: allows string column names that are quoted with table and user" do
|
|
2028
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
2029
|
+
|
|
2030
|
+
ids = Post.order("[dbo].[posts].[id]").pluck(:id)
|
|
2031
|
+
|
|
2032
|
+
assert_equal ids_expected, ids
|
|
2033
|
+
end
|
|
2034
|
+
|
|
2035
|
+
test "order: allows string column names that are quoted with table, user and database" do
|
|
2036
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
2037
|
+
|
|
2038
|
+
ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
|
|
2039
|
+
|
|
2040
|
+
assert_equal ids_expected, ids
|
|
2041
|
+
end
|
|
2042
|
+
|
|
2043
|
+
test "pluck: allows string column name that are quoted" do
|
|
2044
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
|
2045
|
+
|
|
2046
|
+
titles = Post.pluck("[title]")
|
|
2047
|
+
|
|
2048
|
+
assert_equal titles_expected, titles
|
|
2049
|
+
end
|
|
2050
|
+
|
|
2051
|
+
test "pluck: allows string column name that are quoted with table" do
|
|
2052
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
|
2053
|
+
|
|
2054
|
+
titles = Post.pluck("[posts].[title]")
|
|
2055
|
+
|
|
2056
|
+
assert_equal titles_expected, titles
|
|
2057
|
+
end
|
|
2058
|
+
|
|
2059
|
+
test "pluck: allows string column name that are quoted with table and user" do
|
|
2060
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
|
2061
|
+
|
|
2062
|
+
titles = Post.pluck("[dbo].[posts].[title]")
|
|
2063
|
+
|
|
2064
|
+
assert_equal titles_expected, titles
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
test "pluck: allows string column name that are quoted with table, user and database" do
|
|
2068
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
|
2069
|
+
|
|
2070
|
+
titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
|
|
2071
|
+
|
|
2072
|
+
assert_equal titles_expected, titles
|
|
2073
|
+
end
|
|
2074
|
+
|
|
2075
|
+
# Collation name should not be quoted. Hardcoded values for different adapters.
|
|
2076
|
+
coerce_tests! %r{order: allows valid arguments with COLLATE}
|
|
2077
|
+
test "order: allows valid arguments with COLLATE" do
|
|
2078
|
+
collation_name = "Latin1_General_CS_AS_WS"
|
|
2079
|
+
|
|
2080
|
+
ids_expected = Post.order(Arel.sql(%Q'author_id, title COLLATE #{collation_name} DESC')).pluck(:id)
|
|
2081
|
+
|
|
2082
|
+
ids = Post.order(["author_id", %Q'title COLLATE #{collation_name} DESC']).pluck(:id)
|
|
2083
|
+
|
|
2084
|
+
assert_equal ids_expected, ids
|
|
2085
|
+
end
|
|
2086
|
+
end
|
|
2087
|
+
|
|
2088
|
+
class ReservedWordTest < ActiveRecord::TestCase
|
|
2089
|
+
coerce_tests! :test_change_columns
|
|
2090
|
+
def test_change_columns_coerced
|
|
2091
|
+
assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
|
|
2092
|
+
assert_nothing_raised { @connection.change_column("group", "order", :text) }
|
|
2093
|
+
assert_nothing_raised { @connection.change_column_null("group", "order", true) }
|
|
2094
|
+
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
|
|
2095
|
+
end
|
|
2096
|
+
end
|
|
2097
|
+
|
|
2098
|
+
class OptimisticLockingTest < ActiveRecord::TestCase
|
|
2099
|
+
# We do not allow updating identities, but we can test using a non-identity key
|
|
2100
|
+
coerce_tests! :test_update_with_dirty_primary_key
|
|
2101
|
+
def test_update_with_dirty_primary_key_coerced
|
|
2102
|
+
assert_raises(ActiveRecord::RecordNotUnique) do
|
|
2103
|
+
record = StringKeyObject.find("record1")
|
|
2104
|
+
record.id = "record2"
|
|
2105
|
+
record.save!
|
|
2106
|
+
end
|
|
2107
|
+
|
|
2108
|
+
record = StringKeyObject.find("record1")
|
|
2109
|
+
record.id = "record42"
|
|
2110
|
+
record.save!
|
|
2111
|
+
|
|
2112
|
+
assert StringKeyObject.find("record42")
|
|
2113
|
+
assert_raises(ActiveRecord::RecordNotFound) do
|
|
2114
|
+
StringKeyObject.find("record1")
|
|
2115
|
+
end
|
|
2116
|
+
end
|
|
2117
|
+
end
|
|
2118
|
+
|
|
2119
|
+
class RelationMergingTest < ActiveRecord::TestCase
|
|
2120
|
+
# Use nvarchar string (N'') in assert
|
|
2121
|
+
coerce_tests! :test_merging_with_order_with_binds
|
|
2122
|
+
def test_merging_with_order_with_binds_coerced
|
|
2123
|
+
relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
|
|
2124
|
+
assert_equal ["title LIKE N'%suffix'"], relation.order_values
|
|
2125
|
+
end
|
|
2126
|
+
|
|
2127
|
+
# Same as original but change first regexp to match sp_executesql binding syntax
|
|
2128
|
+
coerce_tests! :test_merge_doesnt_duplicate_same_clauses
|
|
2129
|
+
def test_merge_doesnt_duplicate_same_clauses_coerced
|
|
2130
|
+
david, mary, bob = authors(:david, :mary, :bob)
|
|
2131
|
+
|
|
2132
|
+
non_mary_and_bob = Author.where.not(id: [mary, bob])
|
|
2133
|
+
|
|
2134
|
+
author_id = Author.lease_connection.quote_table_name("authors.id")
|
|
2135
|
+
assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
|
|
2136
|
+
assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
|
|
2137
|
+
end
|
|
2138
|
+
|
|
2139
|
+
only_david = Author.where("#{author_id} IN (?)", david)
|
|
2140
|
+
|
|
2141
|
+
assert_queries_match(/WHERE \(#{Regexp.escape(author_id)} IN \(@\d\)\)/) do
|
|
2142
|
+
assert_equal [david], only_david.merge(only_david)
|
|
2143
|
+
end
|
|
2144
|
+
end
|
|
2145
|
+
end
|
|
2146
|
+
|
|
2147
|
+
module ActiveRecord
|
|
2148
|
+
class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
|
|
2149
|
+
# SQL Server does not allow truncation of tables that are referenced by foreign key
|
|
2150
|
+
# constraints. As this test truncates all tables we would need to remove all foreign
|
|
2151
|
+
# key constraints and then restore them afterwards to get this test to pass.
|
|
2152
|
+
coerce_tests! :test_truncate_tables
|
|
2153
|
+
end
|
|
2154
|
+
end
|
|
2155
|
+
|
|
2156
|
+
require "models/book"
|
|
2157
|
+
class EnumTest < ActiveRecord::TestCase
|
|
2158
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2159
|
+
coerce_tests! %r{enums are distinct per class}
|
|
2160
|
+
test "enums are distinct per class coerced" do
|
|
2161
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2162
|
+
|
|
2163
|
+
send(:'original_enums are distinct per class')
|
|
2164
|
+
ensure
|
|
2165
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2166
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2167
|
+
end
|
|
2168
|
+
|
|
2169
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2170
|
+
coerce_tests! %r{creating new objects with enum scopes}
|
|
2171
|
+
test "creating new objects with enum scopes coerced" do
|
|
2172
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2173
|
+
|
|
2174
|
+
send(:'original_creating new objects with enum scopes')
|
|
2175
|
+
ensure
|
|
2176
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2177
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2178
|
+
end
|
|
2179
|
+
|
|
2180
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2181
|
+
coerce_tests! %r{enums are inheritable}
|
|
2182
|
+
test "enums are inheritable coerced" do
|
|
2183
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2184
|
+
|
|
2185
|
+
send(:'original_enums are inheritable')
|
|
2186
|
+
ensure
|
|
2187
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2188
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2189
|
+
end
|
|
2190
|
+
|
|
2191
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2192
|
+
coerce_tests! %r{serializable\? with large number label}
|
|
2193
|
+
test "serializable? with large number label coerced" do
|
|
2194
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2195
|
+
|
|
2196
|
+
send(:'original_serializable\? with large number label')
|
|
2197
|
+
ensure
|
|
2198
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2199
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2200
|
+
end
|
|
2201
|
+
end
|
|
2202
|
+
|
|
2203
|
+
require "models/task"
|
|
2204
|
+
class QueryCacheExpiryTest < ActiveRecord::TestCase
|
|
2205
|
+
# SQL Server does not support skipping or upserting duplicates.
|
|
2206
|
+
coerce_tests! :test_insert_all
|
|
2207
|
+
def test_insert_all_coerced
|
|
2208
|
+
assert_raises(ArgumentError, /does not support skipping duplicates/) do
|
|
2209
|
+
Task.cache { Task.insert({ starting: Time.now }) }
|
|
2210
|
+
end
|
|
2211
|
+
|
|
2212
|
+
assert_raises(ArgumentError, /does not support upsert/) do
|
|
2213
|
+
Task.cache { Task.upsert({ starting: Time.now }) }
|
|
2214
|
+
end
|
|
2215
|
+
|
|
2216
|
+
assert_raises(ArgumentError, /does not support upsert/) do
|
|
2217
|
+
Task.cache { Task.upsert_all([{ starting: Time.now }]) }
|
|
2218
|
+
end
|
|
2219
|
+
|
|
2220
|
+
Task.cache do
|
|
2221
|
+
assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
|
|
2222
|
+
Task.insert_all!([ starting: Time.now ])
|
|
2223
|
+
end
|
|
2224
|
+
|
|
2225
|
+
assert_called(ActiveRecord::Base.connection_pool.query_cache, :clear, times: 1) do
|
|
2226
|
+
Task.insert!({ starting: Time.now })
|
|
2227
|
+
end
|
|
2228
|
+
end
|
|
2229
|
+
end
|
|
2230
|
+
end
|
|
2231
|
+
|
|
2232
|
+
require "models/citation"
|
|
2233
|
+
class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
|
2234
|
+
fixtures :citations
|
|
2235
|
+
|
|
2236
|
+
# Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
|
|
2237
|
+
# could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
|
|
2238
|
+
# (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
|
|
2239
|
+
# However, you cannot change the compatibility level during a test. The purpose of the test is to ensure that an
|
|
2240
|
+
# unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
|
|
2241
|
+
# still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
|
|
2242
|
+
# adapter is 2,098.
|
|
2243
|
+
coerce_tests! :test_eager_loading_too_many_ids
|
|
2244
|
+
def test_eager_loading_too_many_ids_coerced
|
|
2245
|
+
# Remove excess records.
|
|
2246
|
+
Citation.limit(32768).order(id: :desc).delete_all
|
|
2247
|
+
|
|
2248
|
+
# Perform test
|
|
2249
|
+
citation_count = Citation.count
|
|
2250
|
+
assert_queries_match(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
|
|
2251
|
+
assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
|
|
2252
|
+
end
|
|
2253
|
+
end
|
|
2254
|
+
end
|
|
2255
|
+
|
|
2256
|
+
class LogSubscriberTest < ActiveRecord::TestCase
|
|
2257
|
+
# Call original test from coerced test. Fixes issue on CI with Rails installed as a gem.
|
|
2258
|
+
coerce_tests! :test_verbose_query_logs
|
|
2259
|
+
def test_verbose_query_logs_coerced
|
|
2260
|
+
original_test_verbose_query_logs
|
|
2261
|
+
end
|
|
2262
|
+
end
|
|
2263
|
+
|
|
2264
|
+
class ReloadModelsTest < ActiveRecord::TestCase
|
|
2265
|
+
# Skip test on Windows. The number of arguments passed to `IO.popen` in
|
|
2266
|
+
# `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
|
|
2267
|
+
coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
2268
|
+
end
|
|
2269
|
+
|
|
2270
|
+
class MarshalSerializationTest < ActiveRecord::TestCase
|
|
2271
|
+
private
|
|
2272
|
+
|
|
2273
|
+
undef_method :marshal_fixture_path
|
|
2274
|
+
def marshal_fixture_path(file_name)
|
|
2275
|
+
File.expand_path(
|
|
2276
|
+
"support/marshal_compatibility_fixtures/#{ActiveRecord::Base.lease_connection.adapter_name}/#{file_name}.dump",
|
|
2277
|
+
ARTest::SQLServer.test_root_sqlserver
|
|
2278
|
+
)
|
|
2279
|
+
end
|
|
2280
|
+
end
|
|
2281
|
+
|
|
2282
|
+
class NestedThroughAssociationsTest < ActiveRecord::TestCase
|
|
2283
|
+
# Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
|
|
2284
|
+
# "A column has been specified more than once in the order by list"
|
|
2285
|
+
# Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
|
|
2286
|
+
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
|
|
2287
|
+
def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
|
|
2288
|
+
# preload table schemas
|
|
2289
|
+
Author.joins(:category_post_comments).first
|
|
2290
|
+
|
|
2291
|
+
assert_includes_and_joins_equal(
|
|
2292
|
+
Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
|
|
2293
|
+
[authors(:david), authors(:mary)], :category_post_comments
|
|
2294
|
+
)
|
|
2295
|
+
end
|
|
2296
|
+
|
|
2297
|
+
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
|
|
2298
|
+
# preload table schemas
|
|
2299
|
+
Category.joins(:post_comments).first
|
|
2300
|
+
|
|
2301
|
+
assert_includes_and_joins_equal(
|
|
2302
|
+
Category.where("comments.id" => comments(:more_greetings).id).order(:id),
|
|
2303
|
+
[categories(:general), categories(:technology)], :post_comments
|
|
2304
|
+
)
|
|
2305
|
+
end
|
|
2306
|
+
end
|
|
2307
|
+
|
|
2308
|
+
class PreloaderTest < ActiveRecord::TestCase
|
|
2309
|
+
# Need to handle query parameters in SQL regex.
|
|
2310
|
+
coerce_tests! :test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
|
|
2311
|
+
def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute_coerced
|
|
2312
|
+
order = cpk_orders(:cpk_groceries_order_2)
|
|
2313
|
+
_shop_id, order_id = order.id
|
|
2314
|
+
order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
|
|
2315
|
+
|
|
2316
|
+
assert_not_empty order_agreements
|
|
2317
|
+
assert_equal order_agreements.sort, order.order_agreements.sort
|
|
2318
|
+
|
|
2319
|
+
loaded_order = nil
|
|
2320
|
+
sql = capture_sql do
|
|
2321
|
+
loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
|
|
2322
|
+
end
|
|
2323
|
+
|
|
2324
|
+
assert_equal 2, sql.size
|
|
2325
|
+
preload_sql = sql.last
|
|
2326
|
+
|
|
2327
|
+
c = Cpk::OrderAgreement.lease_connection
|
|
2328
|
+
order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
|
|
2329
|
+
order_id_constraint = /#{order_id_column} = @0.*@0 = \d+$/
|
|
2330
|
+
expectation = /SELECT.*WHERE.* #{order_id_constraint}/
|
|
2331
|
+
|
|
2332
|
+
assert_match(expectation, preload_sql)
|
|
2333
|
+
assert_equal order_agreements.sort, loaded_order.order_agreements.sort
|
|
2334
|
+
end
|
|
2335
|
+
|
|
2336
|
+
# Need to handle query parameters in SQL regex.
|
|
2337
|
+
coerce_tests! :test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
|
|
2338
|
+
def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute_coerced
|
|
2339
|
+
order_agreement = cpk_order_agreements(:order_agreement_three)
|
|
2340
|
+
order = cpk_orders(:cpk_groceries_order_2)
|
|
2341
|
+
assert_equal order, order_agreement.order
|
|
2342
|
+
|
|
2343
|
+
loaded_order_agreement = nil
|
|
2344
|
+
sql = capture_sql do
|
|
2345
|
+
loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
|
|
2346
|
+
end
|
|
2347
|
+
|
|
2348
|
+
assert_equal 2, sql.size
|
|
2349
|
+
preload_sql = sql.last
|
|
2350
|
+
|
|
2351
|
+
c = Cpk::Order.lease_connection
|
|
2352
|
+
order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
|
|
2353
|
+
order_constraint = /#{order_id} = @0.*@0 = \d+$/
|
|
2354
|
+
expectation = /SELECT.*WHERE.* #{order_constraint}/
|
|
2355
|
+
|
|
2356
|
+
assert_match(expectation, preload_sql)
|
|
2357
|
+
assert_equal order, loaded_order_agreement.order
|
|
2358
|
+
end
|
|
2359
|
+
end
|
|
2360
|
+
|
|
2361
|
+
class MigratorTest < ActiveRecord::TestCase
|
|
2362
|
+
# Test fails on Windows AppVeyor CI for unknown reason.
|
|
2363
|
+
coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
2364
|
+
end
|
|
2365
|
+
|
|
2366
|
+
class MultiDbMigratorTest < ActiveRecord::TestCase
|
|
2367
|
+
# Test fails on Windows AppVeyor CI for unknown reason.
|
|
2368
|
+
coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
2369
|
+
end
|
|
2370
|
+
|
|
2371
|
+
require "models/book"
|
|
2372
|
+
class FieldOrderedValuesTest < ActiveRecord::TestCase
|
|
2373
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2374
|
+
coerce_tests! :test_in_order_of_with_enums_values
|
|
2375
|
+
def test_in_order_of_with_enums_values_coerced
|
|
2376
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2377
|
+
|
|
2378
|
+
original_test_in_order_of_with_enums_values
|
|
2379
|
+
ensure
|
|
2380
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2381
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2382
|
+
end
|
|
2383
|
+
|
|
2384
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2385
|
+
coerce_tests! :test_in_order_of_with_string_column
|
|
2386
|
+
def test_in_order_of_with_string_column_coerced
|
|
2387
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2388
|
+
|
|
2389
|
+
original_test_in_order_of_with_string_column
|
|
2390
|
+
ensure
|
|
2391
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2392
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2393
|
+
end
|
|
2394
|
+
|
|
2395
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2396
|
+
coerce_tests! :test_in_order_of_with_enums_keys
|
|
2397
|
+
def test_in_order_of_with_enums_keys_coerced
|
|
2398
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2399
|
+
|
|
2400
|
+
original_test_in_order_of_with_enums_keys
|
|
2401
|
+
ensure
|
|
2402
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2403
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2404
|
+
end
|
|
2405
|
+
|
|
2406
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2407
|
+
coerce_tests! :test_in_order_of_with_nil
|
|
2408
|
+
def test_in_order_of_with_nil_coerced
|
|
2409
|
+
Book.lease_connection.remove_index(:books, column: [:author_id, :name])
|
|
2410
|
+
|
|
2411
|
+
original_test_in_order_of_with_nil
|
|
2412
|
+
ensure
|
|
2413
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
2414
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2415
|
+
end
|
|
2416
|
+
end
|
|
2417
|
+
|
|
2418
|
+
require "models/dashboard"
|
|
2419
|
+
class QueryLogsTest < ActiveRecord::TestCase
|
|
2420
|
+
# SQL requires double single-quotes.
|
|
2421
|
+
coerce_tests! :test_sql_commenter_format
|
|
2422
|
+
def test_sql_commenter_format_coerced
|
|
2423
|
+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2424
|
+
ActiveRecord::QueryLogs.tags = [:application]
|
|
2425
|
+
|
|
2426
|
+
assert_queries_match(%r{/\*application=''active_record''\*/}) do
|
|
2427
|
+
Dashboard.first
|
|
2428
|
+
end
|
|
2429
|
+
end
|
|
2430
|
+
|
|
2431
|
+
# SQL requires double single-quotes.
|
|
2432
|
+
coerce_tests! :test_sqlcommenter_format_value
|
|
2433
|
+
def test_sqlcommenter_format_value_coerced
|
|
2434
|
+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2435
|
+
|
|
2436
|
+
ActiveRecord::QueryLogs.tags = [
|
|
2437
|
+
:application,
|
|
2438
|
+
{ tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
|
|
2439
|
+
]
|
|
2440
|
+
|
|
2441
|
+
assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
|
|
2442
|
+
Dashboard.first
|
|
2443
|
+
end
|
|
2444
|
+
end
|
|
2445
|
+
|
|
2446
|
+
# SQL requires double single-quotes.
|
|
2447
|
+
coerce_tests! :test_sqlcommenter_format_value_string_coercible
|
|
2448
|
+
def test_sqlcommenter_format_value_string_coercible_coerced
|
|
2449
|
+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2450
|
+
|
|
2451
|
+
ActiveRecord::QueryLogs.tags = [
|
|
2452
|
+
:application,
|
|
2453
|
+
{ custom_proc: -> { 1234 } },
|
|
2454
|
+
]
|
|
2455
|
+
|
|
2456
|
+
assert_queries_match(%r{custom_proc=''1234''\*/}) do
|
|
2457
|
+
Dashboard.first
|
|
2458
|
+
end
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
# SQL requires double single-quotes.
|
|
2462
|
+
coerce_tests! :test_sqlcommenter_format_allows_string_keys
|
|
2463
|
+
def test_sqlcommenter_format_allows_string_keys_coerced
|
|
2464
|
+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2465
|
+
|
|
2466
|
+
ActiveRecord::QueryLogs.tags = [
|
|
2467
|
+
:application,
|
|
2468
|
+
{
|
|
2469
|
+
"string" => "value",
|
|
2470
|
+
tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7",
|
|
2471
|
+
custom_proc: -> { "Joe's Shack" }
|
|
2472
|
+
},
|
|
2473
|
+
]
|
|
2474
|
+
|
|
2475
|
+
assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
|
|
2476
|
+
Dashboard.first
|
|
2477
|
+
end
|
|
2478
|
+
end
|
|
2479
|
+
|
|
2480
|
+
# Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
|
|
2481
|
+
coerce_tests! :test_invalid_encoding_query
|
|
2482
|
+
def test_invalid_encoding_query_coerced
|
|
2483
|
+
ActiveRecord::QueryLogs.tags = [ :application ]
|
|
2484
|
+
assert_raises ActiveRecord::StatementInvalid do
|
|
2485
|
+
ActiveRecord::Base.lease_connection.execute "select 1 as '\xFF'"
|
|
2486
|
+
end
|
|
2487
|
+
end
|
|
2488
|
+
end
|
|
2489
|
+
|
|
2490
|
+
class InsertAllTest < ActiveRecord::TestCase
|
|
2491
|
+
# Same as original but using INSERTED.name as UPPER argument
|
|
2492
|
+
coerce_tests! :test_insert_all_returns_requested_sql_fields
|
|
2493
|
+
def test_insert_all_returns_requested_sql_fields_coerced
|
|
2494
|
+
skip unless supports_insert_returning?
|
|
2495
|
+
|
|
2496
|
+
result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
|
|
2497
|
+
assert_equal %w[ REWORK ], result.pluck("name")
|
|
2498
|
+
end
|
|
2499
|
+
|
|
2500
|
+
# Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
|
|
2501
|
+
coerce_tests! :test_insert_with_type_casting_and_serialize_is_consistent
|
|
2502
|
+
def test_insert_with_type_casting_and_serialize_is_consistent_coerced
|
|
2503
|
+
connection.remove_index(:books, column: [:author_id, :name])
|
|
2504
|
+
|
|
2505
|
+
original_test_insert_with_type_casting_and_serialize_is_consistent
|
|
2506
|
+
ensure
|
|
2507
|
+
Book.where(author_id: nil, name: '["Array"]').delete_all
|
|
2508
|
+
Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
|
|
2509
|
+
end
|
|
2510
|
+
end
|
|
2511
|
+
|
|
2512
|
+
module ActiveRecord
|
|
2513
|
+
class Migration
|
|
2514
|
+
class InvalidOptionsTest < ActiveRecord::TestCase
|
|
2515
|
+
# Include the additional SQL Server migration options.
|
|
2516
|
+
undef_method :invalid_add_column_option_exception_message
|
|
2517
|
+
def invalid_add_column_option_exception_message(key)
|
|
2518
|
+
default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
|
|
2519
|
+
default_keys.concat([":is_identity"]) # SQL Server additional valid keys
|
|
2520
|
+
|
|
2521
|
+
"Unknown key: :#{key}. Valid keys are: #{default_keys.join(", ")}"
|
|
2522
|
+
end
|
|
2523
|
+
end
|
|
2524
|
+
end
|
|
2525
|
+
end
|
|
2526
|
+
|
|
2527
|
+
# SQL Server does not support upsert. Removed dependency on `insert_all` that uses upsert.
|
|
2528
|
+
class ActiveRecord::Encryption::ConcurrencyTest < ActiveRecord::EncryptionTestCase
|
|
2529
|
+
undef_method :thread_encrypting_and_decrypting
|
|
2530
|
+
def thread_encrypting_and_decrypting(thread_label)
|
|
2531
|
+
posts = 100.times.collect { |index| EncryptedPost.create! title: "Article #{index} (#{thread_label})", body: "Body #{index} (#{thread_label})" }
|
|
2532
|
+
|
|
2533
|
+
Thread.new do
|
|
2534
|
+
posts.each.with_index do |article, index|
|
|
2535
|
+
assert_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
|
|
2536
|
+
article.decrypt
|
|
2537
|
+
assert_not_encrypted_attribute article, :title, "Article #{index} (#{thread_label})"
|
|
2538
|
+
end
|
|
2539
|
+
end
|
|
2540
|
+
end
|
|
2541
|
+
end
|
|
2542
|
+
|
|
2543
|
+
# Need to use `install_unregistered_type_fallback` instead of `install_unregistered_type_error` so that message-pack
|
|
2544
|
+
# can read and write `ActiveRecord::ConnectionAdapters::SQLServer::Type::Data` objects.
|
|
2545
|
+
class ActiveRecordMessagePackTest < ActiveRecord::TestCase
|
|
2546
|
+
private
|
|
2547
|
+
undef_method :serializer
|
|
2548
|
+
def serializer
|
|
2549
|
+
@serializer ||= ::MessagePack::Factory.new.tap do |factory|
|
|
2550
|
+
ActiveRecord::MessagePack::Extensions.install(factory)
|
|
2551
|
+
ActiveSupport::MessagePack::Extensions.install(factory)
|
|
2552
|
+
ActiveSupport::MessagePack::Extensions.install_unregistered_type_fallback(factory)
|
|
2553
|
+
end
|
|
2554
|
+
end
|
|
2555
|
+
end
|
|
2556
|
+
|
|
2557
|
+
class StoreTest < ActiveRecord::TestCase
|
|
2558
|
+
# Set the attribute as JSON type for the `StoreTest#saved changes tracking for accessors with json column` test.
|
|
2559
|
+
Admin::User.attribute :json_options, ActiveRecord::Type::SQLServer::Json.new
|
|
2560
|
+
end
|
|
2561
|
+
|
|
2562
|
+
class TestDatabasesTest < ActiveRecord::TestCase
|
|
2563
|
+
# Tests are not about a specific adapter.
|
|
2564
|
+
coerce_all_tests!
|
|
2565
|
+
end
|
|
2566
|
+
|
|
2567
|
+
module ActiveRecord
|
|
2568
|
+
module ConnectionAdapters
|
|
2569
|
+
class ConnectionHandlersShardingDbTest < ActiveRecord::TestCase
|
|
2570
|
+
# Tests are not about a specific adapter.
|
|
2571
|
+
coerce_all_tests!
|
|
2572
|
+
end
|
|
2573
|
+
end
|
|
2574
|
+
end
|
|
2575
|
+
|
|
2576
|
+
module ActiveRecord
|
|
2577
|
+
module ConnectionAdapters
|
|
2578
|
+
class ConnectionSwappingNestedTest < ActiveRecord::TestCase
|
|
2579
|
+
# Tests are not about a specific adapter.
|
|
2580
|
+
coerce_all_tests!
|
|
2581
|
+
end
|
|
2582
|
+
end
|
|
2583
|
+
end
|
|
2584
|
+
|
|
2585
|
+
module ActiveRecord
|
|
2586
|
+
module ConnectionAdapters
|
|
2587
|
+
class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
|
|
2588
|
+
# Tests are not about a specific adapter.
|
|
2589
|
+
coerce_tests! :test_switching_connections_via_handler
|
|
2590
|
+
end
|
|
2591
|
+
end
|
|
2592
|
+
end
|
|
2593
|
+
|
|
2594
|
+
module ActiveRecord
|
|
2595
|
+
module ConnectionAdapters
|
|
2596
|
+
class ConnectionHandlersMultiPoolConfigTest < ActiveRecord::TestCase
|
|
2597
|
+
# Tests are not about a specific adapter.
|
|
2598
|
+
coerce_all_tests!
|
|
2599
|
+
end
|
|
2600
|
+
end
|
|
2601
|
+
end
|
|
2602
|
+
|
|
2603
|
+
module ActiveRecord
|
|
2604
|
+
class Migration
|
|
2605
|
+
class CheckConstraintTest < ActiveRecord::TestCase
|
|
2606
|
+
# SQL Server formats the check constraint expression differently.
|
|
2607
|
+
coerce_tests! :test_check_constraints
|
|
2608
|
+
def test_check_constraints_coerced
|
|
2609
|
+
check_constraints = @connection.check_constraints("products")
|
|
2610
|
+
assert_equal 1, check_constraints.size
|
|
2611
|
+
|
|
2612
|
+
constraint = check_constraints.first
|
|
2613
|
+
assert_equal "products", constraint.table_name
|
|
2614
|
+
assert_equal "products_price_check", constraint.name
|
|
2615
|
+
assert_equal "[price]>[discounted_price]", constraint.expression
|
|
2616
|
+
end
|
|
2617
|
+
|
|
2618
|
+
# SQL Server formats the check constraint expression differently.
|
|
2619
|
+
coerce_tests! :test_add_check_constraint
|
|
2620
|
+
def test_add_check_constraint_coerced
|
|
2621
|
+
@connection.add_check_constraint :trades, "quantity > 0"
|
|
2622
|
+
|
|
2623
|
+
check_constraints = @connection.check_constraints("trades")
|
|
2624
|
+
assert_equal 1, check_constraints.size
|
|
2625
|
+
|
|
2626
|
+
constraint = check_constraints.first
|
|
2627
|
+
assert_equal "trades", constraint.table_name
|
|
2628
|
+
assert_equal "chk_rails_2189e9f96c", constraint.name
|
|
2629
|
+
assert_equal "[quantity]>(0)", constraint.expression
|
|
2630
|
+
end
|
|
2631
|
+
|
|
2632
|
+
# SQL Server formats the check constraint expression differently.
|
|
2633
|
+
coerce_tests! :test_remove_check_constraint
|
|
2634
|
+
def test_remove_check_constraint_coerced
|
|
2635
|
+
@connection.add_check_constraint :trades, "price > 0", name: "price_check"
|
|
2636
|
+
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
|
|
2637
|
+
|
|
2638
|
+
assert_equal 2, @connection.check_constraints("trades").size
|
|
2639
|
+
@connection.remove_check_constraint :trades, name: "quantity_check"
|
|
2640
|
+
assert_equal 1, @connection.check_constraints("trades").size
|
|
2641
|
+
|
|
2642
|
+
constraint = @connection.check_constraints("trades").first
|
|
2643
|
+
assert_equal "trades", constraint.table_name
|
|
2644
|
+
assert_equal "price_check", constraint.name
|
|
2645
|
+
assert_equal "[price]>(0)", constraint.expression
|
|
2646
|
+
|
|
2647
|
+
@connection.remove_check_constraint :trades, name: :price_check # name as a symbol
|
|
2648
|
+
assert_empty @connection.check_constraints("trades")
|
|
2649
|
+
end
|
|
2650
|
+
end
|
|
2651
|
+
end
|
|
2652
|
+
end
|
|
2653
|
+
|
|
2654
|
+
module ActiveRecord
|
|
2655
|
+
module ConnectionAdapters
|
|
2656
|
+
class PoolConfig
|
|
2657
|
+
class ResolverTest < ActiveRecord::TestCase
|
|
2658
|
+
# SQL Server was not included in the list of available adapters in the error message.
|
|
2659
|
+
coerce_tests! :test_url_invalid_adapter
|
|
2660
|
+
def test_url_invalid_adapter_coerced
|
|
2661
|
+
error = assert_raises(AdapterNotFound) do
|
|
2662
|
+
Base.connection_handler.establish_connection "ridiculous://foo?encoding=utf8"
|
|
2663
|
+
end
|
|
2664
|
+
|
|
2665
|
+
assert_match "Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.", error.message
|
|
2666
|
+
end
|
|
2667
|
+
end
|
|
2668
|
+
end
|
|
2669
|
+
end
|
|
2670
|
+
end
|
|
2671
|
+
|
|
2672
|
+
module ActiveRecord
|
|
2673
|
+
module ConnectionAdapters
|
|
2674
|
+
class RegistrationIsolatedTest < ActiveRecord::TestCase
|
|
2675
|
+
# SQL Server was not included in the list of available adapters in the error message.
|
|
2676
|
+
coerce_tests! %r{resolve raises if the adapter is using the pre 7.2 adapter registration API}
|
|
2677
|
+
def resolve_raises_if_the_adapter_is_using_the_pre_7_2_adapter_registration_API
|
|
2678
|
+
exception = assert_raises(ActiveRecord::AdapterNotFound) do
|
|
2679
|
+
ActiveRecord::ConnectionAdapters.resolve("fake_legacy")
|
|
2680
|
+
end
|
|
2681
|
+
|
|
2682
|
+
assert_equal(
|
|
2683
|
+
"Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.",
|
|
2684
|
+
exception.message
|
|
2685
|
+
)
|
|
2686
|
+
ensure
|
|
2687
|
+
ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).delete("fake_legacy")
|
|
2688
|
+
end
|
|
2689
|
+
end
|
|
2690
|
+
end
|
|
2691
|
+
end
|
|
2692
|
+
|
|
2693
|
+
|
|
2694
|
+
module ActiveRecord
|
|
2695
|
+
class TableMetadataTest < ActiveSupport::TestCase
|
|
2696
|
+
# Adapter returns an object that is subclass of what is expected in the original test.
|
|
2697
|
+
coerce_tests! %r{#associated_table creates the right type caster for joined table with different association name}
|
|
2698
|
+
def associated_table_creates_the_right_type_caster_for_joined_table_with_different_association_name_coerced
|
|
2699
|
+
base_table_metadata = TableMetadata.new(AuditRequiredDeveloper, Arel::Table.new("developers"))
|
|
2700
|
+
|
|
2701
|
+
associated_table_metadata = base_table_metadata.associated_table("audit_logs")
|
|
2702
|
+
|
|
2703
|
+
assert associated_table_metadata.arel_table.type_for_attribute(:message).is_a?(ActiveRecord::Type::String)
|
|
2704
|
+
end
|
|
2705
|
+
end
|
|
2706
|
+
end
|
|
2707
|
+
|
|
2708
|
+
module ActiveRecord
|
|
2709
|
+
module TypeCaster
|
|
2710
|
+
class ConnectionTest < ActiveSupport::TestCase
|
|
2711
|
+
# Adapter returns an object that is subclass of what is expected in the original test.
|
|
2712
|
+
coerce_tests! %r{#type_for_attribute is not aware of custom types}
|
|
2713
|
+
def type_for_attribute_is_not_aware_of_custom_types_coerced
|
|
2714
|
+
type_caster = Connection.new(AttributedDeveloper, "developers")
|
|
2715
|
+
|
|
2716
|
+
type = type_caster.type_for_attribute(:name)
|
|
2717
|
+
|
|
2718
|
+
assert_not_equal DeveloperName, type.class
|
|
2719
|
+
assert type.is_a?(ActiveRecord::Type::String)
|
|
2720
|
+
end
|
|
2721
|
+
end
|
|
2722
|
+
end
|
|
2723
|
+
end
|
|
2724
|
+
|
|
2725
|
+
require "models/car"
|
|
2726
|
+
class ExplainTest < ActiveRecord::TestCase
|
|
2727
|
+
# Expected query slightly different from because of 'sp_executesql' and query parameters.
|
|
2728
|
+
coerce_tests! :test_relation_explain_with_first
|
|
2729
|
+
def test_relation_explain_with_first_coerced
|
|
2730
|
+
expected_query = capture_sql {
|
|
2731
|
+
Car.all.first
|
|
2732
|
+
}.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
|
|
2733
|
+
message = Car.all.explain.first
|
|
2734
|
+
assert_match(/^EXPLAIN/, message)
|
|
2735
|
+
assert_match(expected_query, message)
|
|
2736
|
+
end
|
|
2737
|
+
|
|
2738
|
+
# Expected query slightly different from because of 'sp_executesql' and query parameters.
|
|
2739
|
+
coerce_tests! :test_relation_explain_with_last
|
|
2740
|
+
def test_relation_explain_with_last_coerced
|
|
2741
|
+
expected_query = capture_sql {
|
|
2742
|
+
Car.all.last
|
|
2743
|
+
}.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
|
|
2744
|
+
expected_query = expected_query
|
|
2745
|
+
message = Car.all.explain.last
|
|
2746
|
+
|
|
2747
|
+
assert_match(/^EXPLAIN/, message)
|
|
2748
|
+
assert_match(expected_query, message)
|
|
2749
|
+
end
|
|
2750
|
+
end
|
|
2751
|
+
|
|
2752
|
+
module ActiveRecord
|
|
2753
|
+
module Assertions
|
|
2754
|
+
class QueryAssertionsTest < ActiveSupport::TestCase
|
|
2755
|
+
# Query slightly different in original test.
|
|
2756
|
+
coerce_tests! :test_assert_queries_match
|
|
2757
|
+
def test_assert_queries_match_coerced
|
|
2758
|
+
assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 1) { Post.first }
|
|
2759
|
+
assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i) { Post.first }
|
|
2760
|
+
|
|
2761
|
+
error = assert_raises(Minitest::Assertion) {
|
|
2762
|
+
assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 2) { Post.first }
|
|
2763
|
+
}
|
|
2764
|
+
assert_match(/1 instead of 2 queries/, error.message)
|
|
2765
|
+
|
|
2766
|
+
error = assert_raises(Minitest::Assertion) {
|
|
2767
|
+
assert_queries_match(/ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, count: 0) { Post.first }
|
|
2768
|
+
}
|
|
2769
|
+
assert_match(/1 instead of 0 queries/, error.message)
|
|
2770
|
+
end
|
|
2771
|
+
end
|
|
2772
|
+
end
|
|
2773
|
+
end
|
|
2774
|
+
|
|
2775
|
+
module ActiveRecord
|
|
2776
|
+
class WithTest < ActiveRecord::TestCase
|
|
2777
|
+
# SQL contains just 'WITH' instead of 'WITH RECURSIVE' as expected by the original test.
|
|
2778
|
+
coerce_tests! :test_with_recursive
|
|
2779
|
+
def test_with_recursive_coerced
|
|
2780
|
+
top_companies = Company.where(firm_id: nil).to_a
|
|
2781
|
+
child_companies = Company.where(firm_id: top_companies).to_a
|
|
2782
|
+
top_companies_and_children = (top_companies.map(&:id) + child_companies.map(&:id)).sort
|
|
2783
|
+
|
|
2784
|
+
relation = Company.with_recursive(
|
|
2785
|
+
top_companies_and_children: [
|
|
2786
|
+
Company.where(firm_id: nil),
|
|
2787
|
+
Company.joins("JOIN top_companies_and_children ON companies.firm_id = top_companies_and_children.id"),
|
|
2788
|
+
]
|
|
2789
|
+
).from("top_companies_and_children AS companies")
|
|
2790
|
+
|
|
2791
|
+
assert_equal top_companies_and_children, relation.order(:id).pluck(:id)
|
|
2792
|
+
assert_match "WITH ", relation.to_sql
|
|
2793
|
+
end
|
|
2794
|
+
end
|
|
2795
|
+
end
|
|
2796
|
+
|