activerecord 5.2.2.1 → 5.2.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +116 -0
  3. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  4. data/lib/active_record/associations/collection_association.rb +4 -5
  5. data/lib/active_record/associations/collection_proxy.rb +8 -34
  6. data/lib/active_record/associations/has_many_association.rb +1 -0
  7. data/lib/active_record/associations/has_many_through_association.rb +6 -11
  8. data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
  9. data/lib/active_record/associations/preloader.rb +1 -1
  10. data/lib/active_record/autosave_association.rb +20 -6
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -10
  12. data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
  13. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  14. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  16. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
  17. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
  18. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  19. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  20. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
  21. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
  22. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
  23. data/lib/active_record/core.rb +2 -1
  24. data/lib/active_record/errors.rb +18 -12
  25. data/lib/active_record/gem_version.rb +2 -2
  26. data/lib/active_record/migration/compatibility.rb +15 -15
  27. data/lib/active_record/persistence.rb +3 -1
  28. data/lib/active_record/querying.rb +1 -2
  29. data/lib/active_record/reflection.rb +10 -14
  30. data/lib/active_record/relation/calculations.rb +16 -12
  31. data/lib/active_record/relation/finder_methods.rb +6 -2
  32. data/lib/active_record/relation/merger.rb +6 -3
  33. data/lib/active_record/relation/predicate_builder.rb +14 -9
  34. data/lib/active_record/relation/query_attribute.rb +5 -3
  35. data/lib/active_record/relation/query_methods.rb +35 -10
  36. data/lib/active_record/scoping/default.rb +2 -2
  37. data/lib/active_record/statement_cache.rb +2 -2
  38. data/lib/active_record/transactions.rb +1 -1
  39. metadata +9 -9
@@ -310,7 +310,7 @@ module ActiveRecord
310
310
  include QueryCache::ConnectionPoolConfiguration
311
311
 
312
312
  attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
- attr_reader :spec, :connections, :size, :reaper
313
+ attr_reader :spec, :size, :reaper
314
314
 
315
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
316
316
  # object which describes database connection information (e.g. adapter,
@@ -379,7 +379,7 @@ module ActiveRecord
379
379
  # #connection can be called any number of times; the connection is
380
380
  # held in a cache keyed by a thread.
381
381
  def connection
382
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
382
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
383
383
  end
384
384
 
385
385
  # Returns true if there is an open connection being used for the current thread.
@@ -388,7 +388,7 @@ module ActiveRecord
388
388
  # #connection or #with_connection methods. Connections obtained through
389
389
  # #checkout will not be detected by #active_connection?
390
390
  def active_connection?
391
- @thread_cached_conns[connection_cache_key(Thread.current)]
391
+ @thread_cached_conns[connection_cache_key(current_thread)]
392
392
  end
393
393
 
394
394
  # Signal that the thread is finished with the current connection.
@@ -423,6 +423,21 @@ module ActiveRecord
423
423
  synchronize { @connections.any? }
424
424
  end
425
425
 
426
+ # Returns an array containing the connections currently in the pool.
427
+ # Access to the array does not require synchronization on the pool because
428
+ # the array is newly created and not retained by the pool.
429
+ #
430
+ # However; this method bypasses the ConnectionPool's thread-safe connection
431
+ # access pattern. A returned connection may be owned by another thread,
432
+ # unowned, or by happen-stance owned by the calling thread.
433
+ #
434
+ # Calling methods on a connection without ownership is subject to the
435
+ # thread-safety guarantees of the underlying method. Many of the methods
436
+ # on connection adapter classes are inherently multi-thread unsafe.
437
+ def connections
438
+ synchronize { @connections.dup }
439
+ end
440
+
426
441
  # Disconnects all connections in the pool, and clears the pool.
427
442
  #
428
443
  # Raises:
@@ -668,6 +683,10 @@ module ActiveRecord
668
683
  thread
669
684
  end
670
685
 
686
+ def current_thread
687
+ @lock_thread || Thread.current
688
+ end
689
+
671
690
  # Take control of all existing connections so a "group" action such as
672
691
  # reload/disconnect can be performed safely. It is no longer enough to
673
692
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -915,6 +934,16 @@ module ActiveRecord
915
934
  # about the model. The model needs to pass a specification name to the handler,
916
935
  # in order to look up the correct connection pool.
917
936
  class ConnectionHandler
937
+ def self.create_owner_to_pool # :nodoc:
938
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
939
+ # Discard the parent's connection pools immediately; we have no need
940
+ # of them
941
+ discard_unowned_pools(h)
942
+
943
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
944
+ end
945
+ end
946
+
918
947
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
919
948
  lambda do |_|
920
949
  discard_unowned_pools(pid_map)
@@ -929,13 +958,7 @@ module ActiveRecord
929
958
 
930
959
  def initialize
931
960
  # These caches are keyed by spec.name (ConnectionSpecification#name).
932
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
933
- # Discard the parent's connection pools immediately; we have no need
934
- # of them
935
- ConnectionHandler.discard_unowned_pools(h)
936
-
937
- h[k] = Concurrent::Map.new(initial_capacity: 2)
938
- end
961
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
939
962
 
940
963
  # Backup finalizer: if the forked child never needed a pool, the above
941
964
  # early discard has not occurred
@@ -20,9 +20,22 @@ module ActiveRecord
20
20
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
21
  "The values must be stored on the AST directly"
22
22
  end
23
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
24
- [sql.freeze, binds || []]
23
+
24
+ if prepared_statements
25
+ sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
26
+
27
+ if binds.length > bind_params_length
28
+ unprepared_statement do
29
+ sql, binds = to_sql_and_binds(arel_or_sql_string)
30
+ visitor.preparable = false
31
+ end
32
+ end
33
+ else
34
+ sql = visitor.accept(arel_or_sql_string.ast, collector).value
35
+ end
36
+ [sql.freeze, binds]
25
37
  else
38
+ visitor.preparable = false if prepared_statements
26
39
  [arel_or_sql_string.dup.freeze, binds]
27
40
  end
28
41
  end
@@ -47,13 +60,8 @@ module ActiveRecord
47
60
  arel = arel_from_relation(arel)
48
61
  sql, binds = to_sql_and_binds(arel, binds)
49
62
 
50
- if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
51
- preparable = false
52
- elsif binds.length > bind_params_length
53
- sql, binds = unprepared_statement { to_sql_and_binds(arel) }
54
- preparable = false
55
- else
56
- preparable = visitor.preparable
63
+ if preparable.nil?
64
+ preparable = prepared_statements ? visitor.preparable : false
57
65
  end
58
66
 
59
67
  if prepared_statements && preparable
@@ -32,17 +32,17 @@ module ActiveRecord
32
32
  end
33
33
 
34
34
  def enable_query_cache!
35
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
35
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
36
36
  connection.enable_query_cache! if active_connection?
37
37
  end
38
38
 
39
39
  def disable_query_cache!
40
- @query_cache_enabled.delete connection_cache_key(Thread.current)
40
+ @query_cache_enabled.delete connection_cache_key(current_thread)
41
41
  connection.disable_query_cache! if active_connection?
42
42
  end
43
43
 
44
44
  def query_cache_enabled
45
- @query_cache_enabled[connection_cache_key(Thread.current)]
45
+ @query_cache_enabled[connection_cache_key(current_thread)]
46
46
  end
47
47
  end
48
48
 
@@ -96,6 +96,11 @@ module ActiveRecord
96
96
  if @query_cache_enabled && !locked?(arel)
97
97
  arel = arel_from_relation(arel)
98
98
  sql, binds = to_sql_and_binds(arel, binds)
99
+
100
+ if preparable.nil?
101
+ preparable = prepared_statements ? visitor.preparable : false
102
+ end
103
+
99
104
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
100
105
  else
101
106
  super
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  def index_exists?(table_name, column_name, options = {})
101
101
  column_names = Array(column_name).map(&:to_s)
102
102
  checks = []
103
- checks << lambda { |i| i.columns == column_names }
103
+ checks << lambda { |i| Array(i.columns) == column_names }
104
104
  checks << lambda { |i| i.unique } if options[:unique]
105
105
  checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
106
106
 
@@ -305,7 +305,7 @@ module ActiveRecord
305
305
  yield td if block_given?
306
306
 
307
307
  if options[:force]
308
- drop_table(table_name, **options, if_exists: true)
308
+ drop_table(table_name, options.merge(if_exists: true))
309
309
  end
310
310
 
311
311
  result = execute schema_creation.accept td
@@ -899,7 +899,7 @@ module ActiveRecord
899
899
  foreign_key_options = { to_table: reference_name }
900
900
  end
901
901
  foreign_key_options[:column] ||= "#{ref_name}_id"
902
- remove_foreign_key(table_name, **foreign_key_options)
902
+ remove_foreign_key(table_name, foreign_key_options)
903
903
  end
904
904
 
905
905
  remove_column(table_name, "#{ref_name}_id")
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
  alias :in_use? :owner
82
82
 
83
83
  def self.type_cast_config_to_integer(config)
84
- if config =~ SIMPLE_INT
84
+ if config.is_a?(Integer)
85
+ config
86
+ elsif config =~ SIMPLE_INT
85
87
  config.to_i
86
88
  else
87
89
  config
@@ -804,15 +804,25 @@ module ActiveRecord
804
804
  end
805
805
 
806
806
  def mismatched_foreign_key(message)
807
- parts = message.scan(/`(\w+)`[ $)]/).flatten
808
- MismatchedForeignKey.new(
809
- self,
807
+ match = %r/
808
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
+ /xmi.match(message)
812
+
813
+ options = {
810
814
  message: message,
811
- table: parts[0],
812
- foreign_key: parts[1],
813
- target_table: parts[2],
814
- primary_key: parts[3],
815
- )
815
+ }
816
+
817
+ if match
818
+ options[:table] = match[:table]
819
+ options[:foreign_key] = match[:foreign_key]
820
+ options[:target_table] = match[:target_table]
821
+ options[:primary_key] = match[:primary_key]
822
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
+ end
824
+
825
+ MismatchedForeignKey.new(options)
816
826
  end
817
827
 
818
828
  def integer_to_sql(limit) # :nodoc:
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module DetermineIfPreparableVisitor
6
- attr_reader :preparable
6
+ attr_accessor :preparable
7
7
 
8
8
  def accept(*)
9
9
  @preparable = true
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
29
+ when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
30
30
  value.gsub!(/[^-\d.]/, "")
31
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
31
+ when /^-?\D*+[\d.]+,\d{2}$/ # (2)
32
32
  value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
33
33
  end
34
34
 
@@ -17,6 +17,42 @@ module ActiveRecord
17
17
  "VALIDATE CONSTRAINT #{quote_column_name(name)}"
18
18
  end
19
19
 
20
+ def visit_ChangeColumnDefinition(o)
21
+ column = o.column
22
+ column.sql_type = type_to_sql(column.type, column.options)
23
+ quoted_column_name = quote_column_name(o.name)
24
+
25
+ change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
26
+
27
+ options = column_options(column)
28
+
29
+ if options[:collation]
30
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
31
+ end
32
+
33
+ if options[:using]
34
+ change_column_sql << " USING #{options[:using]}"
35
+ elsif options[:cast_as]
36
+ cast_as_type = type_to_sql(options[:cast_as], options)
37
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
38
+ end
39
+
40
+ if options.key?(:default)
41
+ if options[:default].nil?
42
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
43
+ else
44
+ quoted_default = quote_default_expression(options[:default], column)
45
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
46
+ end
47
+ end
48
+
49
+ if options.key?(:null)
50
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
51
+ end
52
+
53
+ change_column_sql
54
+ end
55
+
20
56
  def add_column_options!(sql, options)
21
57
  if options[:collation]
22
58
  sql << " COLLATE \"#{options[:collation]}\""
@@ -683,38 +683,20 @@ module ActiveRecord
683
683
  end
684
684
  end
685
685
 
686
- def change_column_sql(table_name, column_name, type, options = {})
687
- quoted_column_name = quote_column_name(column_name)
688
- sql_type = type_to_sql(type, options)
689
- sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
690
- if options[:collation]
691
- sql << " COLLATE \"#{options[:collation]}\""
692
- end
693
- if options[:using]
694
- sql << " USING #{options[:using]}"
695
- elsif options[:cast_as]
696
- cast_as_type = type_to_sql(options[:cast_as], options)
697
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
698
- end
699
-
700
- sql
701
- end
702
-
703
686
  def add_column_for_alter(table_name, column_name, type, options = {})
704
687
  return super unless options.key?(:comment)
705
688
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
706
689
  end
707
690
 
708
691
  def change_column_for_alter(table_name, column_name, type, options = {})
709
- sqls = [change_column_sql(table_name, column_name, type, options)]
710
- sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
711
- sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
692
+ td = create_table_definition(table_name)
693
+ cd = td.new_column_definition(column_name, type, options)
694
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
712
695
  sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
713
696
  sqls
714
697
  end
715
698
 
716
- # Changes the default value of a table column.
717
- def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
699
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
718
700
  column = column_for(table_name, column_name)
719
701
  return unless column
720
702
 
@@ -729,8 +711,8 @@ module ActiveRecord
729
711
  end
730
712
  end
731
713
 
732
- def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
733
- "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
714
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
715
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
734
716
  end
735
717
 
736
718
  def add_timestamps_for_alter(table_name, options = {})
@@ -12,6 +12,10 @@ module ActiveRecord
12
12
  quote_column_name(attr)
13
13
  end
14
14
 
15
+ def quote_table_name(name)
16
+ @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
17
+ end
18
+
15
19
  def quote_column_name(name)
16
20
  @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
17
21
  end
@@ -9,7 +9,7 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
9
9
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
10
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
11
 
12
- gem "sqlite3", "~> 1.3.6"
12
+ gem "sqlite3", "~> 1.3", ">= 1.3.6"
13
13
  require "sqlite3"
14
14
 
15
15
  module ActiveRecord
@@ -525,9 +525,9 @@ module ActiveRecord
525
525
  result = exec_query(sql, "SCHEMA").first
526
526
 
527
527
  if result
528
- # Splitting with left parentheses and picking up last will return all
528
+ # Splitting with left parentheses and discarding the first part will return all
529
529
  # columns separated with comma(,).
530
- columns_string = result["sql"].split("(").last
530
+ columns_string = result["sql"].split("(", 2).last
531
531
 
532
532
  columns_string.split(",").each do |column_string|
533
533
  # This regex will match the column name and collation type and will save
@@ -184,7 +184,8 @@ module ActiveRecord
184
184
  end
185
185
 
186
186
  def find_by(*args) # :nodoc:
187
- return super if scope_attributes? || reflect_on_all_aggregations.any?
187
+ return super if scope_attributes? || reflect_on_all_aggregations.any? ||
188
+ columns_hash.key?(inheritance_column) && base_class != self
188
189
 
189
190
  hash = args.first
190
191
 
@@ -117,16 +117,27 @@ module ActiveRecord
117
117
 
118
118
  # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
119
119
  class MismatchedForeignKey < StatementInvalid
120
- def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
121
- @adapter = adapter
120
+ def initialize(
121
+ adapter = nil,
122
+ message: nil,
123
+ sql: nil,
124
+ binds: nil,
125
+ table: nil,
126
+ foreign_key: nil,
127
+ target_table: nil,
128
+ primary_key: nil,
129
+ primary_key_column: nil
130
+ )
122
131
  if table
123
- msg = <<-EOM.strip_heredoc
124
- Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
125
- This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
126
- To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
132
+ type = primary_key_column.bigint? ? :bigint : primary_key_column.type
133
+ msg = <<-EOM.squish
134
+ Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
135
+ which has type `#{primary_key_column.sql_type}`.
136
+ To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
137
+ (For example `t.#{type} :#{foreign_key}`).
127
138
  EOM
128
139
  else
129
- msg = <<-EOM.strip_heredoc
140
+ msg = <<-EOM.squish
130
141
  There is a mismatch between the foreign key and primary key column types.
131
142
  Verify that the foreign key column type and the primary key of the associated table match types.
132
143
  EOM
@@ -136,11 +147,6 @@ module ActiveRecord
136
147
  end
137
148
  super(msg)
138
149
  end
139
-
140
- private
141
- def column_type(table, column)
142
- @adapter.columns(table).detect { |c| c.name == column }.sql_type
143
- end
144
150
  end
145
151
 
146
152
  # Raised when a record cannot be inserted or updated because it would violate a not null constraint.