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.

Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  4. data/lib/active_record/associations/collection_proxy.rb +1 -1
  5. data/lib/active_record/associations/join_dependency.rb +10 -9
  6. data/lib/active_record/associations/join_dependency/join_association.rb +11 -2
  7. data/lib/active_record/associations/preloader/association.rb +3 -1
  8. data/lib/active_record/attribute_methods.rb +0 -51
  9. data/lib/active_record/attribute_methods/dirty.rb +6 -1
  10. data/lib/active_record/autosave_association.rb +1 -1
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +93 -11
  12. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  13. data/lib/active_record/connection_adapters/abstract/quoting.rb +53 -0
  14. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -7
  16. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +40 -20
  18. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  19. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  20. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +3 -1
  21. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -1
  22. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -1
  24. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  25. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -2
  26. data/lib/active_record/connection_handling.rb +6 -2
  27. data/lib/active_record/database_configurations.rb +6 -6
  28. data/lib/active_record/gem_version.rb +1 -1
  29. data/lib/active_record/middleware/database_selector.rb +3 -3
  30. data/lib/active_record/middleware/database_selector/resolver.rb +2 -2
  31. data/lib/active_record/migration.rb +26 -23
  32. data/lib/active_record/railtie.rb +0 -1
  33. data/lib/active_record/railties/databases.rake +57 -23
  34. data/lib/active_record/reflection.rb +1 -1
  35. data/lib/active_record/relation/calculations.rb +1 -1
  36. data/lib/active_record/relation/finder_methods.rb +4 -2
  37. data/lib/active_record/relation/merger.rb +6 -2
  38. data/lib/active_record/relation/query_methods.rb +32 -32
  39. data/lib/active_record/sanitization.rb +30 -2
  40. data/lib/active_record/schema.rb +1 -1
  41. data/lib/active_record/schema_dumper.rb +5 -1
  42. data/lib/active_record/table_metadata.rb +6 -10
  43. data/lib/active_record/tasks/database_tasks.rb +41 -8
  44. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  45. data/lib/active_record/timestamp.rb +26 -16
  46. data/lib/active_record/touch_later.rb +2 -0
  47. data/lib/active_record/transactions.rb +9 -10
  48. data/lib/active_record/type_caster/connection.rb +16 -10
  49. data/lib/arel/visitors/depth_first.rb +1 -1
  50. data/lib/arel/visitors/to_sql.rb +23 -26
  51. data/lib/arel/visitors/visitor.rb +9 -5
  52. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eef34b080ecf453eb7de6b2b21fd1664c907b4a56ce8b34077da04422c14b528
4
- data.tar.gz: 1f03004e330e5825d91500dd14584568fbbb647dd05a91d750d1f84ee83501b1
3
+ metadata.gz: 97bc72165e9eb1685576780f439be5be8894b7c589cc890d8c6340b8125de0a4
4
+ data.tar.gz: 74d58cd2f64976ef8faa42a02e94e6d9c9356fce3f0f2c219f7377a09712e7a4
5
5
  SHA512:
6
- metadata.gz: a9368890aab4ae0d88d495501d21a21481a4ccd891c7ddd65219cfb18fd10418456959c9e1b24edc13ba0200ee9dd96fa0deea56da2214d03e838293625782c3
7
- data.tar.gz: a5c09c091e320de679561ef84cd58833d8a3b4d477aa98f6d917fb494b20bbf3d55dfb6e3167e91a613f8868844a32ae71364b7fb35d5221ecf526066e8371cb
6
+ metadata.gz: 5306d17e736a64c87fc0115299b055c58343df4d18a53d532b09e570d6cc799abf84b4149deef9c03f56563ed23ee563cf57d03fc6c65529e9a6aa0324ca1614
7
+ data.tar.gz: 6fe8094ad7c0d0433c23691e372417124bddf17659c4bcb64b0bca5af0513d2c9b8599cfe0c244d28649529ab64b99c4a74b638bdf5ab9b4932c276a287d1e78
@@ -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 = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
25
+ extension_module_name = "#{name.to_s.camelize}AssociationExtension"
26
26
  extension = Module.new(&block)
27
- model.module_parent.const_set(extension_module_name, extension)
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, join_type, alias_tracker)
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.join_root
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 = Arel::Nodes::OuterJoin)
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
- right = joins.last.right
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
- @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
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)
@@ -416,7 +416,7 @@ module ActiveRecord
416
416
  saved = record.save(validate: false)
417
417
  end
418
418
 
419
- raise ActiveRecord::Rollback unless saved
419
+ raise(RecordInvalid.new(association.owner)) unless saved
420
420
  end
421
421
  end
422
422
  end
@@ -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
- Thread.new(frequency, pool) { |t, p|
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, :connections, :size, :reaper
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(@lock_thread || Thread.current)] ||= checkout
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(Thread.current)]
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