activerecord 6.1.3.2 → 7.0.0.alpha2

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -1058
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +35 -7
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +16 -6
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +24 -25
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +11 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  67. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  82. data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
  83. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  87. data/lib/active_record/connection_adapters.rb +8 -5
  88. data/lib/active_record/connection_handling.rb +20 -38
  89. data/lib/active_record/core.rb +129 -117
  90. data/lib/active_record/database_configurations/database_config.rb +12 -0
  91. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  92. data/lib/active_record/database_configurations/url_config.rb +2 -2
  93. data/lib/active_record/database_configurations.rb +18 -9
  94. data/lib/active_record/delegated_type.rb +33 -11
  95. data/lib/active_record/destroy_association_async_job.rb +1 -1
  96. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  97. data/lib/active_record/dynamic_matchers.rb +1 -1
  98. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  99. data/lib/active_record/encryption/cipher.rb +53 -0
  100. data/lib/active_record/encryption/config.rb +44 -0
  101. data/lib/active_record/encryption/configurable.rb +61 -0
  102. data/lib/active_record/encryption/context.rb +35 -0
  103. data/lib/active_record/encryption/contexts.rb +72 -0
  104. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  105. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  106. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  107. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  108. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  109. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  110. data/lib/active_record/encryption/encryptor.rb +155 -0
  111. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  112. data/lib/active_record/encryption/errors.rb +15 -0
  113. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  114. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  115. data/lib/active_record/encryption/key.rb +28 -0
  116. data/lib/active_record/encryption/key_generator.rb +42 -0
  117. data/lib/active_record/encryption/key_provider.rb +46 -0
  118. data/lib/active_record/encryption/message.rb +33 -0
  119. data/lib/active_record/encryption/message_serializer.rb +80 -0
  120. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  121. data/lib/active_record/encryption/properties.rb +76 -0
  122. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  123. data/lib/active_record/encryption/scheme.rb +99 -0
  124. data/lib/active_record/encryption.rb +55 -0
  125. data/lib/active_record/enum.rb +44 -46
  126. data/lib/active_record/errors.rb +66 -3
  127. data/lib/active_record/fixture_set/file.rb +15 -1
  128. data/lib/active_record/fixture_set/table_row.rb +40 -5
  129. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  130. data/lib/active_record/fixtures.rb +16 -11
  131. data/lib/active_record/future_result.rb +139 -0
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +55 -17
  134. data/lib/active_record/insert_all.rb +39 -6
  135. data/lib/active_record/integration.rb +1 -1
  136. data/lib/active_record/internal_metadata.rb +3 -5
  137. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  138. data/lib/active_record/locking/optimistic.rb +10 -9
  139. data/lib/active_record/log_subscriber.rb +6 -2
  140. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  141. data/lib/active_record/middleware/database_selector.rb +8 -3
  142. data/lib/active_record/migration/command_recorder.rb +4 -4
  143. data/lib/active_record/migration/compatibility.rb +83 -1
  144. data/lib/active_record/migration/join_table.rb +1 -1
  145. data/lib/active_record/migration.rb +109 -79
  146. data/lib/active_record/model_schema.rb +46 -32
  147. data/lib/active_record/nested_attributes.rb +3 -3
  148. data/lib/active_record/no_touching.rb +2 -2
  149. data/lib/active_record/null_relation.rb +2 -6
  150. data/lib/active_record/persistence.rb +134 -45
  151. data/lib/active_record/query_cache.rb +2 -2
  152. data/lib/active_record/query_logs.rb +203 -0
  153. data/lib/active_record/querying.rb +15 -5
  154. data/lib/active_record/railtie.rb +117 -17
  155. data/lib/active_record/railties/controller_runtime.rb +1 -1
  156. data/lib/active_record/railties/databases.rake +83 -58
  157. data/lib/active_record/readonly_attributes.rb +11 -0
  158. data/lib/active_record/reflection.rb +45 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  160. data/lib/active_record/relation/batches.rb +3 -3
  161. data/lib/active_record/relation/calculations.rb +42 -25
  162. data/lib/active_record/relation/delegation.rb +6 -6
  163. data/lib/active_record/relation/finder_methods.rb +32 -23
  164. data/lib/active_record/relation/merger.rb +20 -13
  165. data/lib/active_record/relation/predicate_builder.rb +1 -6
  166. data/lib/active_record/relation/query_attribute.rb +5 -11
  167. data/lib/active_record/relation/query_methods.rb +233 -50
  168. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  169. data/lib/active_record/relation/spawn_methods.rb +2 -2
  170. data/lib/active_record/relation/where_clause.rb +22 -15
  171. data/lib/active_record/relation.rb +170 -87
  172. data/lib/active_record/result.rb +17 -2
  173. data/lib/active_record/runtime_registry.rb +2 -4
  174. data/lib/active_record/sanitization.rb +11 -7
  175. data/lib/active_record/schema_dumper.rb +3 -3
  176. data/lib/active_record/schema_migration.rb +0 -4
  177. data/lib/active_record/scoping/default.rb +62 -15
  178. data/lib/active_record/scoping/named.rb +3 -11
  179. data/lib/active_record/scoping.rb +40 -22
  180. data/lib/active_record/serialization.rb +1 -1
  181. data/lib/active_record/signed_id.rb +1 -1
  182. data/lib/active_record/statement_cache.rb +2 -2
  183. data/lib/active_record/tasks/database_tasks.rb +107 -23
  184. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  185. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  186. data/lib/active_record/test_databases.rb +1 -1
  187. data/lib/active_record/test_fixtures.rb +45 -4
  188. data/lib/active_record/timestamp.rb +3 -4
  189. data/lib/active_record/transactions.rb +9 -14
  190. data/lib/active_record/translation.rb +2 -2
  191. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  192. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  193. data/lib/active_record/type/internal/timezone.rb +2 -2
  194. data/lib/active_record/type/serialized.rb +1 -1
  195. data/lib/active_record/type/type_map.rb +17 -20
  196. data/lib/active_record/type.rb +1 -2
  197. data/lib/active_record/validations/associated.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +1 -1
  199. data/lib/active_record.rb +170 -2
  200. data/lib/arel/attributes/attribute.rb +0 -8
  201. data/lib/arel/collectors/bind.rb +2 -2
  202. data/lib/arel/collectors/composite.rb +3 -3
  203. data/lib/arel/collectors/sql_string.rb +1 -1
  204. data/lib/arel/collectors/substitute_binds.rb +1 -1
  205. data/lib/arel/crud.rb +18 -22
  206. data/lib/arel/delete_manager.rb +2 -4
  207. data/lib/arel/insert_manager.rb +2 -3
  208. data/lib/arel/nodes/casted.rb +1 -1
  209. data/lib/arel/nodes/delete_statement.rb +8 -13
  210. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  211. data/lib/arel/nodes/insert_statement.rb +2 -2
  212. data/lib/arel/nodes/select_core.rb +2 -2
  213. data/lib/arel/nodes/select_statement.rb +2 -2
  214. data/lib/arel/nodes/update_statement.rb +3 -2
  215. data/lib/arel/predications.rb +3 -3
  216. data/lib/arel/select_manager.rb +10 -4
  217. data/lib/arel/table.rb +0 -1
  218. data/lib/arel/tree_manager.rb +0 -12
  219. data/lib/arel/update_manager.rb +2 -4
  220. data/lib/arel/visitors/dot.rb +80 -90
  221. data/lib/arel/visitors/mysql.rb +6 -1
  222. data/lib/arel/visitors/postgresql.rb +0 -10
  223. data/lib/arel/visitors/to_sql.rb +44 -3
  224. data/lib/arel.rb +1 -1
  225. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  227. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  228. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  229. metadata +55 -16
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  module AutosaveAssociation
139
139
  extend ActiveSupport::Concern
140
140
 
141
- module AssociationBuilderExtension #:nodoc:
141
+ module AssociationBuilderExtension # :nodoc:
142
142
  def self.build(model, reflection)
143
143
  model.send(:add_autosave_association_callbacks, reflection)
144
144
  end
@@ -150,25 +150,10 @@ module ActiveRecord
150
150
 
151
151
  included do
152
152
  Associations::Builder::Association.extensions << AssociationBuilderExtension
153
- mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
154
153
  end
155
154
 
156
155
  module ClassMethods # :nodoc:
157
156
  private
158
- if Module.method(:method_defined?).arity == 1 # MRI 2.5 and older
159
- using Module.new {
160
- refine Module do
161
- def method_defined?(method, inherit = true)
162
- if inherit
163
- super(method)
164
- else
165
- instance_methods(false).include?(method.to_sym)
166
- end
167
- end
168
- end
169
- }
170
- end
171
-
172
157
  def define_non_cyclic_method(name, &block)
173
158
  return if method_defined?(name, false)
174
159
 
@@ -210,7 +195,7 @@ module ActiveRecord
210
195
  after_create save_method
211
196
  after_update save_method
212
197
  elsif reflection.has_one?
213
- define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
198
+ define_non_cyclic_method(save_method) { save_has_one_association(reflection) }
214
199
  # Configures two callbacks instead of a single after_save so that
215
200
  # the model may rely on their execution order relative to its
216
201
  # own callbacks.
@@ -349,7 +334,7 @@ module ActiveRecord
349
334
 
350
335
  unless valid = record.valid?(context)
351
336
  if reflection.options[:autosave]
352
- indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
337
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord.index_nested_attribute_errors)
353
338
 
354
339
  record.errors.group_by_attribute.each { |attribute, errors|
355
340
  attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
@@ -12,7 +12,7 @@ require "active_record/attributes"
12
12
  require "active_record/type_caster"
13
13
  require "active_record/database_configurations"
14
14
 
15
- module ActiveRecord #:nodoc:
15
+ module ActiveRecord # :nodoc:
16
16
  # = Active Record
17
17
  #
18
18
  # Active Record objects don't specify their attributes directly, but rather infer them from
@@ -137,6 +137,23 @@ module ActiveRecord #:nodoc:
137
137
  # anonymous = User.new(name: "")
138
138
  # anonymous.name? # => false
139
139
  #
140
+ # Query methods will also respect any overwrites of default accessors:
141
+ #
142
+ # class User
143
+ # # Has admin boolean column
144
+ # def admin
145
+ # false
146
+ # end
147
+ # end
148
+ #
149
+ # user.update(admin: true)
150
+ #
151
+ # user.read_attribute(:admin) # => true, gets the column value
152
+ # user[:admin] # => true, also gets the column value
153
+ #
154
+ # user.admin # => false, due to the getter overwrite
155
+ # user.admin? # => false, due to the getter overwrite
156
+ #
140
157
  # == Accessing attributes before they have been typecasted
141
158
  #
142
159
  # Sometimes you want to be able to read the raw attribute data without having the column-determined
@@ -310,6 +327,7 @@ module ActiveRecord #:nodoc:
310
327
  include SecureToken
311
328
  include SignedId
312
329
  include Suppressor
330
+ include Encryption::EncryptableRecord
313
331
  end
314
332
 
315
333
  ActiveSupport.run_load_hooks(:active_record, Base)
@@ -432,7 +432,7 @@ module ActiveRecord
432
432
  define_model_callbacks :save, :create, :update, :destroy
433
433
  end
434
434
 
435
- def destroy #:nodoc:
435
+ def destroy # :nodoc:
436
436
  @_destroy_callback_already_called ||= false
437
437
  return if @_destroy_callback_already_called
438
438
  @_destroy_callback_already_called = true
@@ -444,7 +444,7 @@ module ActiveRecord
444
444
  @_destroy_callback_already_called = false
445
445
  end
446
446
 
447
- def touch(*, **) #:nodoc:
447
+ def touch(*, **) # :nodoc:
448
448
  _run_touch_callbacks { super }
449
449
  end
450
450
 
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  def load(yaml)
24
24
  return object_class.new if object_class != Object && yaml.nil?
25
25
  return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
26
- obj = YAML.load(yaml)
26
+ obj = yaml_load(yaml)
27
27
 
28
28
  assert_valid_value(obj, action: "load")
29
29
  obj ||= object_class.new if object_class != Object
@@ -44,6 +44,16 @@ module ActiveRecord
44
44
  rescue ArgumentError
45
45
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
46
  end
47
+
48
+ if YAML.respond_to?(:unsafe_load)
49
+ def yaml_load(payload)
50
+ YAML.unsafe_load(payload)
51
+ end
52
+ else
53
+ def yaml_load(payload)
54
+ YAML.load(payload)
55
+ end
56
+ end
47
57
  end
48
58
  end
49
59
  end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
9
+ # for keeping separate connection pools that connect to different databases.
10
+ #
11
+ # For example, suppose that you have 5 models, with the following hierarchy:
12
+ #
13
+ # class Author < ActiveRecord::Base
14
+ # end
15
+ #
16
+ # class BankAccount < ActiveRecord::Base
17
+ # end
18
+ #
19
+ # class Book < ActiveRecord::Base
20
+ # establish_connection :library_db
21
+ # end
22
+ #
23
+ # class ScaryBook < Book
24
+ # end
25
+ #
26
+ # class GoodBook < Book
27
+ # end
28
+ #
29
+ # And a database.yml that looked like this:
30
+ #
31
+ # development:
32
+ # database: my_application
33
+ # host: localhost
34
+ #
35
+ # library_db:
36
+ # database: library
37
+ # host: some.library.org
38
+ #
39
+ # Your primary database in the development environment is "my_application"
40
+ # but the Book model connects to a separate database called "library_db"
41
+ # (this can even be a database on a different machine).
42
+ #
43
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
44
+ # "library_db" while Author, BankAccount, and any other models you create
45
+ # will use the default connection pool to "my_application".
46
+ #
47
+ # The various connection pools are managed by a single instance of
48
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
49
+ # All Active Record models use this handler to determine the connection pool that they
50
+ # should use.
51
+ #
52
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
53
+ # about the model. The model needs to pass a connection specification name to the handler,
54
+ # in order to look up the correct connection pool.
55
+ class ConnectionHandler
56
+ FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
57
+ private_constant :FINALIZER
58
+
59
+ class StringConnectionOwner # :nodoc:
60
+ attr_reader :name
61
+
62
+ def initialize(name)
63
+ @name = name
64
+ end
65
+
66
+ def primary_class?
67
+ false
68
+ end
69
+
70
+ def current_preventing_writes
71
+ false
72
+ end
73
+ end
74
+
75
+ def initialize
76
+ # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
77
+ @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
78
+
79
+ # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
80
+ ObjectSpace.define_finalizer self, FINALIZER
81
+ end
82
+
83
+ def prevent_writes # :nodoc:
84
+ Thread.current[:prevent_writes]
85
+ end
86
+
87
+ def prevent_writes=(prevent_writes) # :nodoc:
88
+ Thread.current[:prevent_writes] = prevent_writes
89
+ end
90
+
91
+ # Prevent writing to the database regardless of role.
92
+ #
93
+ # In some cases you may want to prevent writes to the database
94
+ # even if you are on a database that can write. +while_preventing_writes+
95
+ # will prevent writes to the database for the duration of the block.
96
+ #
97
+ # This method does not provide the same protection as a readonly
98
+ # user and is meant to be a safeguard against accidental writes.
99
+ #
100
+ # See +READ_QUERY+ for the queries that are blocked by this
101
+ # method.
102
+ def while_preventing_writes(enabled = true)
103
+ unless ActiveRecord.legacy_connection_handling
104
+ raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
105
+ end
106
+
107
+ original, self.prevent_writes = self.prevent_writes, enabled
108
+ yield
109
+ ensure
110
+ self.prevent_writes = original
111
+ end
112
+
113
+ def connection_pool_names # :nodoc:
114
+ owner_to_pool_manager.keys
115
+ end
116
+
117
+ def all_connection_pools
118
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
119
+ end
120
+
121
+ def connection_pool_list(role = ActiveRecord::Base.current_role)
122
+ owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
123
+ end
124
+ alias :connection_pools :connection_pool_list
125
+
126
+ def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
127
+ owner_name = StringConnectionOwner.new(config.to_s) if config.is_a?(Symbol)
128
+
129
+ pool_config = resolve_pool_config(config, owner_name)
130
+ db_config = pool_config.db_config
131
+
132
+ # Protects the connection named `ActiveRecord::Base` from being removed
133
+ # if the user calls `establish_connection :primary`.
134
+ if owner_to_pool_manager.key?(pool_config.connection_specification_name)
135
+ remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
136
+ end
137
+
138
+ message_bus = ActiveSupport::Notifications.instrumenter
139
+ payload = {}
140
+ if pool_config
141
+ payload[:spec_name] = pool_config.connection_specification_name
142
+ payload[:shard] = shard
143
+ payload[:config] = db_config.configuration_hash
144
+ end
145
+
146
+ if ActiveRecord.legacy_connection_handling
147
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
148
+ else
149
+ owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
150
+ end
151
+ pool_manager = get_pool_manager(pool_config.connection_specification_name)
152
+ pool_manager.set_pool_config(role, shard, pool_config)
153
+
154
+ message_bus.instrument("!connection.active_record", payload) do
155
+ pool_config.pool
156
+ end
157
+ end
158
+
159
+ # Returns true if there are any active connections among the connection
160
+ # pools that the ConnectionHandler is managing.
161
+ def active_connections?(role = ActiveRecord::Base.current_role)
162
+ connection_pool_list(role).any?(&:active_connection?)
163
+ end
164
+
165
+ # Returns any connections in use by the current thread back to the pool,
166
+ # and also returns connections to the pool cached by threads that are no
167
+ # longer alive.
168
+ def clear_active_connections!(role = ActiveRecord::Base.current_role)
169
+ connection_pool_list(role).each(&:release_connection)
170
+ end
171
+
172
+ # Clears the cache which maps classes.
173
+ #
174
+ # See ConnectionPool#clear_reloadable_connections! for details.
175
+ def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
176
+ connection_pool_list(role).each(&:clear_reloadable_connections!)
177
+ end
178
+
179
+ def clear_all_connections!(role = ActiveRecord::Base.current_role)
180
+ connection_pool_list(role).each(&:disconnect!)
181
+ end
182
+
183
+ # Disconnects all currently idle connections.
184
+ #
185
+ # See ConnectionPool#flush! for details.
186
+ def flush_idle_connections!(role = ActiveRecord::Base.current_role)
187
+ connection_pool_list(role).each(&:flush!)
188
+ end
189
+
190
+ # Locate the connection of the nearest super class. This can be an
191
+ # active or defined connection: if it is the latter, it will be
192
+ # opened and set as the active connection for the class it was defined
193
+ # for (not necessarily the current class).
194
+ def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
195
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
196
+
197
+ unless pool
198
+ if shard != ActiveRecord::Base.default_shard
199
+ message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
200
+ elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
201
+ message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
202
+ elsif role != ActiveRecord::Base.default_role
203
+ message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
204
+ else
205
+ message = "No connection pool for '#{spec_name}' found."
206
+ end
207
+
208
+ raise ConnectionNotEstablished, message
209
+ end
210
+
211
+ pool.connection
212
+ end
213
+
214
+ # Returns true if a connection that's accessible to this class has
215
+ # already been opened.
216
+ def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
217
+ pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
218
+ pool && pool.connected?
219
+ end
220
+
221
+ # Remove the connection for this class. This will close the active
222
+ # connection and the defined connection (if they exist). The result
223
+ # can be used as an argument for #establish_connection, for easily
224
+ # re-establishing the connection.
225
+ def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
226
+ remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash
227
+ end
228
+ deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
229
+
230
+ def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
231
+ if pool_manager = get_pool_manager(owner)
232
+ pool_config = pool_manager.remove_pool_config(role, shard)
233
+
234
+ if pool_config
235
+ pool_config.disconnect!
236
+ pool_config.db_config
237
+ end
238
+ end
239
+ end
240
+
241
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
242
+ # This makes retrieving the connection pool O(1) once the process is warm.
243
+ # When a connection is established or removed, we invalidate the cache.
244
+ def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
245
+ pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
246
+ pool_config&.pool
247
+ end
248
+
249
+ private
250
+ attr_reader :owner_to_pool_manager
251
+
252
+ # Returns the pool manager for an owner.
253
+ #
254
+ # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
255
+ # deprecated in favor of looking it up by `"ActiveRecord::Base"`.
256
+ #
257
+ # During the deprecation period, if `"primary"` is passed, the pool manager
258
+ # for `ActiveRecord::Base` will still be returned.
259
+ def get_pool_manager(owner)
260
+ return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
261
+
262
+ if owner == "primary"
263
+ ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 7.0.0. Please use `ActiveRecord::Base`.")
264
+ owner_to_pool_manager[Base.name]
265
+ end
266
+ end
267
+
268
+ # Returns an instance of PoolConfig for a given adapter.
269
+ # Accepts a hash one layer deep that contains all connection information.
270
+ #
271
+ # == Example
272
+ #
273
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
274
+ # pool_config = Base.configurations.resolve_pool_config(:production)
275
+ # pool_config.db_config.configuration_hash
276
+ # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
277
+ #
278
+ def resolve_pool_config(config, owner_name)
279
+ db_config = Base.configurations.resolve(config)
280
+
281
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
282
+
283
+ # Require the adapter itself and give useful feedback about
284
+ # 1. Missing adapter gems and
285
+ # 2. Adapter gems' missing dependencies.
286
+ path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
287
+ begin
288
+ require path_to_adapter
289
+ rescue LoadError => e
290
+ # We couldn't require the adapter itself. Raise an exception that
291
+ # points out config typos and missing gems.
292
+ if e.path == path_to_adapter
293
+ # We can assume that a non-builtin adapter was specified, so it's
294
+ # either misspelled or missing from Gemfile.
295
+ raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
296
+
297
+ # Bubbled up from the adapter require. Prefix the exception message
298
+ # with some guidance about how to address it and reraise.
299
+ else
300
+ raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
301
+ end
302
+ end
303
+
304
+ unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
305
+ raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
306
+ end
307
+
308
+ ConnectionAdapters::PoolConfig.new(owner_name, db_config)
309
+ end
310
+ end
311
+ end
312
+ end