activerecord 7.2.4 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -87,6 +87,8 @@ module ActiveRecord
87
87
 
88
88
  connections = []
89
89
 
90
+ @shard_keys = shards.keys
91
+
90
92
  if shards.empty?
91
93
  shards[:default] = database
92
94
  end
@@ -98,7 +100,8 @@ module ActiveRecord
98
100
  db_config = resolve_config_for_connection(database_key)
99
101
 
100
102
  self.connection_class = true
101
- connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
103
+ shard = shard.to_sym unless shard.is_a? Integer
104
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
102
105
  end
103
106
  end
104
107
 
@@ -177,6 +180,18 @@ module ActiveRecord
177
180
  end
178
181
  end
179
182
 
183
+ # Passes the block to +connected_to+ for every +shard+ the
184
+ # model is configured to connect to (if any), and returns the
185
+ # results in an array.
186
+ #
187
+ # Optionally, +role+ and/or +prevent_writes+ can be passed which
188
+ # will be forwarded to each +connected_to+ call.
189
+ def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
190
+ shard_keys.map do |shard|
191
+ connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
192
+ end
193
+ end
194
+
180
195
  # Use a specified connection.
181
196
  #
182
197
  # This method is useful for ensuring that a specific connection is
@@ -289,7 +304,7 @@ module ActiveRecord
289
304
 
290
305
  # Checkouts a connection from the pool, yield it and then check it back in.
291
306
  # If a connection was already leased via #lease_connection or a parent call to
292
- # #with_connection, that same connection is yieled.
307
+ # #with_connection, that same connection is yielded.
293
308
  # If #lease_connection is called inside the block, the connection won't be checked
294
309
  # back in.
295
310
  # If #connection is called inside the block, the connection won't be checked back in
@@ -361,6 +376,14 @@ module ActiveRecord
361
376
  connection_pool.schema_cache.clear!
362
377
  end
363
378
 
379
+ def shard_keys
380
+ connection_class_for_self.instance_variable_get(:@shard_keys) || []
381
+ end
382
+
383
+ def sharded?
384
+ shard_keys.any?
385
+ end
386
+
364
387
  private
365
388
  def resolve_config_for_connection(config_or_env)
366
389
  raise "Anonymous class is not allowed." unless name
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
- require "active_support/core_ext/module/delegation"
5
4
  require "active_support/parameter_filter"
6
5
  require "concurrent/map"
7
6
 
@@ -89,6 +88,7 @@ module ActiveRecord
89
88
  class_attribute :belongs_to_required_by_default, instance_accessor: false
90
89
 
91
90
  class_attribute :strict_loading_by_default, instance_accessor: false, default: false
91
+ class_attribute :strict_loading_mode, instance_accessor: false, default: :all
92
92
 
93
93
  class_attribute :has_many_inversing, instance_accessor: false, default: false
94
94
 
@@ -111,7 +111,7 @@ module ActiveRecord
111
111
  # Post.attributes_for_inspect = [:id, :title]
112
112
  # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
113
  #
114
- # When set to `:all` inspect will list all the record's attributes:
114
+ # When set to +:all+ inspect will list all the record's attributes:
115
115
  #
116
116
  # Post.attributes_for_inspect = :all
117
117
  # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
@@ -201,6 +201,17 @@ module ActiveRecord
201
201
  false
202
202
  end
203
203
 
204
+ # Intended to behave like `.current_preventing_writes` given the class name as input.
205
+ # See PoolConfig and ConnectionHandler::ConnectionDescriptor.
206
+ def self.preventing_writes?(class_name) # :nodoc:
207
+ connected_to_stack.reverse_each do |hash|
208
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
209
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name }
210
+ end
211
+
212
+ false
213
+ end
214
+
204
215
  def self.connected_to_stack # :nodoc:
205
216
  if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
206
217
  connected_to_stack
@@ -346,6 +357,8 @@ module ActiveRecord
346
357
  def filter_attributes=(filter_attributes)
347
358
  @inspection_filter = nil
348
359
  @filter_attributes = filter_attributes
360
+
361
+ FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
349
362
  end
350
363
 
351
364
  def inspection_filter # :nodoc:
@@ -381,7 +394,7 @@ module ActiveRecord
381
394
  end
382
395
 
383
396
  def predicate_builder # :nodoc:
384
- @predicate_builder ||= PredicateBuilder.new(table_metadata)
397
+ @predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
385
398
  end
386
399
 
387
400
  def type_caster # :nodoc:
@@ -426,10 +439,6 @@ module ActiveRecord
426
439
  end
427
440
  end
428
441
 
429
- def table_metadata
430
- TableMetadata.new(self, arel_table)
431
- end
432
-
433
442
  def cached_find_by(keys, values)
434
443
  with_connection do |connection|
435
444
  statement = cached_find_by_statement(connection, keys) { |params|
@@ -443,8 +452,8 @@ module ActiveRecord
443
452
  where(wheres).limit(1)
444
453
  }
445
454
 
446
- begin
447
- statement.execute(values.flatten, connection, allow_retry: true).first
455
+ statement.execute(values.flatten, connection).then do |r|
456
+ r.first
448
457
  rescue TypeError
449
458
  raise ActiveRecord::StatementInvalid
450
459
  end
@@ -540,12 +549,7 @@ module ActiveRecord
540
549
 
541
550
  ##
542
551
  def initialize_dup(other) # :nodoc:
543
- @attributes = @attributes.deep_dup
544
- if self.class.composite_primary_key?
545
- @primary_key.each { |key| @attributes.reset(key) }
546
- else
547
- @attributes.reset(@primary_key)
548
- end
552
+ @attributes = init_attributes(other)
549
553
 
550
554
  _run_initialize_callbacks
551
555
 
@@ -557,6 +561,18 @@ module ActiveRecord
557
561
  super
558
562
  end
559
563
 
564
+ def init_attributes(_) # :nodoc:
565
+ attrs = @attributes.deep_dup
566
+
567
+ if self.class.composite_primary_key?
568
+ @primary_key.each { |key| attrs.reset(key) }
569
+ else
570
+ attrs.reset(@primary_key)
571
+ end
572
+
573
+ attrs
574
+ end
575
+
560
576
  # Populate +coder+ with attributes about this record that should be
561
577
  # serialized. The structure of +coder+ defined in this method is
562
578
  # guaranteed to match the structure of +coder+ passed to the #init_with
@@ -626,7 +642,7 @@ module ActiveRecord
626
642
  def hash
627
643
  id = self.id
628
644
 
629
- if primary_key_values_present?
645
+ if self.class.composite_primary_key? ? primary_key_values_present? : id
630
646
  self.class.hash ^ id.hash
631
647
  else
632
648
  super
@@ -830,7 +846,7 @@ module ActiveRecord
830
846
 
831
847
  @primary_key = klass.primary_key
832
848
  @strict_loading = klass.strict_loading_by_default
833
- @strict_loading_mode = :all
849
+ @strict_loading_mode = klass.strict_loading_mode
834
850
 
835
851
  klass.define_attribute_methods
836
852
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  #
18
18
  # ==== Parameters
19
19
  #
20
- # * +id+ - The id of the object you wish to reset a counter on.
20
+ # * +id+ - The id of the object you wish to reset a counter on or an array of ids.
21
21
  # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
22
22
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
23
23
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
@@ -28,13 +28,27 @@ module ActiveRecord
28
28
  # # For the Post with id #1, reset the comments_count
29
29
  # Post.reset_counters(1, :comments)
30
30
  #
31
+ # # For posts with ids #1 and #2, reset the comments_count
32
+ # Post.reset_counters([1, 2], :comments)
33
+ #
31
34
  # # Like above, but also touch the updated_at and/or updated_on
32
35
  # # attributes.
33
36
  # Post.reset_counters(1, :comments, touch: true)
34
37
  def reset_counters(id, *counters, touch: nil)
35
- object = find(id)
38
+ if composite_primary_key?
39
+ ids = id.first.is_a?(Array) ? id : [id]
40
+ types = primary_key.map { |column| type_for_attribute(column) }
41
+ ids = ids.map do |id_value|
42
+ id_value.map.with_index { |id, i| types[i].cast(id) }
43
+ end
44
+ else
45
+ ids = Array(id)
46
+ type = type_for_attribute(primary_key)
47
+ ids = ids.map { |id| type.cast(id) }
48
+ end
49
+
50
+ updates = Hash.new { |h, k| h[k] = {} }
36
51
 
37
- updates = {}
38
52
  counters.each do |counter_association|
39
53
  has_many_association = _reflect_on_association(counter_association)
40
54
  unless has_many_association
@@ -48,14 +62,22 @@ module ActiveRecord
48
62
  has_many_association = has_many_association.through_reflection
49
63
  end
50
64
 
65
+ counter_association = counter_association.to_sym
51
66
  foreign_key = has_many_association.foreign_key.to_s
52
67
  child_class = has_many_association.klass
53
68
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
54
69
  counter_name = reflection.counter_cache_column
55
70
 
56
- count_was = object.send(counter_name)
57
- count = object.send(counter_association).count(:all)
58
- updates[counter_name] = count if count != count_was
71
+ counts =
72
+ unscoped
73
+ .joins(counter_association)
74
+ .where(primary_key => ids)
75
+ .group(primary_key)
76
+ .count(:all)
77
+
78
+ ids.each do |id|
79
+ updates[id].merge!(counter_name => counts[id] || 0)
80
+ end
59
81
  end
60
82
 
61
83
  if touch
@@ -63,10 +85,15 @@ module ActiveRecord
63
85
  names = Array.wrap(names)
64
86
  options = names.extract_options!
65
87
  touch_updates = touch_attributes_with_time(*names, **options)
66
- updates.merge!(touch_updates)
88
+
89
+ updates.each_value do |record_updates|
90
+ record_updates.merge!(touch_updates)
91
+ end
67
92
  end
68
93
 
69
- unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
94
+ updates.each do |id, record_updates|
95
+ unscoped.where(primary_key => [id]).update_all(record_updates)
96
+ end
70
97
 
71
98
  true
72
99
  end
@@ -48,7 +48,11 @@ module ActiveRecord
48
48
  raise NotImplementedError
49
49
  end
50
50
 
51
- def pool
51
+ def min_connections
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def max_connections
52
56
  raise NotImplementedError
53
57
  end
54
58
 
@@ -99,6 +103,10 @@ module ActiveRecord
99
103
  def use_metadata_table?
100
104
  raise NotImplementedError
101
105
  end
106
+
107
+ def seeds?
108
+ raise NotImplementedError
109
+ end
102
110
  end
103
111
  end
104
112
  end
@@ -38,6 +38,7 @@ module ActiveRecord
38
38
  def initialize(env_name, name, configuration_hash)
39
39
  super(env_name, name)
40
40
  @configuration_hash = configuration_hash.symbolize_keys.freeze
41
+ validate_configuration!
41
42
  end
42
43
 
43
44
  # Determines whether a database configuration is for a replica / readonly
@@ -69,16 +70,35 @@ module ActiveRecord
69
70
  @configuration_hash = configuration_hash.merge(database: database).freeze
70
71
  end
71
72
 
72
- def pool
73
- (configuration_hash[:pool] || 5).to_i
73
+ def max_connections
74
+ max_connections = configuration_hash.fetch(:max_connections) {
75
+ configuration_hash.fetch(:pool, 5)
76
+ }&.to_i
77
+ max_connections if max_connections && max_connections >= 0
74
78
  end
75
79
 
80
+ def min_connections
81
+ (configuration_hash[:min_connections] || 0).to_i
82
+ end
83
+
84
+ alias :pool :max_connections
85
+ deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
86
+
76
87
  def min_threads
77
88
  (configuration_hash[:min_threads] || 0).to_i
78
89
  end
79
90
 
80
91
  def max_threads
81
- (configuration_hash[:max_threads] || pool).to_i
92
+ (configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
93
+ end
94
+
95
+ def max_age
96
+ v = configuration_hash[:max_age]&.to_i
97
+ if v && v > 0
98
+ v
99
+ else
100
+ Float::INFINITY
101
+ end
82
102
  end
83
103
 
84
104
  def query_cache
@@ -93,10 +113,8 @@ module ActiveRecord
93
113
  (configuration_hash[:checkout_timeout] || 5).to_f
94
114
  end
95
115
 
96
- # `reaping_frequency` is configurable mostly for historical reasons, but it
97
- # could also be useful if someone wants a very low `idle_timeout`.
98
- def reaping_frequency
99
- configuration_hash.fetch(:reaping_frequency, 60)&.to_f
116
+ def reaping_frequency # :nodoc:
117
+ configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
100
118
  end
101
119
 
102
120
  def idle_timeout
@@ -104,6 +122,18 @@ module ActiveRecord
104
122
  timeout if timeout > 0
105
123
  end
106
124
 
125
+ def keepalive
126
+ case keepalive = configuration_hash[:keepalive]
127
+ when false
128
+ nil
129
+ when nil, true
130
+ 600.0 # default
131
+ else
132
+ keepalive = keepalive.to_f
133
+ keepalive if keepalive > 0.0
134
+ end
135
+ end
136
+
107
137
  def adapter
108
138
  configuration_hash[:adapter]&.to_s
109
139
  end
@@ -130,6 +160,14 @@ module ActiveRecord
130
160
  Base.configurations.primary?(name)
131
161
  end
132
162
 
163
+ # Determines whether the db:prepare task should seed the database from db/seeds.rb.
164
+ #
165
+ # If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it
166
+ # will return `true` for the primary database and `false` for all other configs.
167
+ def seeds?
168
+ configuration_hash.fetch(:seeds, primary?)
169
+ end
170
+
133
171
  # Determines whether to dump the schema/structure files and the filename that
134
172
  # should be used.
135
173
  #
@@ -138,7 +176,7 @@ module ActiveRecord
138
176
  #
139
177
  # If the config option is set that will be used. Otherwise Rails will generate
140
178
  # the filename from the database config name.
141
- def schema_dump(format = ActiveRecord.schema_format)
179
+ def schema_dump(format = schema_format)
142
180
  if configuration_hash.key?(:schema_dump)
143
181
  if config = configuration_hash[:schema_dump]
144
182
  config
@@ -150,6 +188,12 @@ module ActiveRecord
150
188
  end
151
189
  end
152
190
 
191
+ def schema_format # :nodoc:
192
+ format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
193
+ raise "Invalid schema format" unless [:ruby, :sql].include?(format)
194
+ format
195
+ end
196
+
153
197
  def database_tasks? # :nodoc:
154
198
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
155
199
  end
@@ -160,13 +204,34 @@ module ActiveRecord
160
204
 
161
205
  private
162
206
  def schema_file_type(format)
163
- case format
207
+ case format.to_sym
164
208
  when :ruby
165
209
  "schema.rb"
166
210
  when :sql
167
211
  "structure.sql"
168
212
  end
169
213
  end
214
+
215
+ def default_reaping_frequency
216
+ # Reap every 20 seconds by default, but run more often as necessary to
217
+ # meet other configured timeouts.
218
+ [20, idle_timeout, max_age, keepalive].compact.min
219
+ end
220
+
221
+ def validate_configuration!
222
+ if configuration_hash[:pool] && configuration_hash[:max_connections]
223
+ pool_val = configuration_hash[:pool].to_i
224
+ max_conn_val = configuration_hash[:max_connections].to_i
225
+
226
+ if pool_val != max_conn_val
227
+ raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
228
+ end
229
+ end
230
+
231
+ if configuration_hash[:pool] && configuration_hash[:min_connections]
232
+ raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
233
+ end
234
+ end
170
235
  end
171
236
  end
172
237
  end
@@ -47,17 +47,29 @@ module ActiveRecord
47
47
  @configuration_hash[:schema_dump] = false
48
48
  end
49
49
 
50
- if @configuration_hash[:query_cache] == "false"
51
- @configuration_hash[:query_cache] = false
52
- end
50
+ query_cache = parse_query_cache
51
+ @configuration_hash[:query_cache] = query_cache unless query_cache.nil?
53
52
 
54
53
  to_boolean!(@configuration_hash, :replica)
55
54
  to_boolean!(@configuration_hash, :database_tasks)
55
+ to_boolean!(@configuration_hash, :seeds)
56
+ to_boolean!(@configuration_hash, :use_metadata_table)
56
57
 
57
58
  @configuration_hash.freeze
58
59
  end
59
60
 
60
61
  private
62
+ def parse_query_cache
63
+ case value = @configuration_hash[:query_cache]
64
+ when /\A\d+\z/
65
+ value.to_i
66
+ when "false"
67
+ false
68
+ else
69
+ value
70
+ end
71
+ end
72
+
61
73
  def to_boolean!(configuration_hash, key)
62
74
  if configuration_hash[key].is_a?(String)
63
75
  configuration_hash[key] = configuration_hash[key] != "false"
@@ -36,9 +36,11 @@ module ActiveRecord
36
36
  # to respond to `sharded?`. To implement this define the following in an
37
37
  # initializer:
38
38
  #
39
- # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
- # next unless config.key?(:vitess)
41
- # VitessConfig.new(env_name, name, config)
39
+ # ActiveSupport.on_load(:active_record_database_configurations) do
40
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
41
+ # next unless config.key?(:vitess)
42
+ # VitessConfig.new(env_name, name, config)
43
+ # end
42
44
  # end
43
45
  #
44
46
  # Note: applications must handle the condition in which custom config should be
@@ -306,4 +308,6 @@ module ActiveRecord
306
308
  url
307
309
  end
308
310
  end
311
+
312
+ ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
309
313
  end
@@ -229,7 +229,7 @@ module ActiveRecord
229
229
  # @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
230
230
  # @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
231
231
  def delegated_type(role, types:, **options)
232
- belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
232
+ belongs_to role, options.delete(:scope), **options, polymorphic: true
233
233
  define_delegated_type_methods role, types: types, options: options
234
234
  end
235
235
 
@@ -27,7 +27,11 @@ module ActiveRecord
27
27
  records = @records
28
28
 
29
29
  records_by_id = records.group_by do |record|
30
- record[key]
30
+ if key.is_a?(Array)
31
+ key.map { |column| record[column] }
32
+ else
33
+ record[key]
34
+ end
31
35
  end
32
36
 
33
37
  records = ids.flat_map { |id| records_by_id[id] }