activerecord 6.0.0.rc1 → 6.0.0.rc2
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 +39 -0
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +11 -2
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_methods.rb +0 -51
- data/lib/active_record/attribute_methods/dirty.rb +6 -1
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +93 -11
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +40 -20
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +3 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -2
- data/lib/active_record/connection_handling.rb +6 -2
- data/lib/active_record/database_configurations.rb +6 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +3 -3
- data/lib/active_record/middleware/database_selector/resolver.rb +2 -2
- data/lib/active_record/migration.rb +26 -23
- data/lib/active_record/railtie.rb +0 -1
- data/lib/active_record/railties/databases.rake +57 -23
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/relation/calculations.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +4 -2
- data/lib/active_record/relation/merger.rb +6 -2
- data/lib/active_record/relation/query_methods.rb +32 -32
- data/lib/active_record/sanitization.rb +30 -2
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +5 -1
- data/lib/active_record/table_metadata.rb +6 -10
- data/lib/active_record/tasks/database_tasks.rb +41 -8
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/timestamp.rb +26 -16
- data/lib/active_record/touch_later.rb +2 -0
- data/lib/active_record/transactions.rb +9 -10
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/arel/visitors/depth_first.rb +1 -1
- data/lib/arel/visitors/to_sql.rb +23 -26
- data/lib/arel/visitors/visitor.rb +9 -5
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97bc72165e9eb1685576780f439be5be8894b7c589cc890d8c6340b8125de0a4
|
4
|
+
data.tar.gz: 74d58cd2f64976ef8faa42a02e94e6d9c9356fce3f0f2c219f7377a09712e7a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5306d17e736a64c87fc0115299b055c58343df4d18a53d532b09e570d6cc799abf84b4149deef9c03f56563ed23ee563cf57d03fc6c65529e9a6aa0324ca1614
|
7
|
+
data.tar.gz: 6fe8094ad7c0d0433c23691e372417124bddf17659c4bcb64b0bca5af0513d2c9b8599cfe0c244d28649529ab64b99c4a74b638bdf5ab9b4932c276a287d1e78
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,42 @@
|
|
1
|
+
## Rails 6.0.0.rc2 (July 22, 2019) ##
|
2
|
+
|
3
|
+
* Add database_exists? method to connection adapters to check if a database exists.
|
4
|
+
|
5
|
+
*Guilherme Mansur*
|
6
|
+
|
7
|
+
* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
|
8
|
+
|
9
|
+
Fixes #36022.
|
10
|
+
|
11
|
+
*Ryuta Kamizono*
|
12
|
+
|
13
|
+
* Make ActiveRecord `ConnectionPool.connections` method thread-safe.
|
14
|
+
|
15
|
+
Fixes #36465.
|
16
|
+
|
17
|
+
*Jeff Doering*
|
18
|
+
|
19
|
+
* Fix sqlite3 collation parsing when using decimal columns.
|
20
|
+
|
21
|
+
*Martin R. Schuster*
|
22
|
+
|
23
|
+
* Fix invalid schema when primary key column has a comment.
|
24
|
+
|
25
|
+
Fixes #29966.
|
26
|
+
|
27
|
+
*Guilherme Goettems Schneider*
|
28
|
+
|
29
|
+
* Fix table comment also being applied to the primary key column.
|
30
|
+
|
31
|
+
*Guilherme Goettems Schneider*
|
32
|
+
|
33
|
+
* Fix merging left_joins to maintain its own `join_type` context.
|
34
|
+
|
35
|
+
Fixes #36103.
|
36
|
+
|
37
|
+
*Ryuta Kamizono*
|
38
|
+
|
39
|
+
|
1
40
|
## Rails 6.0.0.rc1 (April 24, 2019) ##
|
2
41
|
|
3
42
|
* Add `touch` option to `has_one` association.
|
@@ -22,9 +22,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
22
22
|
|
23
23
|
def self.define_extensions(model, name, &block)
|
24
24
|
if block_given?
|
25
|
-
extension_module_name = "#{
|
25
|
+
extension_module_name = "#{name.to_s.camelize}AssociationExtension"
|
26
26
|
extension = Module.new(&block)
|
27
|
-
model.
|
27
|
+
model.const_set(extension_module_name, extension)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -1029,7 +1029,7 @@ module ActiveRecord
|
|
1029
1029
|
alias_method :append, :<<
|
1030
1030
|
alias_method :concat, :<<
|
1031
1031
|
|
1032
|
-
def prepend(*args)
|
1032
|
+
def prepend(*args) # :nodoc:
|
1033
1033
|
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
|
1034
1034
|
end
|
1035
1035
|
|
@@ -64,16 +64,17 @@ module ActiveRecord
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def initialize(base, table, associations)
|
67
|
+
def initialize(base, table, associations, join_type)
|
68
68
|
tree = self.class.make_tree associations
|
69
69
|
@join_root = JoinBase.new(base, table, build(tree, base))
|
70
|
+
@join_type = join_type
|
70
71
|
end
|
71
72
|
|
72
73
|
def reflections
|
73
74
|
join_root.drop(1).map!(&:reflection)
|
74
75
|
end
|
75
76
|
|
76
|
-
def join_constraints(joins_to_add,
|
77
|
+
def join_constraints(joins_to_add, alias_tracker)
|
77
78
|
@alias_tracker = alias_tracker
|
78
79
|
|
79
80
|
construct_tables!(join_root)
|
@@ -82,9 +83,9 @@ module ActiveRecord
|
|
82
83
|
joins.concat joins_to_add.flat_map { |oj|
|
83
84
|
construct_tables!(oj.join_root)
|
84
85
|
if join_root.match? oj.join_root
|
85
|
-
walk join_root, oj.
|
86
|
+
walk(join_root, oj.join_root, oj.join_type)
|
86
87
|
else
|
87
|
-
make_join_constraints(oj.join_root, join_type)
|
88
|
+
make_join_constraints(oj.join_root, oj.join_type)
|
88
89
|
end
|
89
90
|
}
|
90
91
|
end
|
@@ -125,7 +126,7 @@ module ActiveRecord
|
|
125
126
|
end
|
126
127
|
|
127
128
|
protected
|
128
|
-
attr_reader :join_root
|
129
|
+
attr_reader :join_root, :join_type
|
129
130
|
|
130
131
|
private
|
131
132
|
attr_reader :alias_tracker
|
@@ -151,7 +152,7 @@ module ActiveRecord
|
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
154
|
-
def make_constraints(parent, child, join_type
|
155
|
+
def make_constraints(parent, child, join_type)
|
155
156
|
foreign_table = parent.table
|
156
157
|
foreign_klass = parent.base_klass
|
157
158
|
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
|
@@ -173,13 +174,13 @@ module ActiveRecord
|
|
173
174
|
join ? "#{name}_join" : name
|
174
175
|
end
|
175
176
|
|
176
|
-
def walk(left, right)
|
177
|
+
def walk(left, right, join_type)
|
177
178
|
intersection, missing = right.children.map { |node1|
|
178
179
|
[left.children.find { |node2| node1.match? node2 }, node1]
|
179
180
|
}.partition(&:first)
|
180
181
|
|
181
|
-
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
|
182
|
-
joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
|
182
|
+
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
|
183
|
+
joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
|
183
184
|
end
|
184
185
|
|
185
186
|
def find_reflection(klass, name)
|
@@ -44,8 +44,7 @@ module ActiveRecord
|
|
44
44
|
|
45
45
|
unless others.empty?
|
46
46
|
joins.concat arel.join_sources
|
47
|
-
|
48
|
-
right.expr.children.concat(others)
|
47
|
+
append_constraints(joins.last, others)
|
49
48
|
end
|
50
49
|
|
51
50
|
# The current table in this iteration becomes the foreign table in the next
|
@@ -65,6 +64,16 @@ module ActiveRecord
|
|
65
64
|
|
66
65
|
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
|
67
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def append_constraints(join, constraints)
|
70
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
71
|
+
join_string = table.create_and(constraints.unshift(join.left))
|
72
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
73
|
+
else
|
74
|
+
join.right.expr.children.concat(constraints)
|
75
|
+
end
|
76
|
+
end
|
68
77
|
end
|
69
78
|
end
|
70
79
|
end
|
@@ -27,7 +27,9 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def records_by_owner
|
30
|
-
|
30
|
+
# owners can be duplicated when a relation has a collection association join
|
31
|
+
# #compare_by_identity makes such owners different hash keys
|
32
|
+
@records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
|
31
33
|
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
32
34
|
(result[owner] ||= []) << record
|
33
35
|
end
|
@@ -159,57 +159,6 @@ module ActiveRecord
|
|
159
159
|
end
|
160
160
|
end
|
161
161
|
|
162
|
-
# Regexp for column names (with or without a table name prefix). Matches
|
163
|
-
# the following:
|
164
|
-
# "#{table_name}.#{column_name}"
|
165
|
-
# "#{column_name}"
|
166
|
-
COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
|
167
|
-
|
168
|
-
# Regexp for column names with order (with or without a table name
|
169
|
-
# prefix, with or without various order modifiers). Matches the following:
|
170
|
-
# "#{table_name}.#{column_name}"
|
171
|
-
# "#{table_name}.#{column_name} #{direction}"
|
172
|
-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
173
|
-
# "#{table_name}.#{column_name} NULLS LAST"
|
174
|
-
# "#{column_name}"
|
175
|
-
# "#{column_name} #{direction}"
|
176
|
-
# "#{column_name} #{direction} NULLS FIRST"
|
177
|
-
# "#{column_name} NULLS LAST"
|
178
|
-
COLUMN_NAME_WITH_ORDER = /
|
179
|
-
\A
|
180
|
-
(?:\w+\.)?
|
181
|
-
\w+
|
182
|
-
(?:\s+asc|\s+desc)?
|
183
|
-
(?:\s+nulls\s+(?:first|last))?
|
184
|
-
\z
|
185
|
-
/ix
|
186
|
-
|
187
|
-
def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
|
188
|
-
unexpected = args.reject do |arg|
|
189
|
-
Arel.arel_node?(arg) ||
|
190
|
-
arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
|
191
|
-
end
|
192
|
-
|
193
|
-
return if unexpected.none?
|
194
|
-
|
195
|
-
if allow_unsafe_raw_sql == :deprecated
|
196
|
-
ActiveSupport::Deprecation.warn(
|
197
|
-
"Dangerous query method (method whose arguments are used as raw " \
|
198
|
-
"SQL) called with non-attribute argument(s): " \
|
199
|
-
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
|
200
|
-
"arguments will be disallowed in Rails 6.1. This method should " \
|
201
|
-
"not be called with user-provided values, such as request " \
|
202
|
-
"parameters or model attributes. Known-safe values can be passed " \
|
203
|
-
"by wrapping them in Arel.sql()."
|
204
|
-
)
|
205
|
-
else
|
206
|
-
raise(ActiveRecord::UnknownAttributeReference,
|
207
|
-
"Query method called with non-attribute argument(s): " +
|
208
|
-
unexpected.map(&:inspect).join(", ")
|
209
|
-
)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
162
|
# Returns true if the given attribute exists, otherwise false.
|
214
163
|
#
|
215
164
|
# class Person < ActiveRecord::Base
|
@@ -177,6 +177,11 @@ module ActiveRecord
|
|
177
177
|
|
178
178
|
affected_rows = super
|
179
179
|
|
180
|
+
if @_skip_dirty_tracking ||= false
|
181
|
+
clear_attribute_changes(@_touch_attr_names)
|
182
|
+
return affected_rows
|
183
|
+
end
|
184
|
+
|
180
185
|
changes = {}
|
181
186
|
@attributes.keys.each do |attr_name|
|
182
187
|
next if @_touch_attr_names.include?(attr_name)
|
@@ -193,7 +198,7 @@ module ActiveRecord
|
|
193
198
|
|
194
199
|
affected_rows
|
195
200
|
ensure
|
196
|
-
@_touch_attr_names = nil
|
201
|
+
@_touch_attr_names, @_skip_dirty_tracking = nil, nil
|
197
202
|
end
|
198
203
|
|
199
204
|
def _update_record(attribute_names = attribute_names_for_partial_writes)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "thread"
|
4
4
|
require "concurrent/map"
|
5
5
|
require "monitor"
|
6
|
+
require "weakref"
|
6
7
|
|
7
8
|
module ActiveRecord
|
8
9
|
# Raised when a connection could not be obtained within the connection
|
@@ -19,6 +20,26 @@ module ActiveRecord
|
|
19
20
|
end
|
20
21
|
|
21
22
|
module ConnectionAdapters
|
23
|
+
module AbstractPool # :nodoc:
|
24
|
+
def get_schema_cache(connection)
|
25
|
+
@schema_cache ||= SchemaCache.new(connection)
|
26
|
+
@schema_cache.connection = connection
|
27
|
+
@schema_cache
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_schema_cache(cache)
|
31
|
+
@schema_cache = cache
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NullPool # :nodoc:
|
36
|
+
include ConnectionAdapters::AbstractPool
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@schema_cache = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
22
43
|
# Connection pool base class for managing Active Record database
|
23
44
|
# connections.
|
24
45
|
#
|
@@ -294,23 +315,51 @@ module ActiveRecord
|
|
294
315
|
@frequency = frequency
|
295
316
|
end
|
296
317
|
|
318
|
+
@mutex = Mutex.new
|
319
|
+
@pools = {}
|
320
|
+
|
321
|
+
class << self
|
322
|
+
def register_pool(pool, frequency) # :nodoc:
|
323
|
+
@mutex.synchronize do
|
324
|
+
unless @pools.key?(frequency)
|
325
|
+
@pools[frequency] = []
|
326
|
+
spawn_thread(frequency)
|
327
|
+
end
|
328
|
+
@pools[frequency] << WeakRef.new(pool)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
private
|
333
|
+
|
334
|
+
def spawn_thread(frequency)
|
335
|
+
Thread.new(frequency) do |t|
|
336
|
+
loop do
|
337
|
+
sleep t
|
338
|
+
@mutex.synchronize do
|
339
|
+
@pools[frequency].select!(&:weakref_alive?)
|
340
|
+
@pools[frequency].each do |p|
|
341
|
+
p.reap
|
342
|
+
p.flush
|
343
|
+
rescue WeakRef::RefError
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
297
351
|
def run
|
298
352
|
return unless frequency && frequency > 0
|
299
|
-
|
300
|
-
loop do
|
301
|
-
sleep t
|
302
|
-
p.reap
|
303
|
-
p.flush
|
304
|
-
end
|
305
|
-
}
|
353
|
+
self.class.register_pool(pool, frequency)
|
306
354
|
end
|
307
355
|
end
|
308
356
|
|
309
357
|
include MonitorMixin
|
310
358
|
include QueryCache::ConnectionPoolConfiguration
|
359
|
+
include ConnectionAdapters::AbstractPool
|
311
360
|
|
312
361
|
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
313
|
-
attr_reader :spec, :
|
362
|
+
attr_reader :spec, :size, :reaper
|
314
363
|
|
315
364
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
316
365
|
# object which describes database connection information (e.g. adapter,
|
@@ -379,7 +428,7 @@ module ActiveRecord
|
|
379
428
|
# #connection can be called any number of times; the connection is
|
380
429
|
# held in a cache keyed by a thread.
|
381
430
|
def connection
|
382
|
-
@thread_cached_conns[connection_cache_key(
|
431
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
383
432
|
end
|
384
433
|
|
385
434
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -388,7 +437,7 @@ module ActiveRecord
|
|
388
437
|
# #connection or #with_connection methods. Connections obtained through
|
389
438
|
# #checkout will not be detected by #active_connection?
|
390
439
|
def active_connection?
|
391
|
-
@thread_cached_conns[connection_cache_key(
|
440
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
392
441
|
end
|
393
442
|
|
394
443
|
# Signal that the thread is finished with the current connection.
|
@@ -423,6 +472,21 @@ module ActiveRecord
|
|
423
472
|
synchronize { @connections.any? }
|
424
473
|
end
|
425
474
|
|
475
|
+
# Returns an array containing the connections currently in the pool.
|
476
|
+
# Access to the array does not require synchronization on the pool because
|
477
|
+
# the array is newly created and not retained by the pool.
|
478
|
+
#
|
479
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
480
|
+
# access pattern. A returned connection may be owned by another thread,
|
481
|
+
# unowned, or by happen-stance owned by the calling thread.
|
482
|
+
#
|
483
|
+
# Calling methods on a connection without ownership is subject to the
|
484
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
485
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
486
|
+
def connections
|
487
|
+
synchronize { @connections.dup }
|
488
|
+
end
|
489
|
+
|
426
490
|
# Disconnects all connections in the pool, and clears the pool.
|
427
491
|
#
|
428
492
|
# Raises:
|
@@ -668,6 +732,10 @@ module ActiveRecord
|
|
668
732
|
thread
|
669
733
|
end
|
670
734
|
|
735
|
+
def current_thread
|
736
|
+
@lock_thread || Thread.current
|
737
|
+
end
|
738
|
+
|
671
739
|
# Take control of all existing connections so a "group" action such as
|
672
740
|
# reload/disconnect can be performed safely. It is no longer enough to
|
673
741
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -809,7 +877,6 @@ module ActiveRecord
|
|
809
877
|
|
810
878
|
def new_connection
|
811
879
|
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
812
|
-
conn.schema_cache = schema_cache.dup if schema_cache
|
813
880
|
conn.check_version
|
814
881
|
end
|
815
882
|
end
|
@@ -938,15 +1005,30 @@ module ActiveRecord
|
|
938
1005
|
end
|
939
1006
|
end
|
940
1007
|
|
1008
|
+
attr_reader :prevent_writes
|
1009
|
+
|
941
1010
|
def initialize
|
942
1011
|
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
943
1012
|
@owner_to_pool = ConnectionHandler.create_owner_to_pool
|
1013
|
+
@prevent_writes = false
|
944
1014
|
|
945
1015
|
# Backup finalizer: if the forked child never needed a pool, the above
|
946
1016
|
# early discard has not occurred
|
947
1017
|
ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
|
948
1018
|
end
|
949
1019
|
|
1020
|
+
# Prevent writing to the database regardless of role.
|
1021
|
+
#
|
1022
|
+
# In some cases you may want to prevent writes to the database
|
1023
|
+
# even if you are on a database that can write. `while_preventing_writes`
|
1024
|
+
# will prevent writes to the database for the duration of the block.
|
1025
|
+
def while_preventing_writes
|
1026
|
+
original, @prevent_writes = @prevent_writes, true
|
1027
|
+
yield
|
1028
|
+
ensure
|
1029
|
+
@prevent_writes = original
|
1030
|
+
end
|
1031
|
+
|
950
1032
|
def connection_pool_list
|
951
1033
|
owner_to_pool.values.compact
|
952
1034
|
end
|