activerecord-jdbcsqlserver-adapter 51.1.0 → 52.0.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.
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