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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +22 -39
  4. data/{Dockerfile → Dockerfile.ci} +0 -0
  5. data/Gemfile +1 -3
  6. data/README.md +5 -8
  7. data/VERSION +1 -1
  8. data/activerecord-jdbcsqlserver-adapter.gemspec +2 -3
  9. data/docker-compose.ci.yml +7 -5
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +25 -29
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +14 -18
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +43 -0
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +26 -0
  14. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +53 -10
  16. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +1 -0
  17. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +5 -13
  18. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +2 -1
  19. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -2
  20. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +43 -27
  21. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
  22. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
  23. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
  24. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -0
  25. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +20 -14
  26. data/lib/active_record/tasks/sqlserver_database_tasks.rb +3 -1
  27. data/lib/activerecord-jdbcsqlserver-adapter.rb +3 -0
  28. data/lib/arel/visitors/sqlserver.rb +1 -1
  29. data/lib/arel_sqlserver.rb +0 -1
  30. data/test/bin/install-freetds.sh +18 -0
  31. data/test/cases/adapter_test_sqlserver.rb +29 -21
  32. data/test/cases/change_column_null_test_sqlserver.rb +42 -0
  33. data/test/cases/coerced_tests.rb +304 -30
  34. data/test/cases/column_test_sqlserver.rb +496 -462
  35. data/test/cases/connection_test_sqlserver.rb +2 -2
  36. data/test/cases/fetch_test_sqlserver.rb +5 -5
  37. data/test/cases/helper_sqlserver.rb +6 -0
  38. data/test/cases/json_test_sqlserver.rb +6 -6
  39. data/test/cases/migration_test_sqlserver.rb +13 -3
  40. data/test/cases/order_test_sqlserver.rb +19 -19
  41. data/test/cases/pessimistic_locking_test_sqlserver.rb +9 -9
  42. data/test/cases/rake_test_sqlserver.rb +20 -20
  43. data/test/cases/schema_dumper_test_sqlserver.rb +34 -33
  44. data/test/cases/schema_test_sqlserver.rb +2 -2
  45. data/test/cases/showplan_test_sqlserver.rb +25 -10
  46. data/test/cases/specific_schema_test_sqlserver.rb +11 -11
  47. data/test/cases/transaction_test_sqlserver.rb +9 -9
  48. data/test/cases/trigger_test_sqlserver.rb +8 -8
  49. data/test/cases/utils_test_sqlserver.rb +36 -36
  50. data/test/cases/uuid_test_sqlserver.rb +8 -8
  51. data/test/migrations/create_clients_and_change_column_null.rb +23 -0
  52. data/test/schema/datatypes/2012.sql +1 -0
  53. data/test/schema/sqlserver_specific_schema.rb +9 -1
  54. data/test/support/core_ext/query_cache.rb +29 -0
  55. metadata +19 -10
  56. 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
@@ -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
- def test_column_names_are_quoted_when_using_from_clause_and_model_has_ignored_columns_coerced
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.quote(quoted_id)}.* FROM developers/, query)
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 BindParameterTest < ActiveRecord::TestCase
147
- # Never finds `sql` since we use `EXEC sp_executesql` wrappers.
148
- coerce_tests! :test_binds_are_logged
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! :test_migration_sets_internal_metadata_even_when_fully_migrated
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\] = 1/) do
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! :test_cache_does_not_wrap_string_results_in_arrays
682
- def test_cache_does_not_wrap_string_results_in_arrays_coerced
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