activerecord 5.2.2 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +121 -0
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +4 -5
- data/lib/active_record/associations/collection_proxy.rb +8 -34
- data/lib/active_record/associations/has_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_through_association.rb +6 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/autosave_association.rb +20 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
- data/lib/active_record/core.rb +2 -1
- data/lib/active_record/errors.rb +18 -12
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +15 -15
- data/lib/active_record/persistence.rb +3 -1
- data/lib/active_record/querying.rb +1 -2
- data/lib/active_record/reflection.rb +10 -14
- data/lib/active_record/relation/calculations.rb +16 -12
- data/lib/active_record/relation/finder_methods.rb +6 -2
- data/lib/active_record/relation/merger.rb +6 -3
- data/lib/active_record/relation/predicate_builder.rb +14 -9
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +35 -10
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/transactions.rb +1 -1
- metadata +9 -10
@@ -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, :
|
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(
|
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(
|
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 =
|
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
|
-
|
24
|
-
|
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
|
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(
|
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(
|
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(
|
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,
|
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,
|
902
|
+
remove_foreign_key(table_name, foreign_key_options)
|
903
903
|
end
|
904
904
|
|
905
905
|
remove_column(table_name, "#{ref_name}_id")
|
@@ -804,15 +804,25 @@ module ActiveRecord
|
|
804
804
|
end
|
805
805
|
|
806
806
|
def mismatched_foreign_key(message)
|
807
|
-
|
808
|
-
|
809
|
-
|
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
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
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:
|
@@ -26,9 +26,9 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
28
|
case value
|
29
|
-
when /^-?\D
|
29
|
+
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
30
|
value.gsub!(/[^-\d.]/, "")
|
31
|
-
when /^-?\D
|
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
|
-
|
710
|
-
|
711
|
-
sqls
|
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
|
-
|
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)
|
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
|
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
|
data/lib/active_record/core.rb
CHANGED
@@ -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
|
|
data/lib/active_record/errors.rb
CHANGED
@@ -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(
|
121
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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.
|
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.
|