activerecord-sqlserver-adapter 5.1.6 → 5.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +16 -19
  4. data/CHANGELOG.md +6 -46
  5. data/Dockerfile +14 -0
  6. data/Gemfile +1 -2
  7. data/README.md +4 -4
  8. data/VERSION +1 -1
  9. data/activerecord-sqlserver-adapter.gemspec +1 -1
  10. data/circle.yml +8 -6
  11. data/docker-compose.ci.yml +11 -0
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +11 -18
  13. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
  14. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +45 -9
  15. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +2 -1
  16. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -2
  17. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +33 -14
  18. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
  19. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
  20. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
  21. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +14 -12
  22. data/lib/active_record/tasks/sqlserver_database_tasks.rb +3 -1
  23. data/lib/arel/visitors/sqlserver.rb +1 -1
  24. data/lib/arel_sqlserver.rb +0 -1
  25. data/test/bin/install-freetds.sh +1 -1
  26. data/test/cases/adapter_test_sqlserver.rb +10 -2
  27. data/test/cases/coerced_tests.rb +262 -13
  28. data/test/cases/column_test_sqlserver.rb +6 -0
  29. data/test/cases/helper_sqlserver.rb +2 -1
  30. data/test/cases/migration_test_sqlserver.rb +3 -1
  31. data/test/cases/order_test_sqlserver.rb +19 -19
  32. data/test/schema/sqlserver_specific_schema.rb +9 -1
  33. data/test/support/core_ext/query_cache.rb +29 -0
  34. metadata +11 -9
  35. data/test/bin/.keep +0 -0
@@ -26,13 +26,13 @@ module ActiveRecord
26
26
 
27
27
  end
28
28
 
29
- Transaction.send :include, SQLServerTransaction
29
+ Transaction.send :prepend, SQLServerTransaction
30
30
 
31
31
  module SQLServerRealTransaction
32
32
 
33
33
  attr_reader :starting_isolation_level
34
34
 
35
- def initialize(connection, options, run_commit_callbacks: false)
35
+ def initialize(connection, options, *args)
36
36
  @connection = connection
37
37
  @starting_isolation_level = current_isolation_level if options[:isolation]
38
38
  super
@@ -58,7 +58,6 @@ module ActiveRecord
58
58
 
59
59
  end
60
60
 
61
- RealTransaction.send :include, SQLServerRealTransaction
62
-
61
+ RealTransaction.send :prepend, SQLServerRealTransaction
63
62
  end
64
63
  end
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module SQLServer
4
4
  module Type
5
- class Json < ActiveRecord::Type::Internal::AbstractJson
5
+ class Json < ActiveRecord::Type::Json
6
6
 
7
7
  end
8
8
  end
@@ -4,6 +4,13 @@ module ActiveRecord
4
4
  module Type
5
5
  class String < ActiveRecord::Type::String
6
6
 
7
+ def changed_in_place?(raw_old_value, new_value)
8
+ if raw_old_value.is_a?(Data)
9
+ raw_old_value.value != new_value
10
+ else
11
+ super
12
+ end
13
+ end
7
14
 
8
15
  end
9
16
  end
@@ -34,7 +34,6 @@ module ActiveRecord
34
34
  SQLServer::Quoting,
35
35
  SQLServer::DatabaseStatements,
36
36
  SQLServer::Showplan,
37
- SQLServer::SchemaDumper,
38
37
  SQLServer::SchemaStatements,
39
38
  SQLServer::DatabaseLimits,
40
39
  SQLServer::DatabaseTasks
@@ -136,6 +135,10 @@ module ActiveRecord
136
135
  false
137
136
  end
138
137
 
138
+ def supports_savepoints?
139
+ true
140
+ end
141
+
139
142
  def supports_in_memory_oltp?
140
143
  @version_year >= 2014
141
144
  end
@@ -189,7 +192,7 @@ module ActiveRecord
189
192
 
190
193
  def tables_with_referential_integrity
191
194
  schemas_and_tables = select_rows <<-SQL.strip_heredoc
192
- SELECT s.name, o.name
195
+ SELECT DISTINCT s.name, o.name
193
196
  FROM sys.foreign_keys i
194
197
  INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
195
198
  INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
@@ -212,7 +215,7 @@ module ActiveRecord
212
215
  end
213
216
 
214
217
  def sqlserver_azure?
215
- @sqlserver_azure ||= !!(select_value('SELECT @@version', 'SCHEMA') =~ /Azure/i)
218
+ !!(sqlserver_version =~ /Azure/i)
216
219
  end
217
220
 
218
221
  def database_prefix_remote_server?
@@ -253,7 +256,7 @@ module ActiveRecord
253
256
 
254
257
  # === Abstract Adapter (Misc Support) =========================== #
255
258
 
256
- def initialize_type_map(m)
259
+ def initialize_type_map(m = type_map)
257
260
  m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
258
261
  # Exact Numerics
259
262
  register_class_with_limit m, 'bigint(8)', SQLServer::Type::BigInteger
@@ -435,16 +438,15 @@ module ActiveRecord
435
438
  end
436
439
 
437
440
  def version_year
438
- return @version_year if defined?(@version_year)
439
- @version_year = begin
440
- vstring = _raw_select('SELECT @@version', fetch: :rows).first.first.to_s
441
- return 2016 if vstring =~ /vNext/
442
- /SQL Server (\d+)/.match(vstring).to_a.last.to_s.to_i
443
- rescue Exception => e
444
- 2016
445
- end
441
+ return 2016 if sqlserver_version =~ /vNext/
442
+ /SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
443
+ rescue StandardError => e
444
+ 2016
446
445
  end
447
446
 
447
+ def sqlserver_version
448
+ @sqlserver_version ||= _raw_select('SELECT @@version', fetch: :rows).first.first.to_s
449
+ end
448
450
  end
449
451
  end
450
452
  end
@@ -49,9 +49,11 @@ module ActiveRecord
49
49
  end
50
50
 
51
51
  def structure_dump(filename, extra_flags)
52
+ server_arg = "-S #{Shellwords.escape(configuration['host'])}"
53
+ server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration['port']
52
54
  command = [
53
55
  "defncopy-ttds",
54
- "-S #{Shellwords.escape(configuration['host'])}",
56
+ server_arg,
55
57
  "-D #{Shellwords.escape(configuration['database'])}",
56
58
  "-U #{Shellwords.escape(configuration['username'])}",
57
59
  "-P #{Shellwords.escape(configuration['password'])}",
@@ -14,7 +14,7 @@ module Arel
14
14
  # SQLServer ToSql/Visitor (Overides)
15
15
 
16
16
  def visit_Arel_Nodes_BindParam o, collector
17
- collector.add_bind(o) { |i| "@#{i-1}" }
17
+ collector.add_bind(o.value) { |i| "@#{i-1}" }
18
18
  end
19
19
 
20
20
  def visit_Arel_Nodes_Bin o, collector
@@ -1,3 +1,2 @@
1
1
  require 'arel'
2
- require 'arel/visitors/bind_visitor'
3
2
  require 'arel/visitors/sqlserver'
@@ -5,7 +5,7 @@ set -e
5
5
 
6
6
  FREETDS_VERSION=1.00.21
7
7
 
8
- wget ftp://ftp.freetds.org/pub/freetds/stable/freetds-$FREETDS_VERSION.tar.gz
8
+ wget http://www.freetds.org/files/stable/freetds-$FREETDS_VERSION.tar.gz
9
9
  tar -xzf freetds-$FREETDS_VERSION.tar.gz
10
10
  cd freetds-$FREETDS_VERSION
11
11
  ./configure --prefix=/opt/local \
@@ -231,6 +231,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
231
231
  end
232
232
  end
233
233
 
234
+ it 'not disable referential integrity for the same table twice' do
235
+ tables = SSTestHasPk.connection.tables_with_referential_integrity
236
+ assert_equal tables.size, tables.uniq.size
237
+ end
238
+
234
239
  end
235
240
 
236
241
  describe 'database statements' do
@@ -268,9 +273,12 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
268
273
  assert_equal 'integer', connection.type_to_sql(:integer, limit: 3)
269
274
  end
270
275
 
271
- it 'create smallints when limit is less than 3' do
276
+ it 'create smallints when limit is 2' do
272
277
  assert_equal 'smallint', connection.type_to_sql(:integer, limit: 2)
273
- assert_equal 'smallint', connection.type_to_sql(:integer, limit: 1)
278
+ end
279
+
280
+ it 'create tinyints when limit is 1' do
281
+ assert_equal 'tinyint', connection.type_to_sql(:integer, limit: 1)
274
282
  end
275
283
 
276
284
  it 'create bigints when limit is greateer than 4' do
@@ -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
@@ -71,7 +72,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
71
72
  end
72
73
 
73
74
 
74
-
75
+ class NumericDataTest < ActiveRecord::TestCase
76
+ # We do not have do the DecimalWithoutScale type.
77
+ coerce_tests! :test_numeric_fields
78
+ coerce_tests! :test_numeric_fields_with_scale
79
+ end
75
80
 
76
81
  class BasicsTest < ActiveRecord::TestCase
77
82
  coerce_tests! :test_column_names_are_escaped
@@ -80,10 +85,6 @@ class BasicsTest < ActiveRecord::TestCase
80
85
  assert_equal '[t]]]', conn.quote_column_name('t]')
81
86
  end
82
87
 
83
- # We do not have do the DecimalWithoutScale type.
84
- coerce_tests! :test_numeric_fields
85
- coerce_tests! :test_numeric_fields_with_scale
86
-
87
88
  # Just like PostgreSQLAdapter does.
88
89
  coerce_tests! :test_respect_internal_encoding
89
90
 
@@ -112,6 +113,16 @@ class BasicsTest < ActiveRecord::TestCase
112
113
  end
113
114
  end
114
115
  end
116
+
117
+ # Need to escape `quoted_id` once it contains brackets
118
+ coerce_tests! %r{column names are quoted when using #from clause and model has ignored columns}
119
+ test "column names are quoted when using #from clause and model has ignored columns coerced" do
120
+ refute_empty Developer.ignored_columns
121
+ query = Developer.from("developers").to_sql
122
+ quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}"
123
+
124
+ assert_match(/SELECT #{Regexp.escape(quoted_id)}.* FROM developers/, query)
125
+ end
115
126
  end
116
127
 
117
128
 
@@ -140,7 +151,25 @@ module ActiveRecord
140
151
  end
141
152
 
142
153
 
143
-
154
+ module ActiveRecord
155
+ class InstrumentationTest < ActiveRecord::TestCase
156
+ # This fails randomly due to schema cache being lost?
157
+ coerce_tests! :test_payload_name_on_load
158
+ def test_payload_name_on_load_coerced
159
+ Book.create(name: "test book")
160
+ Book.first
161
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
162
+ event = ActiveSupport::Notifications::Event.new(*args)
163
+ if event.payload[:sql].match "SELECT"
164
+ assert_equal "Book Load", event.payload[:name]
165
+ end
166
+ end
167
+ Book.first
168
+ ensure
169
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
170
+ end
171
+ end
172
+ end
144
173
 
145
174
  class CalculationsTest < ActiveRecord::TestCase
146
175
  # This fails randomly due to schema cache being lost?
@@ -173,6 +202,14 @@ class CalculationsTest < ActiveRecord::TestCase
173
202
  queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY.*@0 = 1, @1 = 1}
174
203
  end
175
204
 
205
+ # SQL Server needs an alias for the calculated column
206
+ coerce_tests! :test_distinct_count_all_with_custom_select_and_order
207
+ def test_distinct_count_all_with_custom_select_and_order_coerced
208
+ accounts = Account.distinct.select("credit_limit % 10 AS the_limit").order(Arel.sql("credit_limit % 10"))
209
+ assert_queries(1) { assert_equal 3, accounts.count(:all) }
210
+ assert_queries(1) { assert_equal 3, accounts.load.size }
211
+ end
212
+
176
213
  # Leave it up to users to format selects/functions so HAVING works correctly.
177
214
  coerce_tests! :test_having_with_strong_parameters
178
215
  end
@@ -186,6 +223,7 @@ module ActiveRecord
186
223
  coerce_tests! :test_create_table_with_bigint,
187
224
  :test_create_table_with_defaults
188
225
  end
226
+
189
227
  class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
190
228
  # In SQL Server you have to delete the tables yourself in the right order.
191
229
  coerce_tests! :test_create_table_with_force_cascade_drops_dependent_objects
@@ -313,7 +351,7 @@ class MigrationTest < ActiveRecord::TestCase
313
351
  end
314
352
 
315
353
  # For some reason our tests set Rails.@_env which breaks test env switching.
316
- coerce_tests! :test_migration_sets_internal_metadata_even_when_fully_migrated
354
+ coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
317
355
  coerce_tests! :test_internal_metadata_stores_environment
318
356
  end
319
357
 
@@ -339,6 +377,7 @@ module ActiveRecord
339
377
  # a value of 'default_env' will still show tests failing. Just ignoring all
340
378
  # of them since we have no monkey in this circus.
341
379
  MergeAndResolveDefaultUrlConfigTest.coerce_all_tests! if defined?(MergeAndResolveDefaultUrlConfigTest)
380
+ ConnectionHandlerTest.coerce_all_tests! if defined?(ConnectionHandlerTest)
342
381
  end
343
382
  end
344
383
 
@@ -513,7 +552,7 @@ class InheritanceTest < ActiveRecord::TestCase
513
552
  coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
514
553
  def test_eager_load_belongs_to_primary_key_quoting_coerced
515
554
  con = Account.connection
516
- assert_sql(/\[companies\]\.\[id\] = 1/) do
555
+ assert_sql(/\[companies\]\.\[id\] = @0.* @0 = 1/) do
517
556
  Account.all.merge!(:includes => :firm).find(1)
518
557
  end
519
558
  end
@@ -566,6 +605,7 @@ end
566
605
 
567
606
 
568
607
 
608
+ require 'models/parrot'
569
609
  require 'models/topic'
570
610
  class PersistenceTest < ActiveRecord::TestCase
571
611
  # We can not UPDATE identity columns.
@@ -642,8 +682,8 @@ end
642
682
 
643
683
  require 'models/task'
644
684
  class QueryCacheTest < ActiveRecord::TestCase
645
- coerce_tests! :test_cache_does_not_wrap_string_results_in_arrays
646
- def test_cache_does_not_wrap_string_results_in_arrays_coerced
685
+ coerce_tests! :test_cache_does_not_wrap_results_in_arrays
686
+ def test_cache_does_not_wrap_results_in_arrays_coerced
647
687
  Task.cache do
648
688
  assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
649
689
  end
@@ -658,16 +698,16 @@ class RelationTest < ActiveRecord::TestCase
658
698
  # Use LEN vs LENGTH function.
659
699
  coerce_tests! :test_reverse_order_with_function
660
700
  def test_reverse_order_with_function_coerced
661
- topics = Topic.order("LEN(title)").reverse_order
701
+ topics = Topic.order(Arel.sql("LEN(title)")).reverse_order
662
702
  assert_equal topics(:second).title, topics.first.title
663
703
  end
664
704
 
665
705
  # Use LEN vs LENGTH function.
666
706
  coerce_tests! :test_reverse_order_with_function_other_predicates
667
707
  def test_reverse_order_with_function_other_predicates_coerced
668
- topics = Topic.order("author_name, LEN(title), id").reverse_order
708
+ topics = Topic.order(Arel.sql("author_name, LEN(title), id")).reverse_order
669
709
  assert_equal topics(:second).title, topics.first.title
670
- topics = Topic.order("LEN(author_name), id, LEN(title)").reverse_order
710
+ topics = Topic.order(Arel.sql("LEN(author_name), id, LEN(title)")).reverse_order
671
711
  assert_equal topics(:fifth).title, topics.first.title
672
712
  end
673
713
 
@@ -697,6 +737,22 @@ class RelationTest < ActiveRecord::TestCase
697
737
  # so we are skipping all together.
698
738
  coerce_tests! :test_empty_complex_chained_relations
699
739
 
740
+ # Can't apply offset withour ORDER
741
+ coerce_tests! %r{using a custom table affects the wheres}
742
+ test 'using a custom table affects the wheres coerced' do
743
+ post = posts(:welcome)
744
+
745
+ assert_equal post, custom_post_relation.where!(title: post.title).order(:id).take
746
+ end
747
+
748
+ # Can't apply offset withour ORDER
749
+ coerce_tests! %r{using a custom table with joins affects the joins}
750
+ test 'using a custom table with joins affects the joins coerced' do
751
+ post = posts(:welcome)
752
+
753
+ assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).order(:id).take
754
+ end
755
+
700
756
  # Use LEN() vs length() function.
701
757
  coerce_tests! :test_reverse_arel_assoc_order_with_function
702
758
  def test_reverse_arel_assoc_order_with_function_coerced
@@ -705,6 +761,24 @@ class RelationTest < ActiveRecord::TestCase
705
761
  end
706
762
  end
707
763
 
764
+ class ActiveRecord::RelationTest < ActiveRecord::TestCase
765
+ coerce_tests! :test_relation_merging_with_merged_symbol_joins_is_aliased
766
+ def test_relation_merging_with_merged_symbol_joins_is_aliased__coerced
767
+ categorizations_with_authors = Categorization.joins(:author)
768
+ queries = capture_sql { Post.joins(:author, :categorizations).merge(Author.select(:id)).merge(categorizations_with_authors).to_a }
769
+
770
+ nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
771
+ assert_equal 3, nb_inner_join, "Wrong amount of INNER JOIN in query"
772
+
773
+ # using `\W` as the column separator
774
+ query_matches = queries.any? do |sql|
775
+ %r[INNER\s+JOIN\s+#{Regexp.escape(Author.quoted_table_name)}\s+\Wauthors_categorizations\W]i.match?(sql)
776
+ end
777
+
778
+ assert query_matches, "Should be aliasing the child INNER JOINs in query"
779
+ end
780
+ end
781
+
708
782
 
709
783
 
710
784
 
@@ -857,10 +931,53 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
857
931
  end
858
932
  end
859
933
  end
934
+
935
+ # datetime is rounded to increments of .000, .003, or .007 seconds
936
+ coerce_tests! :test_datetime_precision_is_truncated_on_assignment
937
+ def test_datetime_precision_is_truncated_on_assignment_coerced
938
+ @connection.create_table(:foos, force: true)
939
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
940
+ @connection.add_column :foos, :updated_at, :datetime, precision: 6
941
+
942
+ time = ::Time.now.change(nsec: 123456789)
943
+ foo = Foo.new(created_at: time, updated_at: time)
944
+
945
+ assert_equal 0, foo.created_at.nsec
946
+ assert_equal 123457000, foo.updated_at.nsec
947
+
948
+ foo.save!
949
+ foo.reload
950
+
951
+ assert_equal 0, foo.created_at.nsec
952
+ assert_equal 123457000, foo.updated_at.nsec
953
+ end
860
954
  end
861
955
 
862
956
 
863
957
 
958
+ class TimePrecisionTest < ActiveRecord::TestCase
959
+ # datetime is rounded to increments of .000, .003, or .007 seconds
960
+ coerce_tests! :test_time_precision_is_truncated_on_assignment
961
+ def test_time_precision_is_truncated_on_assignment_coerced
962
+ @connection.create_table(:foos, force: true)
963
+ @connection.add_column :foos, :start, :time, precision: 0
964
+ @connection.add_column :foos, :finish, :time, precision: 6
965
+
966
+ time = ::Time.now.change(nsec: 123456789)
967
+ foo = Foo.new(start: time, finish: time)
968
+
969
+ assert_equal 0, foo.start.nsec
970
+ assert_equal 123457000, foo.finish.nsec
971
+
972
+ foo.save!
973
+ foo.reload
974
+
975
+ assert_equal 0, foo.start.nsec
976
+ assert_equal 123457000, foo.finish.nsec
977
+ end
978
+ end
979
+
980
+
864
981
 
865
982
  class DefaultNumbersTest < ActiveRecord::TestCase
866
983
  # We do better with native types and do not return strings for everything.
@@ -913,3 +1030,135 @@ module ActiveRecord
913
1030
  end
914
1031
  end
915
1032
 
1033
+ class UnsafeRawSqlTest < ActiveRecord::TestCase
1034
+ coerce_tests! %r{always allows Arel}
1035
+ test 'order: always allows Arel' do
1036
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("len(title)")).pluck(:title) }
1037
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("len(title)")).pluck(:title) }
1038
+
1039
+ assert_equal ids_depr, ids_disabled
1040
+ end
1041
+
1042
+ test "pluck: always allows Arel" do
1043
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1044
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("len(title)")) }
1045
+
1046
+ assert_equal values_depr, values_disabled
1047
+ end
1048
+
1049
+
1050
+ coerce_tests! %r{order: disallows invalid Array arguments}
1051
+ test "order: disallows invalid Array arguments" do
1052
+ with_unsafe_raw_sql_disabled do
1053
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
1054
+ Post.order(["author_id", "len(title)"]).pluck(:id)
1055
+ end
1056
+ end
1057
+ end
1058
+
1059
+ coerce_tests! %r{order: allows valid Array arguments}
1060
+ test "order: allows valid Array arguments" do
1061
+ ids_expected = Post.order(Arel.sql("author_id, len(title)")).pluck(:id)
1062
+
1063
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
1064
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("len(title)")]).pluck(:id) }
1065
+
1066
+ assert_equal ids_expected, ids_depr
1067
+ assert_equal ids_expected, ids_disabled
1068
+ end
1069
+
1070
+ coerce_tests! %r{order: logs deprecation warning for unrecognized column}
1071
+ test "order: logs deprecation warning for unrecognized column" do
1072
+ with_unsafe_raw_sql_deprecated do
1073
+ assert_deprecated(/Dangerous query method/) do
1074
+ Post.order("len(title)")
1075
+ end
1076
+ end
1077
+ end
1078
+
1079
+ coerce_tests! %r{pluck: disallows invalid column name}
1080
+ test "pluck: disallows invalid column name" do
1081
+ with_unsafe_raw_sql_disabled do
1082
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
1083
+ Post.pluck("len(title)")
1084
+ end
1085
+ end
1086
+ end
1087
+
1088
+ coerce_tests! %r{pluck: disallows invalid column name amongst valid names}
1089
+ test "pluck: disallows invalid column name amongst valid names" do
1090
+ with_unsafe_raw_sql_disabled do
1091
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
1092
+ Post.pluck(:title, "len(title)")
1093
+ end
1094
+ end
1095
+ end
1096
+
1097
+ coerce_tests! %r{pluck: disallows invalid column names with includes}
1098
+ test "pluck: disallows invalid column names with includes" do
1099
+ with_unsafe_raw_sql_disabled do
1100
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
1101
+ Post.includes(:comments).pluck(:title, "len(title)")
1102
+ end
1103
+ end
1104
+ end
1105
+
1106
+ coerce_tests! %r{pluck: logs deprecation warning}
1107
+ test "pluck: logs deprecation warning" do
1108
+ with_unsafe_raw_sql_deprecated do
1109
+ assert_deprecated(/Dangerous query method/) do
1110
+ Post.includes(:comments).pluck(:title, "len(title)")
1111
+ end
1112
+ end
1113
+ end
1114
+ end
1115
+
1116
+
1117
+ class ReservedWordTest < ActiveRecord::TestCase
1118
+ coerce_tests! :test_change_columns
1119
+ def test_change_columns_coerced
1120
+ assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
1121
+ assert_nothing_raised { @connection.change_column("group", "order", :text) }
1122
+ assert_nothing_raised { @connection.change_column_null("group", "order", true) }
1123
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
1124
+ end
1125
+ end
1126
+
1127
+
1128
+
1129
+ class OptimisticLockingTest < ActiveRecord::TestCase
1130
+ # We do not allow updating identities, but we can test using a non-identity key
1131
+ coerce_tests! :test_update_with_dirty_primary_key
1132
+ def test_update_with_dirty_primary_key_coerced
1133
+ assert_raises(ActiveRecord::RecordNotUnique) do
1134
+ record = StringKeyObject.find('record1')
1135
+ record.id = 'record2'
1136
+ record.save!
1137
+ end
1138
+
1139
+ record = StringKeyObject.find('record1')
1140
+ record.id = 'record42'
1141
+ record.save!
1142
+
1143
+ assert StringKeyObject.find('record42')
1144
+ assert_raises(ActiveRecord::RecordNotFound) do
1145
+ StringKeyObject.find('record1')
1146
+ end
1147
+ end
1148
+ end
1149
+
1150
+
1151
+
1152
+ class RelationMergingTest < ActiveRecord::TestCase
1153
+ coerce_tests! :test_merging_with_order_with_binds
1154
+ def test_merging_with_order_with_binds_coerced
1155
+ relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
1156
+ assert_equal ["title LIKE N'%suffix'"], relation.order_values
1157
+ end
1158
+ end
1159
+
1160
+
1161
+ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
1162
+ # Temporarily coerce this test due to https://github.com/rails/rails/issues/34945
1163
+ coerce_tests! :test_eager_loading_too_may_ids
1164
+ end