activerecord-jdbcsqlserver-adapter 51.1.0 → 52.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +22 -39
- data/{Dockerfile → Dockerfile.ci} +0 -0
- data/Gemfile +1 -3
- data/README.md +5 -8
- data/VERSION +1 -1
- data/activerecord-jdbcsqlserver-adapter.gemspec +2 -3
- data/docker-compose.ci.yml +7 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +25 -29
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +14 -18
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +43 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +53 -10
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +5 -13
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +43 -27
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +20 -14
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +3 -1
- data/lib/activerecord-jdbcsqlserver-adapter.rb +3 -0
- data/lib/arel/visitors/sqlserver.rb +1 -1
- data/lib/arel_sqlserver.rb +0 -1
- data/test/bin/install-freetds.sh +18 -0
- data/test/cases/adapter_test_sqlserver.rb +29 -21
- data/test/cases/change_column_null_test_sqlserver.rb +42 -0
- data/test/cases/coerced_tests.rb +304 -30
- data/test/cases/column_test_sqlserver.rb +496 -462
- data/test/cases/connection_test_sqlserver.rb +2 -2
- data/test/cases/fetch_test_sqlserver.rb +5 -5
- data/test/cases/helper_sqlserver.rb +6 -0
- data/test/cases/json_test_sqlserver.rb +6 -6
- data/test/cases/migration_test_sqlserver.rb +13 -3
- data/test/cases/order_test_sqlserver.rb +19 -19
- data/test/cases/pessimistic_locking_test_sqlserver.rb +9 -9
- data/test/cases/rake_test_sqlserver.rb +20 -20
- data/test/cases/schema_dumper_test_sqlserver.rb +34 -33
- data/test/cases/schema_test_sqlserver.rb +2 -2
- data/test/cases/showplan_test_sqlserver.rb +25 -10
- data/test/cases/specific_schema_test_sqlserver.rb +11 -11
- data/test/cases/transaction_test_sqlserver.rb +9 -9
- data/test/cases/trigger_test_sqlserver.rb +8 -8
- data/test/cases/utils_test_sqlserver.rb +36 -36
- data/test/cases/uuid_test_sqlserver.rb +8 -8
- data/test/migrations/create_clients_and_change_column_null.rb +23 -0
- data/test/schema/datatypes/2012.sql +1 -0
- data/test/schema/sqlserver_specific_schema.rb +9 -1
- data/test/support/core_ext/query_cache.rb +29 -0
- metadata +19 -10
- data/BACKERS.md +0 -32
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'cases/helper_sqlserver'
|
2
|
+
require 'migrations/create_clients_and_change_column_null'
|
3
|
+
|
4
|
+
class ChangeColumnNullTestSqlServer < ActiveRecord::TestCase
|
5
|
+
before do
|
6
|
+
@old_verbose = ActiveRecord::Migration.verbose
|
7
|
+
ActiveRecord::Migration.verbose = false
|
8
|
+
CreateClientsAndChangeColumnNull.new.up
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
CreateClientsAndChangeColumnNull.new.down
|
13
|
+
ActiveRecord::Migration.verbose = @old_verbose
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_column(table, name)
|
17
|
+
table.find { |column| column.name == name }
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:clients_table) { connection.columns('clients') }
|
21
|
+
let(:name_column) { find_column(clients_table, 'name') }
|
22
|
+
let(:code_column) { find_column(clients_table, 'code') }
|
23
|
+
let(:value_column) { find_column(clients_table, 'value') }
|
24
|
+
|
25
|
+
describe '#change_column_null' do
|
26
|
+
it 'does not change the column limit' do
|
27
|
+
_(name_column.limit).must_equal 15
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not change the column default' do
|
31
|
+
_(code_column.default).must_equal 'n/a'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not change the column precision' do
|
35
|
+
_(value_column.precision).must_equal 32
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'does not change the column scale' do
|
39
|
+
_(value_column.scale).must_equal 8
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/test/cases/coerced_tests.rb
CHANGED
@@ -33,6 +33,7 @@ module ActiveRecord
|
|
33
33
|
class AdapterTest < ActiveRecord::TestCase
|
34
34
|
# I really dont think we can support legacy binds.
|
35
35
|
coerce_tests! :test_select_all_with_legacy_binds
|
36
|
+
coerce_tests! :test_insert_update_delete_with_legacy_binds
|
36
37
|
|
37
38
|
# As far as I can tell, SQL Server does not support null bytes in strings.
|
38
39
|
coerce_tests! :test_update_prepared_statement
|
@@ -72,7 +73,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|
72
73
|
end
|
73
74
|
|
74
75
|
|
75
|
-
|
76
|
+
class NumericDataTest < ActiveRecord::TestCase
|
77
|
+
# We do not have do the DecimalWithoutScale type.
|
78
|
+
coerce_tests! :test_numeric_fields
|
79
|
+
coerce_tests! :test_numeric_fields_with_scale
|
80
|
+
end
|
76
81
|
|
77
82
|
class BasicsTest < ActiveRecord::TestCase
|
78
83
|
coerce_tests! :test_column_names_are_escaped
|
@@ -81,10 +86,6 @@ class BasicsTest < ActiveRecord::TestCase
|
|
81
86
|
assert_equal '[t]]]', conn.quote_column_name('t]')
|
82
87
|
end
|
83
88
|
|
84
|
-
# We do not have do the DecimalWithoutScale type.
|
85
|
-
coerce_tests! :test_numeric_fields
|
86
|
-
coerce_tests! :test_numeric_fields_with_scale
|
87
|
-
|
88
89
|
# Just like PostgreSQLAdapter does.
|
89
90
|
coerce_tests! :test_respect_internal_encoding
|
90
91
|
|
@@ -114,13 +115,14 @@ class BasicsTest < ActiveRecord::TestCase
|
|
114
115
|
end
|
115
116
|
end
|
116
117
|
|
118
|
+
# Need to escape `quoted_id` once it contains brackets
|
117
119
|
coerce_tests! %r{column names are quoted when using #from clause and model has ignored columns}
|
118
|
-
|
120
|
+
test "column names are quoted when using #from clause and model has ignored columns coerced" do
|
119
121
|
refute_empty Developer.ignored_columns
|
120
122
|
query = Developer.from("developers").to_sql
|
121
123
|
quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}"
|
122
124
|
|
123
|
-
assert_match(/SELECT #{Regexp.
|
125
|
+
assert_match(/SELECT #{Regexp.escape(quoted_id)}.* FROM developers/, query)
|
124
126
|
end
|
125
127
|
end
|
126
128
|
|
@@ -142,16 +144,53 @@ end
|
|
142
144
|
|
143
145
|
|
144
146
|
|
147
|
+
# module ActiveRecord
|
148
|
+
# class BindParameterTest < ActiveRecord::TestCase
|
149
|
+
# # Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
|
150
|
+
# # coerce_tests! :test_binds_are_logged
|
151
|
+
# # def test_binds_are_logged_coerced
|
152
|
+
# # sub = Arel::Nodes::BindParam.new(1)
|
153
|
+
# # binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
|
154
|
+
# # sql = "select * from topics where id = #{sub.to_sql}"
|
155
|
+
# #
|
156
|
+
# # @connection.exec_query(sql, "SQL", binds)
|
157
|
+
# #
|
158
|
+
# # logged_sql = "EXEC sp_executesql N'#{sql}', N'#{sub.to_sql} int', #{sub.to_sql} = 1"
|
159
|
+
# # message = @subscriber.calls.find { |args| args[4][:sql] == logged_sql }
|
160
|
+
# #
|
161
|
+
# # assert_equal binds, message[4][:binds]
|
162
|
+
# # end
|
163
|
+
# #
|
164
|
+
# # # SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
|
165
|
+
# # coerce_tests! :test_statement_cache
|
166
|
+
# # coerce_tests! :test_statement_cache_with_query_cache
|
167
|
+
# # coerce_tests! :test_statement_cache_with_find_by
|
168
|
+
# # coerce_tests! :test_statement_cache_with_in_clause
|
169
|
+
# # coerce_tests! :test_statement_cache_with_sql_string_literal
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
|
173
|
+
|
145
174
|
module ActiveRecord
|
146
|
-
class
|
147
|
-
#
|
148
|
-
coerce_tests! :
|
175
|
+
class InstrumentationTest < ActiveRecord::TestCase
|
176
|
+
# This fails randomly due to schema cache being lost?
|
177
|
+
coerce_tests! :test_payload_name_on_load
|
178
|
+
def test_payload_name_on_load_coerced
|
179
|
+
Book.create(name: "test book")
|
180
|
+
Book.first
|
181
|
+
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
|
182
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
183
|
+
if event.payload[:sql].match "SELECT"
|
184
|
+
assert_equal "Book Load", event.payload[:name]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
Book.first
|
188
|
+
ensure
|
189
|
+
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
|
190
|
+
end
|
149
191
|
end
|
150
192
|
end
|
151
193
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
194
|
class CalculationsTest < ActiveRecord::TestCase
|
156
195
|
# This fails randomly due to schema cache being lost?
|
157
196
|
coerce_tests! :test_offset_is_kept
|
@@ -173,28 +212,36 @@ class CalculationsTest < ActiveRecord::TestCase
|
|
173
212
|
def test_limit_is_kept_coerced
|
174
213
|
queries = capture_sql_ss { Account.limit(1).count }
|
175
214
|
assert_equal 1, queries.length
|
176
|
-
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1}
|
215
|
+
_(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY.*@0 = 1}
|
177
216
|
end unless defined? JRUBY_VERSION
|
178
217
|
|
179
218
|
def test_limit_is_kept_coerced
|
180
219
|
queries = capture_sql_ss { Account.limit(1).count }
|
181
220
|
assert_equal 1, queries.length
|
182
|
-
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT \? ROWS ONLY}
|
221
|
+
_(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT \? ROWS ONLY}
|
183
222
|
end if defined? JRUBY_VERSION
|
184
223
|
|
185
224
|
coerce_tests! :test_limit_with_offset_is_kept
|
186
225
|
def test_limit_with_offset_is_kept_coerced
|
187
226
|
queries = capture_sql_ss { Account.limit(1).offset(1).count }
|
188
227
|
assert_equal 1, queries.length
|
189
|
-
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1}
|
228
|
+
_(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1}
|
190
229
|
end unless defined? JRUBY_VERSION
|
191
230
|
|
192
231
|
def test_limit_with_offset_is_kept_coerced
|
193
232
|
queries = capture_sql_ss { Account.limit(1).offset(1).count }
|
194
233
|
assert_equal 1, queries.length
|
195
|
-
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET \? ROWS FETCH NEXT \? ROWS ONLY}
|
234
|
+
_(queries.first).must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET \? ROWS FETCH NEXT \? ROWS ONLY}
|
196
235
|
end if defined? JRUBY_VERSION
|
197
236
|
|
237
|
+
# SQL Server needs an alias for the calculated column
|
238
|
+
coerce_tests! :test_distinct_count_all_with_custom_select_and_order
|
239
|
+
def test_distinct_count_all_with_custom_select_and_order_coerced
|
240
|
+
accounts = Account.distinct.select("credit_limit % 10 AS the_limit").order(Arel.sql("credit_limit % 10"))
|
241
|
+
assert_queries(1) { assert_equal 3, accounts.count(:all) }
|
242
|
+
assert_queries(1) { assert_equal 3, accounts.load.size }
|
243
|
+
end
|
244
|
+
|
198
245
|
# Leave it up to users to format selects/functions so HAVING works correctly.
|
199
246
|
coerce_tests! :test_having_with_strong_parameters
|
200
247
|
end
|
@@ -208,6 +255,7 @@ module ActiveRecord
|
|
208
255
|
coerce_tests! :test_create_table_with_bigint,
|
209
256
|
:test_create_table_with_defaults
|
210
257
|
end
|
258
|
+
|
211
259
|
class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
|
212
260
|
# In SQL Server you have to delete the tables yourself in the right order.
|
213
261
|
coerce_tests! :test_create_table_with_force_cascade_drops_dependent_objects
|
@@ -249,7 +297,7 @@ module ActiveRecord
|
|
249
297
|
def test_add_column_without_limit_coerced
|
250
298
|
add_column :test_models, :description, :string, limit: nil
|
251
299
|
TestModel.reset_column_information
|
252
|
-
TestModel.columns_hash["description"].limit.must_equal 4000
|
300
|
+
_(TestModel.columns_hash["description"].limit).must_equal 4000
|
253
301
|
end
|
254
302
|
end
|
255
303
|
end
|
@@ -335,7 +383,7 @@ class MigrationTest < ActiveRecord::TestCase
|
|
335
383
|
end
|
336
384
|
|
337
385
|
# For some reason our tests set Rails.@_env which breaks test env switching.
|
338
|
-
coerce_tests! :
|
386
|
+
coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
|
339
387
|
coerce_tests! :test_internal_metadata_stores_environment
|
340
388
|
end
|
341
389
|
|
@@ -361,6 +409,7 @@ module ActiveRecord
|
|
361
409
|
# a value of 'default_env' will still show tests failing. Just ignoring all
|
362
410
|
# of them since we have no monkey in this circus.
|
363
411
|
MergeAndResolveDefaultUrlConfigTest.coerce_all_tests! if defined?(MergeAndResolveDefaultUrlConfigTest)
|
412
|
+
ConnectionHandlerTest.coerce_all_tests! if defined?(ConnectionHandlerTest)
|
364
413
|
end
|
365
414
|
end
|
366
415
|
|
@@ -549,7 +598,7 @@ class InheritanceTest < ActiveRecord::TestCase
|
|
549
598
|
coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
|
550
599
|
def test_eager_load_belongs_to_primary_key_quoting_coerced
|
551
600
|
con = Account.connection
|
552
|
-
assert_sql(/\[companies\]\.\[id\] =
|
601
|
+
assert_sql(/\[companies\]\.\[id\] = \?/) do
|
553
602
|
Account.all.merge!(:includes => :firm).find(1)
|
554
603
|
end
|
555
604
|
end
|
@@ -602,6 +651,7 @@ end
|
|
602
651
|
|
603
652
|
|
604
653
|
|
654
|
+
require 'models/parrot'
|
605
655
|
require 'models/topic'
|
606
656
|
class PersistenceTest < ActiveRecord::TestCase
|
607
657
|
# We can not UPDATE identity columns.
|
@@ -611,14 +661,14 @@ class PersistenceTest < ActiveRecord::TestCase
|
|
611
661
|
coerce_tests! :test_update_all_doesnt_ignore_order
|
612
662
|
def test_update_all_doesnt_ignore_order_coerced
|
613
663
|
david, mary = authors(:david), authors(:mary)
|
614
|
-
david.id.must_equal 1
|
615
|
-
mary.id.must_equal 2
|
616
|
-
david.name.wont_equal mary.name
|
664
|
+
_(david.id).must_equal 1
|
665
|
+
_(mary.id).must_equal 2
|
666
|
+
_(david.name).wont_equal mary.name
|
617
667
|
assert_sql(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
|
618
668
|
Author.where('[id] > 1').order(:id).update_all(name: 'Test')
|
619
669
|
end
|
620
|
-
david.reload.name.must_equal 'David'
|
621
|
-
mary.reload.name.must_equal 'Test'
|
670
|
+
_(david.reload.name).must_equal 'David'
|
671
|
+
_(mary.reload.name).must_equal 'Test'
|
622
672
|
end
|
623
673
|
|
624
674
|
# We can not UPDATE identity columns.
|
@@ -644,6 +694,19 @@ class PersistenceTest < ActiveRecord::TestCase
|
|
644
694
|
# assert_nothing_raised { topic.reload }
|
645
695
|
# assert_equal topic.title, Topic.find(1234).title
|
646
696
|
end
|
697
|
+
|
698
|
+
coerce_tests! :test_delete_new_record
|
699
|
+
def test_delete_new_record_coerced
|
700
|
+
client = Client.new(name: "37signals")
|
701
|
+
client.delete
|
702
|
+
assert_predicate client, :frozen?
|
703
|
+
|
704
|
+
assert_not client.save
|
705
|
+
assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
|
706
|
+
|
707
|
+
assert_predicate client, :frozen?
|
708
|
+
assert_raise(FrozenError) { client.name = "something else" } # For some reason we get a FrozenError instead of a RuntimeError here
|
709
|
+
end
|
647
710
|
end
|
648
711
|
|
649
712
|
|
@@ -678,8 +741,8 @@ end
|
|
678
741
|
|
679
742
|
require 'models/task'
|
680
743
|
class QueryCacheTest < ActiveRecord::TestCase
|
681
|
-
coerce_tests! :
|
682
|
-
def
|
744
|
+
coerce_tests! :test_cache_does_not_wrap_results_in_arrays
|
745
|
+
def test_cache_does_not_wrap_results_in_arrays_coerced
|
683
746
|
Task.cache do
|
684
747
|
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
|
685
748
|
end
|
@@ -694,16 +757,16 @@ class RelationTest < ActiveRecord::TestCase
|
|
694
757
|
# Use LEN vs LENGTH function.
|
695
758
|
coerce_tests! :test_reverse_order_with_function
|
696
759
|
def test_reverse_order_with_function_coerced
|
697
|
-
topics = Topic.order("LEN(title)").reverse_order
|
760
|
+
topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
|
698
761
|
assert_equal topics(:second).title, topics.first.title
|
699
762
|
end
|
700
763
|
|
701
764
|
# Use LEN vs LENGTH function.
|
702
765
|
coerce_tests! :test_reverse_order_with_function_other_predicates
|
703
766
|
def test_reverse_order_with_function_other_predicates_coerced
|
704
|
-
topics = Topic.order("author_name, LEN(title), id").reverse_order
|
767
|
+
topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
|
705
768
|
assert_equal topics(:second).title, topics.first.title
|
706
|
-
topics = Topic.order("LEN(author_name), id, LEN(title)").reverse_order
|
769
|
+
topics = Topic.order(Arel.sql("LEN(author_name), id, LEN(title)")).reverse_order
|
707
770
|
assert_equal topics(:fifth).title, topics.first.title
|
708
771
|
end
|
709
772
|
|
@@ -733,6 +796,22 @@ class RelationTest < ActiveRecord::TestCase
|
|
733
796
|
# so we are skipping all together.
|
734
797
|
coerce_tests! :test_empty_complex_chained_relations
|
735
798
|
|
799
|
+
# Can't apply offset withour ORDER
|
800
|
+
coerce_tests! %r{using a custom table affects the wheres}
|
801
|
+
test 'using a custom table affects the wheres coerced' do
|
802
|
+
post = posts(:welcome)
|
803
|
+
|
804
|
+
assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
|
805
|
+
end
|
806
|
+
|
807
|
+
# Can't apply offset withour ORDER
|
808
|
+
coerce_tests! %r{using a custom table with joins affects the joins}
|
809
|
+
test 'using a custom table with joins affects the joins coerced' do
|
810
|
+
post = posts(:welcome)
|
811
|
+
|
812
|
+
assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
|
813
|
+
end
|
814
|
+
|
736
815
|
# Use LEN() vs length() function.
|
737
816
|
coerce_tests! :test_reverse_arel_assoc_order_with_function
|
738
817
|
def test_reverse_arel_assoc_order_with_function_coerced
|
@@ -741,6 +820,24 @@ class RelationTest < ActiveRecord::TestCase
|
|
741
820
|
end
|
742
821
|
end
|
743
822
|
|
823
|
+
class ActiveRecord::RelationTest < ActiveRecord::TestCase
|
824
|
+
coerce_tests! :test_relation_merging_with_merged_symbol_joins_is_aliased
|
825
|
+
def test_relation_merging_with_merged_symbol_joins_is_aliased__coerced
|
826
|
+
categorizations_with_authors = Categorization.joins(:author)
|
827
|
+
queries = capture_sql { Post.joins(:author, :categorizations).merge(Author.select(:id)).merge(categorizations_with_authors).to_a }
|
828
|
+
|
829
|
+
nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
|
830
|
+
assert_equal 3, nb_inner_join, "Wrong amount of INNER JOIN in query"
|
831
|
+
|
832
|
+
# using `\W` as the column separator
|
833
|
+
query_matches = queries.any? do |sql|
|
834
|
+
%r[INNER\s+JOIN\s+#{Regexp.escape(Author.quoted_table_name)}\s+\Wauthors_categorizations\W]i.match?(sql)
|
835
|
+
end
|
836
|
+
|
837
|
+
assert query_matches, "Should be aliasing the child INNER JOINs in query"
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
744
841
|
|
745
842
|
|
746
843
|
|
@@ -893,10 +990,55 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
|
|
893
990
|
end
|
894
991
|
end
|
895
992
|
end
|
993
|
+
|
994
|
+
unless defined? JRUBY_VERSION
|
995
|
+
# datetime is rounded to increments of .000, .003, or .007 seconds
|
996
|
+
coerce_tests! :test_datetime_precision_is_truncated_on_assignment
|
997
|
+
def test_datetime_precision_is_truncated_on_assignment_coerced
|
998
|
+
@connection.create_table(:foos, force: true)
|
999
|
+
@connection.add_column :foos, :created_at, :datetime, precision: 0
|
1000
|
+
@connection.add_column :foos, :updated_at, :datetime, precision: 6
|
1001
|
+
|
1002
|
+
time = ::Time.now.change(nsec: 123456789)
|
1003
|
+
foo = Foo.new(created_at: time, updated_at: time)
|
1004
|
+
|
1005
|
+
assert_equal 0, foo.created_at.nsec
|
1006
|
+
assert_equal 123457000, foo.updated_at.nsec
|
1007
|
+
|
1008
|
+
foo.save!
|
1009
|
+
foo.reload
|
1010
|
+
|
1011
|
+
assert_equal 0, foo.created_at.nsec
|
1012
|
+
assert_equal 123457000, foo.updated_at.nsec
|
1013
|
+
end
|
1014
|
+
end
|
896
1015
|
end
|
897
1016
|
|
898
1017
|
|
899
1018
|
|
1019
|
+
class TimePrecisionTest < ActiveRecord::TestCase
|
1020
|
+
# datetime is rounded to increments of .000, .003, or .007 seconds
|
1021
|
+
coerce_tests! :test_time_precision_is_truncated_on_assignment
|
1022
|
+
def test_time_precision_is_truncated_on_assignment_coerced
|
1023
|
+
@connection.create_table(:foos, force: true)
|
1024
|
+
@connection.add_column :foos, :start, :time, precision: 0
|
1025
|
+
@connection.add_column :foos, :finish, :time, precision: 6
|
1026
|
+
|
1027
|
+
time = ::Time.now.change(nsec: 123456789)
|
1028
|
+
foo = Foo.new(start: time, finish: time)
|
1029
|
+
|
1030
|
+
assert_equal 0, foo.start.nsec
|
1031
|
+
assert_equal 123457000, foo.finish.nsec
|
1032
|
+
|
1033
|
+
foo.save!
|
1034
|
+
foo.reload
|
1035
|
+
|
1036
|
+
assert_equal 0, foo.start.nsec
|
1037
|
+
assert_equal 123457000, foo.finish.nsec
|
1038
|
+
end
|
1039
|
+
end unless defined? JRUBY_VERSION
|
1040
|
+
|
1041
|
+
|
900
1042
|
|
901
1043
|
class DefaultNumbersTest < ActiveRecord::TestCase
|
902
1044
|
# We do better with native types and do not return strings for everything.
|
@@ -949,3 +1091,135 @@ module ActiveRecord
|
|
949
1091
|
end
|
950
1092
|
end
|
951
1093
|
|
1094
|
+
class UnsafeRawSqlTest < ActiveRecord::TestCase
|
1095
|
+
coerce_tests! %r{always allows Arel}
|
1096
|
+
test 'order: always allows Arel' do
|
1097
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
|
1098
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
|
1099
|
+
|
1100
|
+
assert_equal ids_depr, ids_disabled
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
test "pluck: always allows Arel" do
|
1104
|
+
values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
|
1105
|
+
values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
|
1106
|
+
|
1107
|
+
assert_equal values_depr, values_disabled
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
|
1111
|
+
coerce_tests! %r{order: disallows invalid Array arguments}
|
1112
|
+
test "order: disallows invalid Array arguments" do
|
1113
|
+
with_unsafe_raw_sql_disabled do
|
1114
|
+
assert_raises(ActiveRecord::UnknownAttributeReference) do
|
1115
|
+
Post.order(["author_id", "len(title)"]).pluck(:id)
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
coerce_tests! %r{order: allows valid Array arguments}
|
1121
|
+
test "order: allows valid Array arguments" do
|
1122
|
+
ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
|
1123
|
+
|
1124
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
|
1125
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
|
1126
|
+
|
1127
|
+
assert_equal ids_expected, ids_depr
|
1128
|
+
assert_equal ids_expected, ids_disabled
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
coerce_tests! %r{order: logs deprecation warning for unrecognized column}
|
1132
|
+
test "order: logs deprecation warning for unrecognized column" do
|
1133
|
+
with_unsafe_raw_sql_deprecated do
|
1134
|
+
assert_deprecated(/Dangerous query method/) do
|
1135
|
+
Post.order("len(title)")
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
coerce_tests! %r{pluck: disallows invalid column name}
|
1141
|
+
test "pluck: disallows invalid column name" do
|
1142
|
+
with_unsafe_raw_sql_disabled do
|
1143
|
+
assert_raises(ActiveRecord::UnknownAttributeReference) do
|
1144
|
+
Post.pluck("len(title)")
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
coerce_tests! %r{pluck: disallows invalid column name amongst valid names}
|
1150
|
+
test "pluck: disallows invalid column name amongst valid names" do
|
1151
|
+
with_unsafe_raw_sql_disabled do
|
1152
|
+
assert_raises(ActiveRecord::UnknownAttributeReference) do
|
1153
|
+
Post.pluck(:title, "len(title)")
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
coerce_tests! %r{pluck: disallows invalid column names with includes}
|
1159
|
+
test "pluck: disallows invalid column names with includes" do
|
1160
|
+
with_unsafe_raw_sql_disabled do
|
1161
|
+
assert_raises(ActiveRecord::UnknownAttributeReference) do
|
1162
|
+
Post.includes(:comments).pluck(:title, "len(title)")
|
1163
|
+
end
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
coerce_tests! %r{pluck: logs deprecation warning}
|
1168
|
+
test "pluck: logs deprecation warning" do
|
1169
|
+
with_unsafe_raw_sql_deprecated do
|
1170
|
+
assert_deprecated(/Dangerous query method/) do
|
1171
|
+
Post.includes(:comments).pluck(:title, "len(title)")
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
|
1178
|
+
class ReservedWordTest < ActiveRecord::TestCase
|
1179
|
+
coerce_tests! :test_change_columns
|
1180
|
+
def test_change_columns_coerced
|
1181
|
+
assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
|
1182
|
+
assert_nothing_raised { @connection.change_column("group", "order", :text) }
|
1183
|
+
assert_nothing_raised { @connection.change_column_null("group", "order", true) }
|
1184
|
+
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
|
1185
|
+
end
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
|
1189
|
+
|
1190
|
+
class OptimisticLockingTest < ActiveRecord::TestCase
|
1191
|
+
# We do not allow updating identities, but we can test using a non-identity key
|
1192
|
+
coerce_tests! :test_update_with_dirty_primary_key
|
1193
|
+
def test_update_with_dirty_primary_key_coerced
|
1194
|
+
assert_raises(ActiveRecord::RecordNotUnique) do
|
1195
|
+
record = StringKeyObject.find('record1')
|
1196
|
+
record.id = 'record2'
|
1197
|
+
record.save!
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
record = StringKeyObject.find('record1')
|
1201
|
+
record.id = 'record42'
|
1202
|
+
record.save!
|
1203
|
+
|
1204
|
+
assert StringKeyObject.find('record42')
|
1205
|
+
assert_raises(ActiveRecord::RecordNotFound) do
|
1206
|
+
StringKeyObject.find('record1')
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
|
1212
|
+
|
1213
|
+
class RelationMergingTest < ActiveRecord::TestCase
|
1214
|
+
coerce_tests! :test_merging_with_order_with_binds
|
1215
|
+
def test_merging_with_order_with_binds_coerced
|
1216
|
+
relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
|
1217
|
+
assert_equal ["title LIKE N'%suffix'"], relation.order_values
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
|
1222
|
+
class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
1223
|
+
# Temporarily coerce this test due to https://github.com/rails/rails/issues/34945
|
1224
|
+
coerce_tests! :test_eager_loading_too_may_ids
|
1225
|
+
end
|