activerecord-sqlserver-adapter 6.0.2 → 6.1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -56
- data/README.md +28 -11
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +8 -6
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -70
- data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
- data/lib/active_record/sqlserver_base.rb +9 -15
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
- data/lib/arel/visitors/sqlserver.rb +74 -29
- data/test/cases/adapter_test_sqlserver.rb +27 -17
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/coerced_tests.rb +544 -77
- data/test/cases/column_test_sqlserver.rb +4 -0
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
- data/test/cases/fetch_test_sqlserver.rb +18 -0
- data/test/cases/in_clause_test_sqlserver.rb +27 -0
- data/test/cases/migration_test_sqlserver.rb +7 -0
- data/test/cases/order_test_sqlserver.rb +7 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +38 -2
- data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/schema/sqlserver_specific_schema.rb +25 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +14 -12
- metadata +23 -8
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
|
@@ -53,7 +53,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
53
53
|
assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
|
|
54
54
|
|
|
55
55
|
# Test when database and owner included in table name.
|
|
56
|
-
|
|
56
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
57
|
+
Topic.table_name = "#{db_config.database}.dbo.topics"
|
|
57
58
|
assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
|
|
58
59
|
ensure
|
|
59
60
|
Topic.table_name = "topics"
|
|
@@ -64,8 +65,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
64
65
|
arunit_connection = Topic.connection
|
|
65
66
|
arunit2_connection = College.connection
|
|
66
67
|
|
|
67
|
-
arunit_database = arunit_connection.pool.
|
|
68
|
-
arunit2_database = arunit2_connection.pool.
|
|
68
|
+
arunit_database = arunit_connection.pool.db_config.database
|
|
69
|
+
arunit2_database = arunit2_connection.pool.db_config.database
|
|
69
70
|
|
|
70
71
|
# Assert that connections use different default databases schemas.
|
|
71
72
|
assert_not_equal arunit_database, arunit2_database
|
|
@@ -100,21 +101,31 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
100
101
|
|
|
101
102
|
it "test bad connection" do
|
|
102
103
|
assert_raise ActiveRecord::NoDatabaseError do
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
105
|
+
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
|
|
106
|
+
ActiveRecord::Base.sqlserver_connection configuration
|
|
105
107
|
end
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
it "test database exists returns false if database does not exist" do
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
112
|
+
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
|
|
113
|
+
assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
|
|
111
114
|
"expected database to not exist"
|
|
112
115
|
end
|
|
113
116
|
|
|
114
117
|
it "test database exists returns true when the database exists" do
|
|
115
|
-
|
|
116
|
-
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(
|
|
117
|
-
"expected database #{
|
|
118
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
119
|
+
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
|
|
120
|
+
"expected database #{db_config.database} to exist"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "test primary key violation" do
|
|
124
|
+
Post.create!(id: 0, title: 'Setup', body: 'Create post with primary key of zero')
|
|
125
|
+
|
|
126
|
+
assert_raise ActiveRecord::RecordNotUnique do
|
|
127
|
+
Post.create!(id: 0, title: 'Test', body: 'Try to create another post with primary key of zero')
|
|
128
|
+
end
|
|
118
129
|
end
|
|
119
130
|
|
|
120
131
|
describe "with different language" do
|
|
@@ -374,7 +385,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
374
385
|
assert !SSTestCustomersView.columns.blank?
|
|
375
386
|
assert_equal columns.size, SSTestCustomersView.columns.size
|
|
376
387
|
columns.each do |colname|
|
|
377
|
-
assert_instance_of ActiveRecord::ConnectionAdapters::
|
|
388
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
|
378
389
|
SSTestCustomersView.columns_hash[colname],
|
|
379
390
|
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
|
|
380
391
|
end
|
|
@@ -401,7 +412,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
401
412
|
assert !SSTestStringDefaultsView.columns.blank?
|
|
402
413
|
assert_equal columns.size, SSTestStringDefaultsView.columns.size
|
|
403
414
|
columns.each do |colname|
|
|
404
|
-
assert_instance_of ActiveRecord::ConnectionAdapters::
|
|
415
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
|
405
416
|
SSTestStringDefaultsView.columns_hash[colname],
|
|
406
417
|
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
|
|
407
418
|
end
|
|
@@ -466,12 +477,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
466
477
|
describe "block writes to a database" do
|
|
467
478
|
def setup
|
|
468
479
|
@conn = ActiveRecord::Base.connection
|
|
469
|
-
@connection_handler = ActiveRecord::Base.connection_handler
|
|
470
480
|
end
|
|
471
481
|
|
|
472
482
|
def test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
473
483
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
|
474
|
-
|
|
484
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
475
485
|
@conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
|
476
486
|
end
|
|
477
487
|
end
|
|
@@ -481,7 +491,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
481
491
|
@conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
|
482
492
|
|
|
483
493
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
|
484
|
-
|
|
494
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
485
495
|
@conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
|
|
486
496
|
end
|
|
487
497
|
end
|
|
@@ -491,7 +501,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
491
501
|
@conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
|
492
502
|
|
|
493
503
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
|
494
|
-
|
|
504
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
495
505
|
@conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
|
|
496
506
|
end
|
|
497
507
|
end
|
|
@@ -500,7 +510,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
500
510
|
def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
|
|
501
511
|
@conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
|
502
512
|
|
|
503
|
-
|
|
513
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
504
514
|
assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
|
|
505
515
|
end
|
|
506
516
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
require "migrations/create_clients_and_change_column_collation"
|
|
5
|
+
|
|
6
|
+
class ChangeColumnCollationTestSqlServer < ActiveRecord::TestCase
|
|
7
|
+
before do
|
|
8
|
+
@old_verbose = ActiveRecord::Migration.verbose
|
|
9
|
+
ActiveRecord::Migration.verbose = false
|
|
10
|
+
CreateClientsAndChangeColumnCollation.new.up
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
CreateClientsAndChangeColumnCollation.new.down
|
|
15
|
+
ActiveRecord::Migration.verbose = @old_verbose
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def find_column(table, name)
|
|
19
|
+
table.find { |column| column.name == name }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
let(:clients_table) { connection.columns("clients") }
|
|
23
|
+
let(:name_column) { find_column(clients_table, "name") }
|
|
24
|
+
let(:code_column) { find_column(clients_table, "code") }
|
|
25
|
+
|
|
26
|
+
it "change column collation to other than default" do
|
|
27
|
+
_(name_column.collation).must_equal "SQL_Latin1_General_CP1_CS_AS"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "change column collation to default" do
|
|
31
|
+
_(code_column.collation).must_equal "SQL_Latin1_General_CP1_CI_AS"
|
|
32
|
+
end
|
|
33
|
+
end
|
data/test/cases/coerced_tests.rb
CHANGED
|
@@ -24,22 +24,35 @@ class UniquenessValidationTest < ActiveRecord::TestCase
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
coerce_tests! :
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
skip if database_collation.include?("_CI_")
|
|
27
|
+
# Same as original coerced test except that it handles default SQL Server case-insensitive collation.
|
|
28
|
+
coerce_tests! :test_validate_uniqueness_by_default_database_collation
|
|
29
|
+
def test_validate_uniqueness_by_default_database_collation_coerced
|
|
30
|
+
Topic.validates_uniqueness_of(:author_email_address)
|
|
32
31
|
|
|
33
|
-
|
|
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
|
|
34
47
|
end
|
|
35
48
|
end
|
|
36
49
|
|
|
37
50
|
require "models/event"
|
|
38
51
|
module ActiveRecord
|
|
39
52
|
class AdapterTest < ActiveRecord::TestCase
|
|
40
|
-
#
|
|
41
|
-
coerce_tests! :
|
|
42
|
-
coerce_tests! :
|
|
53
|
+
# Legacy binds are not supported.
|
|
54
|
+
coerce_tests! :test_select_all_insert_update_delete_with_casted_binds
|
|
55
|
+
coerce_tests! :test_select_all_insert_update_delete_with_legacy_binds
|
|
43
56
|
|
|
44
57
|
# As far as I can tell, SQL Server does not support null bytes in strings.
|
|
45
58
|
coerce_tests! :test_update_prepared_statement
|
|
@@ -54,13 +67,63 @@ module ActiveRecord
|
|
|
54
67
|
assert_not_nil error.cause
|
|
55
68
|
end
|
|
56
69
|
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module ActiveRecord
|
|
74
|
+
class AdapterPreventWritesTest < ActiveRecord::TestCase
|
|
75
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
76
|
+
coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
77
|
+
def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
|
|
78
|
+
Subscriber.send(:load_schema!)
|
|
79
|
+
original_test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
83
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
84
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
|
|
85
|
+
Subscriber.send(:load_schema!)
|
|
86
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
90
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
91
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
|
|
92
|
+
Subscriber.send(:load_schema!)
|
|
93
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
57
97
|
|
|
98
|
+
module ActiveRecord
|
|
99
|
+
class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
|
|
58
100
|
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
59
101
|
coerce_tests! :test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
60
102
|
def test_errors_when_an_insert_query_is_called_while_preventing_writes_coerced
|
|
61
103
|
Subscriber.send(:load_schema!)
|
|
62
104
|
original_test_errors_when_an_insert_query_is_called_while_preventing_writes
|
|
63
105
|
end
|
|
106
|
+
|
|
107
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
108
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
|
|
109
|
+
def test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes_coerced
|
|
110
|
+
Subscriber.send(:load_schema!)
|
|
111
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_slash_star_comment_is_called_while_preventing_writes
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
115
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
116
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes_coerced
|
|
117
|
+
Subscriber.send(:load_schema!)
|
|
118
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_is_called_while_preventing_writes
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
122
|
+
coerce_tests! :test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
123
|
+
def test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes_coerced
|
|
124
|
+
Subscriber.send(:load_schema!)
|
|
125
|
+
original_test_errors_when_an_insert_query_prefixed_by_a_double_dash_comment_containing_read_command_is_called_while_preventing_writes
|
|
126
|
+
end
|
|
64
127
|
end
|
|
65
128
|
end
|
|
66
129
|
|
|
@@ -176,7 +239,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|
|
176
239
|
# SQL Server does not have query for release_savepoint
|
|
177
240
|
coerce_tests! %r{an empty transaction does not raise if preventing writes}
|
|
178
241
|
test "an empty transaction does not raise if preventing writes coerced" do
|
|
179
|
-
ActiveRecord::Base.
|
|
242
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
180
243
|
assert_queries(1, ignore_none: true) do
|
|
181
244
|
Bird.transaction do
|
|
182
245
|
ActiveRecord::Base.connection.materialize_transactions
|
|
@@ -234,6 +297,55 @@ module ActiveRecord
|
|
|
234
297
|
coerce_tests! :test_statement_cache_with_find_by
|
|
235
298
|
coerce_tests! :test_statement_cache_with_in_clause
|
|
236
299
|
coerce_tests! :test_statement_cache_with_sql_string_literal
|
|
300
|
+
|
|
301
|
+
# Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
|
|
302
|
+
coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
|
|
303
|
+
def test_bind_params_to_sql_with_prepared_statements_coerced
|
|
304
|
+
assert_bind_params_to_sql_coerced(prepared: true)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def test_bind_params_to_sql_with_unprepared_statements_coerced
|
|
308
|
+
@connection.unprepared_statement do
|
|
309
|
+
assert_bind_params_to_sql_coerced(prepared: false)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
private
|
|
314
|
+
|
|
315
|
+
def assert_bind_params_to_sql_coerced(prepared:)
|
|
316
|
+
table = Author.quoted_table_name
|
|
317
|
+
pk = "#{table}.#{Author.quoted_primary_key}"
|
|
318
|
+
|
|
319
|
+
# prepared_statements: true
|
|
320
|
+
#
|
|
321
|
+
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
322
|
+
#
|
|
323
|
+
# prepared_statements: false
|
|
324
|
+
#
|
|
325
|
+
# SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
|
|
326
|
+
#
|
|
327
|
+
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
|
|
328
|
+
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
329
|
+
|
|
330
|
+
authors = Author.where(id: [1, 2, 3, nil])
|
|
331
|
+
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
332
|
+
assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
333
|
+
|
|
334
|
+
# prepared_statements: true
|
|
335
|
+
#
|
|
336
|
+
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
337
|
+
#
|
|
338
|
+
# prepared_statements: false
|
|
339
|
+
#
|
|
340
|
+
# SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
|
|
341
|
+
#
|
|
342
|
+
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
|
|
343
|
+
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
344
|
+
|
|
345
|
+
authors = Author.where(id: [1, 2, 3, 9223372036854775808])
|
|
346
|
+
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
347
|
+
assert_sql(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
348
|
+
end
|
|
237
349
|
end
|
|
238
350
|
end
|
|
239
351
|
|
|
@@ -256,11 +368,112 @@ class CalculationsTest < ActiveRecord::TestCase
|
|
|
256
368
|
original_test_offset_is_kept
|
|
257
369
|
end
|
|
258
370
|
|
|
259
|
-
#
|
|
371
|
+
# The SQL Server `AVG()` function for a list of integers returns an integer (not a decimal).
|
|
260
372
|
coerce_tests! :test_should_return_decimal_average_of_integer_field
|
|
261
373
|
def test_should_return_decimal_average_of_integer_field_coerced
|
|
262
374
|
value = Account.average(:id)
|
|
263
|
-
assert_equal
|
|
375
|
+
assert_equal 3, value
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
379
|
+
# Match SQL Server limit implementation.
|
|
380
|
+
coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_sql
|
|
381
|
+
def test_select_avg_with_group_by_as_virtual_attribute_with_sql_coerced
|
|
382
|
+
rails_core = companies(:rails_core)
|
|
383
|
+
|
|
384
|
+
sql = <<~SQL
|
|
385
|
+
SELECT firm_id, AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit
|
|
386
|
+
FROM accounts
|
|
387
|
+
WHERE firm_id = ?
|
|
388
|
+
GROUP BY firm_id
|
|
389
|
+
ORDER BY firm_id
|
|
390
|
+
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
|
391
|
+
SQL
|
|
392
|
+
|
|
393
|
+
account = Account.find_by_sql([sql, rails_core]).first
|
|
394
|
+
|
|
395
|
+
# id was not selected, so it should be nil
|
|
396
|
+
# (cannot select id because it wasn't used in the GROUP BY clause)
|
|
397
|
+
assert_nil account.id
|
|
398
|
+
|
|
399
|
+
# firm_id was explicitly selected, so it should be present
|
|
400
|
+
assert_equal(rails_core, account.firm)
|
|
401
|
+
|
|
402
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
403
|
+
assert_equal(52.5, account.avg_credit_limit)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
407
|
+
# Order column must be in the GROUP clause.
|
|
408
|
+
coerce_tests! :test_select_avg_with_group_by_as_virtual_attribute_with_ar
|
|
409
|
+
def test_select_avg_with_group_by_as_virtual_attribute_with_ar_coerced
|
|
410
|
+
rails_core = companies(:rails_core)
|
|
411
|
+
|
|
412
|
+
account = Account
|
|
413
|
+
.select(:firm_id, "AVG(CAST(credit_limit AS DECIMAL)) AS avg_credit_limit")
|
|
414
|
+
.where(firm: rails_core)
|
|
415
|
+
.group(:firm_id)
|
|
416
|
+
.order(:firm_id)
|
|
417
|
+
.take!
|
|
418
|
+
|
|
419
|
+
# id was not selected, so it should be nil
|
|
420
|
+
# (cannot select id because it wasn't used in the GROUP BY clause)
|
|
421
|
+
assert_nil account.id
|
|
422
|
+
|
|
423
|
+
# firm_id was explicitly selected, so it should be present
|
|
424
|
+
assert_equal(rails_core, account.firm)
|
|
425
|
+
|
|
426
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
427
|
+
assert_equal(52.5, account.avg_credit_limit)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
431
|
+
# SELECT columns must be in the GROUP clause.
|
|
432
|
+
# Match SQL Server limit implementation.
|
|
433
|
+
coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
|
|
434
|
+
def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql_coerced
|
|
435
|
+
rails_core = companies(:rails_core)
|
|
436
|
+
|
|
437
|
+
sql = <<~SQL
|
|
438
|
+
SELECT companies.*, AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit
|
|
439
|
+
FROM companies
|
|
440
|
+
INNER JOIN accounts ON companies.id = accounts.firm_id
|
|
441
|
+
WHERE companies.id = ?
|
|
442
|
+
GROUP BY companies.id, companies.type, companies.firm_id, companies.firm_name, companies.name, companies.client_of, companies.rating, companies.account_id, companies.description
|
|
443
|
+
ORDER BY companies.id
|
|
444
|
+
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
|
445
|
+
SQL
|
|
446
|
+
|
|
447
|
+
firm = DependentFirm.find_by_sql([sql, rails_core]).first
|
|
448
|
+
|
|
449
|
+
# all the DependentFirm attributes should be present
|
|
450
|
+
assert_equal rails_core, firm
|
|
451
|
+
assert_equal rails_core.name, firm.name
|
|
452
|
+
|
|
453
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
454
|
+
assert_equal(52.5, firm.avg_credit_limit)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# In SQL Server the `AVG()` function for a list of integers returns an integer so need to cast values as decimals before averaging.
|
|
459
|
+
# SELECT columns must be in the GROUP clause.
|
|
460
|
+
coerce_tests! :test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
|
|
461
|
+
def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar_coerced
|
|
462
|
+
rails_core = companies(:rails_core)
|
|
463
|
+
|
|
464
|
+
firm = DependentFirm
|
|
465
|
+
.select("companies.*", "AVG(CAST(accounts.credit_limit AS DECIMAL)) AS avg_credit_limit")
|
|
466
|
+
.where(id: rails_core)
|
|
467
|
+
.joins(:account)
|
|
468
|
+
.group(:id, :type, :firm_id, :firm_name, :name, :client_of, :rating, :account_id, :description)
|
|
469
|
+
.take!
|
|
470
|
+
|
|
471
|
+
# all the DependentFirm attributes should be present
|
|
472
|
+
assert_equal rails_core, firm
|
|
473
|
+
assert_equal rails_core.name, firm.name
|
|
474
|
+
|
|
475
|
+
# avg_credit_limit should be present as a virtual attribute
|
|
476
|
+
assert_equal(52.5, firm.avg_credit_limit)
|
|
264
477
|
end
|
|
265
478
|
|
|
266
479
|
# Match SQL Server limit implementation
|
|
@@ -329,14 +542,18 @@ module ActiveRecord
|
|
|
329
542
|
coerce_tests! :test_quote_ar_object
|
|
330
543
|
def test_quote_ar_object_coerced
|
|
331
544
|
value = DatetimePrimaryKey.new(id: @time)
|
|
332
|
-
|
|
545
|
+
assert_deprecated do
|
|
546
|
+
assert_equal "'02-14-2017 12:34:56.79'", @connection.quote(value)
|
|
547
|
+
end
|
|
333
548
|
end
|
|
334
549
|
|
|
335
550
|
# Use our date format.
|
|
336
551
|
coerce_tests! :test_type_cast_ar_object
|
|
337
552
|
def test_type_cast_ar_object_coerced
|
|
338
553
|
value = DatetimePrimaryKey.new(id: @time)
|
|
339
|
-
|
|
554
|
+
assert_deprecated do
|
|
555
|
+
assert_equal "02-14-2017 12:34:56.79", @connection.type_cast(value)
|
|
556
|
+
end
|
|
340
557
|
end
|
|
341
558
|
end
|
|
342
559
|
end
|
|
@@ -551,11 +768,6 @@ module ActiveRecord
|
|
|
551
768
|
end
|
|
552
769
|
end
|
|
553
770
|
|
|
554
|
-
class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
|
|
555
|
-
# Skip this test with /tmp/my_schema_cache.yml path on Windows.
|
|
556
|
-
coerce_tests! :test_dump_schema_cache if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
557
|
-
end
|
|
558
|
-
|
|
559
771
|
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
|
|
560
772
|
# We extend `local_database?` so that common VM IPs can be used.
|
|
561
773
|
coerce_tests! :test_ignores_remote_databases, :test_warning_for_remote_databases
|
|
@@ -620,7 +832,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|
|
620
832
|
end
|
|
621
833
|
|
|
622
834
|
require "models/topic"
|
|
835
|
+
require "models/customer"
|
|
836
|
+
require "models/non_primary_key"
|
|
623
837
|
class FinderTest < ActiveRecord::TestCase
|
|
838
|
+
fixtures :customers, :topics, :authors
|
|
839
|
+
|
|
624
840
|
# We have implicit ordering, via FETCH.
|
|
625
841
|
coerce_tests! %r{doesn't have implicit ordering},
|
|
626
842
|
:test_find_doesnt_have_implicit_ordering
|
|
@@ -665,6 +881,87 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
665
881
|
end
|
|
666
882
|
end
|
|
667
883
|
end
|
|
884
|
+
|
|
885
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
886
|
+
coerce_tests! :test_include_on_unloaded_relation_with_match
|
|
887
|
+
def test_include_on_unloaded_relation_with_match_coerced
|
|
888
|
+
assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
889
|
+
assert_equal true, Customer.where(name: "David").include?(customers(:david))
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
894
|
+
coerce_tests! :test_include_on_unloaded_relation_without_match
|
|
895
|
+
def test_include_on_unloaded_relation_without_match_coerced
|
|
896
|
+
assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
897
|
+
assert_equal false, Customer.where(name: "David").include?(customers(:mary))
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
902
|
+
coerce_tests! :test_member_on_unloaded_relation_with_match
|
|
903
|
+
def test_member_on_unloaded_relation_with_match_coerced
|
|
904
|
+
assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
905
|
+
assert_equal true, Customer.where(name: "David").member?(customers(:david))
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
910
|
+
coerce_tests! :test_member_on_unloaded_relation_without_match
|
|
911
|
+
def test_member_on_unloaded_relation_without_match_coerced
|
|
912
|
+
assert_sql(/1 AS one.*FETCH NEXT @2 ROWS ONLY.*@2 = 1/) do
|
|
913
|
+
assert_equal false, Customer.where(name: "David").member?(customers(:mary))
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
918
|
+
coerce_tests! :test_implicit_order_column_is_configurable
|
|
919
|
+
def test_implicit_order_column_is_configurable_coerced
|
|
920
|
+
old_implicit_order_column = Topic.implicit_order_column
|
|
921
|
+
Topic.implicit_order_column = "title"
|
|
922
|
+
|
|
923
|
+
assert_equal topics(:fifth), Topic.first
|
|
924
|
+
assert_equal topics(:third), Topic.last
|
|
925
|
+
|
|
926
|
+
c = Topic.connection
|
|
927
|
+
assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
928
|
+
Topic.last
|
|
929
|
+
}
|
|
930
|
+
ensure
|
|
931
|
+
Topic.implicit_order_column = old_implicit_order_column
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
935
|
+
coerce_tests! :test_implicit_order_set_to_primary_key
|
|
936
|
+
def test_implicit_order_set_to_primary_key_coerced
|
|
937
|
+
old_implicit_order_column = Topic.implicit_order_column
|
|
938
|
+
Topic.implicit_order_column = "id"
|
|
939
|
+
|
|
940
|
+
c = Topic.connection
|
|
941
|
+
assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
942
|
+
Topic.last
|
|
943
|
+
}
|
|
944
|
+
ensure
|
|
945
|
+
Topic.implicit_order_column = old_implicit_order_column
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
949
|
+
coerce_tests! :test_implicit_order_for_model_without_primary_key
|
|
950
|
+
def test_implicit_order_for_model_without_primary_key_coerced
|
|
951
|
+
old_implicit_order_column = NonPrimaryKey.implicit_order_column
|
|
952
|
+
NonPrimaryKey.implicit_order_column = "created_at"
|
|
953
|
+
|
|
954
|
+
c = NonPrimaryKey.connection
|
|
955
|
+
|
|
956
|
+
assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1/i) {
|
|
957
|
+
NonPrimaryKey.last
|
|
958
|
+
}
|
|
959
|
+
ensure
|
|
960
|
+
NonPrimaryKey.implicit_order_column = old_implicit_order_column
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
# SQL Server is unable to use aliased SELECT in the HAVING clause.
|
|
964
|
+
coerce_tests! :test_include_on_unloaded_relation_with_having_referencing_aliased_select
|
|
668
965
|
end
|
|
669
966
|
|
|
670
967
|
module ActiveRecord
|
|
@@ -896,7 +1193,14 @@ class RelationTest < ActiveRecord::TestCase
|
|
|
896
1193
|
coerce_tests! :test_reorder_with_first
|
|
897
1194
|
def test_reorder_with_first_coerced
|
|
898
1195
|
sql_log = capture_sql do
|
|
899
|
-
|
|
1196
|
+
message = <<~MSG.squish
|
|
1197
|
+
`.reorder(nil)` with `.first` / `.first!` no longer
|
|
1198
|
+
takes non-deterministic result in Rails 6.2.
|
|
1199
|
+
To continue taking non-deterministic result, use `.take` / `.take!` instead.
|
|
1200
|
+
MSG
|
|
1201
|
+
assert_deprecated(message) do
|
|
1202
|
+
assert Post.order(:title).reorder(nil).first
|
|
1203
|
+
end
|
|
900
1204
|
end
|
|
901
1205
|
assert sql_log.none? { |sql| /order by [posts].[title]/i.match?(sql) }, "ORDER BY title was used in the query: #{sql_log}"
|
|
902
1206
|
assert sql_log.all? { |sql| /order by \[posts\]\.\[id\]/i.match?(sql) }, "default ORDER BY ID was not used in the query: #{sql_log}"
|
|
@@ -961,6 +1265,16 @@ module ActiveRecord
|
|
|
961
1265
|
query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
|
|
962
1266
|
assert_equal expected, query
|
|
963
1267
|
end
|
|
1268
|
+
|
|
1269
|
+
# Original Rails test fails on Windows CI because the dump file was not being binary read.
|
|
1270
|
+
coerce_tests! :test_marshal_load_legacy_relation
|
|
1271
|
+
def test_marshal_load_legacy_relation_coerced
|
|
1272
|
+
path = File.expand_path(
|
|
1273
|
+
"support/marshal_compatibility_fixtures/legacy_relation.dump",
|
|
1274
|
+
ARTest::SQLServer.root_activerecord_test
|
|
1275
|
+
)
|
|
1276
|
+
assert_equal 11, Marshal.load(File.binread(path)).size
|
|
1277
|
+
end
|
|
964
1278
|
end
|
|
965
1279
|
end
|
|
966
1280
|
|
|
@@ -1207,6 +1521,7 @@ module ActiveRecord
|
|
|
1207
1521
|
|
|
1208
1522
|
original_test_statement_cache_values_differ
|
|
1209
1523
|
ensure
|
|
1524
|
+
Book.where(author_id: nil, name: 'my book').delete_all
|
|
1210
1525
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1211
1526
|
end
|
|
1212
1527
|
end
|
|
@@ -1215,8 +1530,43 @@ end
|
|
|
1215
1530
|
module ActiveRecord
|
|
1216
1531
|
module ConnectionAdapters
|
|
1217
1532
|
class SchemaCacheTest < ActiveRecord::TestCase
|
|
1533
|
+
# Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
|
|
1534
|
+
coerce_tests! :test_yaml_dump_and_load, :test_yaml_dump_and_load_with_gzip if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1535
|
+
|
|
1536
|
+
# Ruby 2.5 and 2.6 have issues to marshal Time before 1900. 2012.sql has one column with default value 1753
|
|
1537
|
+
coerce_tests! :test_marshal_dump_and_load_with_gzip, :test_marshal_dump_and_load_via_disk
|
|
1538
|
+
|
|
1539
|
+
# Tests fail on Windows AppVeyor CI with 'Permission denied' error when renaming file during `File.atomic_write` call.
|
|
1540
|
+
unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1541
|
+
def test_marshal_dump_and_load_with_gzip_coerced
|
|
1542
|
+
with_marshable_time_defaults { original_test_marshal_dump_and_load_with_gzip }
|
|
1543
|
+
end
|
|
1544
|
+
def test_marshal_dump_and_load_via_disk_coerced
|
|
1545
|
+
with_marshable_time_defaults { original_test_marshal_dump_and_load_via_disk }
|
|
1546
|
+
end
|
|
1547
|
+
end
|
|
1548
|
+
|
|
1218
1549
|
private
|
|
1219
1550
|
|
|
1551
|
+
def with_marshable_time_defaults
|
|
1552
|
+
# Detect problems
|
|
1553
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7")
|
|
1554
|
+
column = @connection.columns(:sst_datatypes).find { |c| c.name == "datetime" }
|
|
1555
|
+
current_default = column.default if column.default.is_a?(Time) && column.default.year < 1900
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
# Correct problems
|
|
1559
|
+
if current_default.present?
|
|
1560
|
+
@connection.change_column_default(:sst_datatypes, :datetime, current_default.dup.change(year: 1900))
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
# Run original test
|
|
1564
|
+
yield
|
|
1565
|
+
ensure
|
|
1566
|
+
# Revert changes
|
|
1567
|
+
@connection.change_column_default(:sst_datatypes, :datetime, current_default) if current_default.present?
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1220
1570
|
# We need to give the full path for this to work.
|
|
1221
1571
|
def schema_dump_path
|
|
1222
1572
|
File.join ARTest::SQLServer.root_activerecord, "test/assets/schema_dump_5_1.yml"
|
|
@@ -1233,19 +1583,18 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
|
|
|
1233
1583
|
# Use LEN() vs length() function.
|
|
1234
1584
|
coerce_tests! %r{order: always allows Arel}
|
|
1235
1585
|
test "order: always allows Arel" do
|
|
1236
|
-
|
|
1237
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
|
|
1586
|
+
titles = Post.order(Arel.sql("len(title)")).pluck(:title)
|
|
1238
1587
|
|
|
1239
|
-
|
|
1588
|
+
assert_not_empty titles
|
|
1240
1589
|
end
|
|
1241
1590
|
|
|
1242
1591
|
# Use LEN() vs length() function.
|
|
1243
1592
|
coerce_tests! %r{pluck: always allows Arel}
|
|
1244
1593
|
test "pluck: always allows Arel" do
|
|
1245
|
-
|
|
1246
|
-
|
|
1594
|
+
excepted_values = Post.includes(:comments).pluck(:title).map { |title| [title, title.size] }
|
|
1595
|
+
values = Post.includes(:comments).pluck(:title, Arel.sql("len(title)"))
|
|
1247
1596
|
|
|
1248
|
-
assert_equal
|
|
1597
|
+
assert_equal excepted_values, values
|
|
1249
1598
|
end
|
|
1250
1599
|
|
|
1251
1600
|
# Use LEN() vs length() function.
|
|
@@ -1253,91 +1602,73 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
|
|
|
1253
1602
|
test "order: allows valid Array arguments" do
|
|
1254
1603
|
ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
|
|
1255
1604
|
|
|
1256
|
-
|
|
1257
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "len(title)"]).pluck(:id) }
|
|
1605
|
+
ids = Post.order(["author_id", "len(title)"]).pluck(:id)
|
|
1258
1606
|
|
|
1259
|
-
assert_equal ids_expected,
|
|
1260
|
-
assert_equal ids_expected, ids_disabled
|
|
1607
|
+
assert_equal ids_expected, ids
|
|
1261
1608
|
end
|
|
1262
1609
|
|
|
1263
1610
|
test "order: allows string column names that are quoted" do
|
|
1264
1611
|
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
1265
1612
|
|
|
1266
|
-
|
|
1267
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[id]").pluck(:id) }
|
|
1613
|
+
ids = Post.order("[id]").pluck(:id)
|
|
1268
1614
|
|
|
1269
|
-
assert_equal ids_expected,
|
|
1270
|
-
assert_equal ids_expected, ids_disabled
|
|
1615
|
+
assert_equal ids_expected, ids
|
|
1271
1616
|
end
|
|
1272
1617
|
|
|
1273
1618
|
test "order: allows string column names that are quoted with table" do
|
|
1274
1619
|
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
1275
1620
|
|
|
1276
|
-
|
|
1277
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[posts].[id]").pluck(:id) }
|
|
1621
|
+
ids = Post.order("[posts].[id]").pluck(:id)
|
|
1278
1622
|
|
|
1279
|
-
assert_equal ids_expected,
|
|
1280
|
-
assert_equal ids_expected, ids_disabled
|
|
1623
|
+
assert_equal ids_expected, ids
|
|
1281
1624
|
end
|
|
1282
1625
|
|
|
1283
1626
|
test "order: allows string column names that are quoted with table and user" do
|
|
1284
1627
|
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
1285
1628
|
|
|
1286
|
-
|
|
1287
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[dbo].[posts].[id]").pluck(:id) }
|
|
1629
|
+
ids = Post.order("[dbo].[posts].[id]").pluck(:id)
|
|
1288
1630
|
|
|
1289
|
-
assert_equal ids_expected,
|
|
1290
|
-
assert_equal ids_expected, ids_disabled
|
|
1631
|
+
assert_equal ids_expected, ids
|
|
1291
1632
|
end
|
|
1292
1633
|
|
|
1293
1634
|
test "order: allows string column names that are quoted with table, user and database" do
|
|
1294
1635
|
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
|
1295
1636
|
|
|
1296
|
-
|
|
1297
|
-
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
|
|
1637
|
+
ids = Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id)
|
|
1298
1638
|
|
|
1299
|
-
assert_equal ids_expected,
|
|
1300
|
-
assert_equal ids_expected, ids_disabled
|
|
1639
|
+
assert_equal ids_expected, ids
|
|
1301
1640
|
end
|
|
1302
1641
|
|
|
1303
1642
|
test "pluck: allows string column name that are quoted" do
|
|
1304
1643
|
titles_expected = Post.pluck(Arel.sql("title"))
|
|
1305
1644
|
|
|
1306
|
-
|
|
1307
|
-
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[title]") }
|
|
1645
|
+
titles = Post.pluck("[title]")
|
|
1308
1646
|
|
|
1309
|
-
assert_equal titles_expected,
|
|
1310
|
-
assert_equal titles_expected, titles_disabled
|
|
1647
|
+
assert_equal titles_expected, titles
|
|
1311
1648
|
end
|
|
1312
1649
|
|
|
1313
1650
|
test "pluck: allows string column name that are quoted with table" do
|
|
1314
1651
|
titles_expected = Post.pluck(Arel.sql("title"))
|
|
1315
1652
|
|
|
1316
|
-
|
|
1317
|
-
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[posts].[title]") }
|
|
1653
|
+
titles = Post.pluck("[posts].[title]")
|
|
1318
1654
|
|
|
1319
|
-
assert_equal titles_expected,
|
|
1320
|
-
assert_equal titles_expected, titles_disabled
|
|
1655
|
+
assert_equal titles_expected, titles
|
|
1321
1656
|
end
|
|
1322
1657
|
|
|
1323
1658
|
test "pluck: allows string column name that are quoted with table and user" do
|
|
1324
1659
|
titles_expected = Post.pluck(Arel.sql("title"))
|
|
1325
1660
|
|
|
1326
|
-
|
|
1327
|
-
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[dbo].[posts].[title]") }
|
|
1661
|
+
titles = Post.pluck("[dbo].[posts].[title]")
|
|
1328
1662
|
|
|
1329
|
-
assert_equal titles_expected,
|
|
1330
|
-
assert_equal titles_expected, titles_disabled
|
|
1663
|
+
assert_equal titles_expected, titles
|
|
1331
1664
|
end
|
|
1332
1665
|
|
|
1333
1666
|
test "pluck: allows string column name that are quoted with table, user and database" do
|
|
1334
1667
|
titles_expected = Post.pluck(Arel.sql("title"))
|
|
1335
1668
|
|
|
1336
|
-
|
|
1337
|
-
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
|
|
1669
|
+
titles = Post.pluck("[activerecord_unittest].[dbo].[posts].[title]")
|
|
1338
1670
|
|
|
1339
|
-
assert_equal titles_expected,
|
|
1340
|
-
assert_equal titles_expected, titles_disabled
|
|
1671
|
+
assert_equal titles_expected, titles
|
|
1341
1672
|
end
|
|
1342
1673
|
end
|
|
1343
1674
|
|
|
@@ -1379,6 +1710,25 @@ class RelationMergingTest < ActiveRecord::TestCase
|
|
|
1379
1710
|
relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
|
|
1380
1711
|
assert_equal ["title LIKE N'%suffix'"], relation.order_values
|
|
1381
1712
|
end
|
|
1713
|
+
|
|
1714
|
+
# Same as original but change first regexp to match sp_executesql binding syntax
|
|
1715
|
+
coerce_tests! :test_merge_doesnt_duplicate_same_clauses
|
|
1716
|
+
def test_merge_doesnt_duplicate_same_clauses_coerced
|
|
1717
|
+
david, mary, bob = authors(:david, :mary, :bob)
|
|
1718
|
+
|
|
1719
|
+
non_mary_and_bob = Author.where.not(id: [mary, bob])
|
|
1720
|
+
|
|
1721
|
+
author_id = Author.connection.quote_table_name("authors.id")
|
|
1722
|
+
assert_sql(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)'/) do
|
|
1723
|
+
assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
only_david = Author.where("#{author_id} IN (?)", david)
|
|
1727
|
+
|
|
1728
|
+
assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \(1\)\)\z/) do
|
|
1729
|
+
assert_equal [david], only_david.merge(only_david)
|
|
1730
|
+
end
|
|
1731
|
+
end
|
|
1382
1732
|
end
|
|
1383
1733
|
|
|
1384
1734
|
module ActiveRecord
|
|
@@ -1399,6 +1749,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
|
1399
1749
|
|
|
1400
1750
|
send(:'original_enums are distinct per class')
|
|
1401
1751
|
ensure
|
|
1752
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
1402
1753
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1403
1754
|
end
|
|
1404
1755
|
|
|
@@ -1409,6 +1760,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
|
1409
1760
|
|
|
1410
1761
|
send(:'original_creating new objects with enum scopes')
|
|
1411
1762
|
ensure
|
|
1763
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
1412
1764
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1413
1765
|
end
|
|
1414
1766
|
|
|
@@ -1419,6 +1771,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
|
1419
1771
|
|
|
1420
1772
|
send(:'original_enums are inheritable')
|
|
1421
1773
|
ensure
|
|
1774
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
1422
1775
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1423
1776
|
end
|
|
1424
1777
|
|
|
@@ -1429,6 +1782,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
|
1429
1782
|
|
|
1430
1783
|
send(:'original_declare multiple enums at a time')
|
|
1431
1784
|
ensure
|
|
1785
|
+
Book.where(author_id: nil, name: nil).delete_all
|
|
1432
1786
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
|
1433
1787
|
end
|
|
1434
1788
|
end
|
|
@@ -1466,6 +1820,8 @@ end
|
|
|
1466
1820
|
|
|
1467
1821
|
require "models/citation"
|
|
1468
1822
|
class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
|
1823
|
+
fixtures :citations
|
|
1824
|
+
|
|
1469
1825
|
# Original Rails test fails with SQL Server error message "The query processor ran out of internal resources and
|
|
1470
1826
|
# could not produce a query plan". This error goes away if you change database compatibility level to 110 (SQL 2012)
|
|
1471
1827
|
# (see https://www.mssqltips.com/sqlservertip/5279/sql-server-error-query-processor-ran-out-of-internal-resources-and-could-not-produce-a-query-plan/).
|
|
@@ -1473,14 +1829,14 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
|
|
1473
1829
|
# unprepared statement is used if the number of values exceeds the adapter's `bind_params_length`. The coerced test
|
|
1474
1830
|
# still does this as there will be 32,768 remaining citation records in the database and the `bind_params_length` of
|
|
1475
1831
|
# adapter is 2,098.
|
|
1476
|
-
coerce_tests! :
|
|
1477
|
-
def
|
|
1832
|
+
coerce_tests! :test_eager_loading_too_many_ids
|
|
1833
|
+
def test_eager_loading_too_many_ids_coerced
|
|
1478
1834
|
# Remove excess records.
|
|
1479
1835
|
Citation.limit(32768).order(id: :desc).delete_all
|
|
1480
1836
|
|
|
1481
1837
|
# Perform test
|
|
1482
1838
|
citation_count = Citation.count
|
|
1483
|
-
assert_sql(/WHERE \
|
|
1839
|
+
assert_sql(/WHERE \[citations\]\.\[id\] IN \(0, 1/) do
|
|
1484
1840
|
assert_equal citation_count, Citation.eager_load(:citations).offset(0).size
|
|
1485
1841
|
end
|
|
1486
1842
|
end
|
|
@@ -1492,6 +1848,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
|
|
|
1492
1848
|
def test_vebose_query_logs_coerced
|
|
1493
1849
|
original_test_vebose_query_logs
|
|
1494
1850
|
end
|
|
1851
|
+
|
|
1852
|
+
# Bindings logged slightly differently.
|
|
1853
|
+
coerce_tests! :test_where_in_binds_logging_include_attribute_names
|
|
1854
|
+
def test_where_in_binds_logging_include_attribute_names_coerced
|
|
1855
|
+
Developer.where(id: [1, 2, 3, 4, 5]).load
|
|
1856
|
+
wait
|
|
1857
|
+
assert_match(%{@0 = 1, @1 = 2, @2 = 3, @3 = 4, @4 = 5 [["id", nil], ["id", nil], ["id", nil], ["id", nil], ["id", nil]]}, @logger.logged(:debug).last)
|
|
1858
|
+
end
|
|
1495
1859
|
end
|
|
1496
1860
|
|
|
1497
1861
|
class ActiveRecordSchemaTest < ActiveRecord::TestCase
|
|
@@ -1503,22 +1867,125 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
|
|
|
1503
1867
|
end
|
|
1504
1868
|
end
|
|
1505
1869
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1870
|
+
class ReloadModelsTest < ActiveRecord::TestCase
|
|
1871
|
+
# Skip test on Windows. The number of arguments passed to `IO.popen` in
|
|
1872
|
+
# `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
|
|
1873
|
+
coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
require "models/post"
|
|
1877
|
+
class AnnotateTest < ActiveRecord::TestCase
|
|
1878
|
+
# Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
|
|
1879
|
+
# TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
|
|
1880
|
+
coerce_tests! :test_annotate_wraps_content_in_an_inline_comment
|
|
1881
|
+
def test_annotate_wraps_content_in_an_inline_comment_coerced
|
|
1882
|
+
quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
|
|
1883
|
+
|
|
1884
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
|
|
1885
|
+
posts = Post.select(:id).annotate("foo")
|
|
1886
|
+
assert posts.first
|
|
1887
|
+
end
|
|
1888
|
+
end
|
|
1889
|
+
|
|
1890
|
+
# Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
|
|
1891
|
+
# TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
|
|
1892
|
+
coerce_tests! :test_annotate_is_sanitized
|
|
1893
|
+
def test_annotate_is_sanitized_coerced
|
|
1894
|
+
quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
|
|
1895
|
+
|
|
1896
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
|
|
1897
|
+
posts = Post.select(:id).annotate("*/foo/*")
|
|
1898
|
+
assert posts.first
|
|
1899
|
+
end
|
|
1900
|
+
|
|
1901
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
|
|
1902
|
+
posts = Post.select(:id).annotate("**//foo//**")
|
|
1903
|
+
assert posts.first
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
|
|
1907
|
+
posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
|
|
1908
|
+
assert posts.first
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
|
|
1912
|
+
posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
|
|
1913
|
+
assert posts.first
|
|
1511
1914
|
end
|
|
1512
1915
|
end
|
|
1513
1916
|
end
|
|
1514
1917
|
|
|
1515
|
-
class
|
|
1516
|
-
|
|
1517
|
-
|
|
1918
|
+
class MarshalSerializationTest < ActiveRecord::TestCase
|
|
1919
|
+
private
|
|
1920
|
+
|
|
1921
|
+
def marshal_fixture_path(file_name)
|
|
1922
|
+
File.expand_path(
|
|
1923
|
+
"support/marshal_compatibility_fixtures/#{ActiveRecord::Base.connection.adapter_name}/#{file_name}.dump",
|
|
1924
|
+
ARTest::SQLServer.test_root_sqlserver
|
|
1925
|
+
)
|
|
1926
|
+
end
|
|
1518
1927
|
end
|
|
1519
1928
|
|
|
1520
|
-
class
|
|
1521
|
-
#
|
|
1522
|
-
#
|
|
1523
|
-
|
|
1929
|
+
class NestedThroughAssociationsTest < ActiveRecord::TestCase
|
|
1930
|
+
# Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
|
|
1931
|
+
# "A column has been specified more than once in the order by list"
|
|
1932
|
+
# Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
|
|
1933
|
+
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
|
|
1934
|
+
def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
|
|
1935
|
+
# preload table schemas
|
|
1936
|
+
Author.joins(:category_post_comments).first
|
|
1937
|
+
|
|
1938
|
+
assert_includes_and_joins_equal(
|
|
1939
|
+
Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
|
|
1940
|
+
[authors(:david), authors(:mary)], :category_post_comments
|
|
1941
|
+
)
|
|
1942
|
+
end
|
|
1943
|
+
|
|
1944
|
+
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
|
|
1945
|
+
# preload table schemas
|
|
1946
|
+
Category.joins(:post_comments).first
|
|
1947
|
+
|
|
1948
|
+
assert_includes_and_joins_equal(
|
|
1949
|
+
Category.where("comments.id" => comments(:more_greetings).id).order(:id),
|
|
1950
|
+
[categories(:general), categories(:technology)], :post_comments
|
|
1951
|
+
)
|
|
1952
|
+
end
|
|
1953
|
+
end
|
|
1954
|
+
|
|
1955
|
+
class BasePreventWritesTest < ActiveRecord::TestCase
|
|
1956
|
+
# SQL Server does not have query for release_savepoint
|
|
1957
|
+
coerce_tests! %r{an empty transaction does not raise if preventing writes}
|
|
1958
|
+
test "an empty transaction does not raise if preventing writes coerced" do
|
|
1959
|
+
ActiveRecord::Base.while_preventing_writes do
|
|
1960
|
+
assert_queries(1, ignore_none: true) do
|
|
1961
|
+
Bird.transaction do
|
|
1962
|
+
ActiveRecord::Base.connection.materialize_transactions
|
|
1963
|
+
end
|
|
1964
|
+
end
|
|
1965
|
+
end
|
|
1966
|
+
end
|
|
1967
|
+
|
|
1968
|
+
class BasePreventWritesLegacyTest < ActiveRecord::TestCase
|
|
1969
|
+
# SQL Server does not have query for release_savepoint
|
|
1970
|
+
coerce_tests! %r{an empty transaction does not raise if preventing writes}
|
|
1971
|
+
test "an empty transaction does not raise if preventing writes coerced" do
|
|
1972
|
+
ActiveRecord::Base.connection_handler.while_preventing_writes do
|
|
1973
|
+
assert_queries(1, ignore_none: true) do
|
|
1974
|
+
Bird.transaction do
|
|
1975
|
+
ActiveRecord::Base.connection.materialize_transactions
|
|
1976
|
+
end
|
|
1977
|
+
end
|
|
1978
|
+
end
|
|
1979
|
+
end
|
|
1980
|
+
end
|
|
1981
|
+
end
|
|
1982
|
+
|
|
1983
|
+
class MigratorTest < ActiveRecord::TestCase
|
|
1984
|
+
# Test fails on Windows AppVeyor CI for unknown reason.
|
|
1985
|
+
coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1986
|
+
end
|
|
1987
|
+
|
|
1988
|
+
class MultiDbMigratorTest < ActiveRecord::TestCase
|
|
1989
|
+
# Test fails on Windows AppVeyor CI for unknown reason.
|
|
1990
|
+
coerce_tests! :test_migrator_db_has_no_schema_migrations_table if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
1524
1991
|
end
|