activerecord 7.0.8.4 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1540 -1458
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +3 -3
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +46 -10
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. data/lib/active_record/null_relation.rb +0 -63
@@ -5,6 +5,8 @@ require "concurrent/map"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
+ # = Active Record Connection Handler
9
+ #
8
10
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
9
11
  # for keeping separate connection pools that connect to different databases.
10
12
  #
@@ -56,7 +58,7 @@ module ActiveRecord
56
58
  FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
57
59
  private_constant :FINALIZER
58
60
 
59
- class StringConnectionOwner # :nodoc:
61
+ class StringConnectionName # :nodoc:
60
62
  attr_reader :name
61
63
 
62
64
  def initialize(name)
@@ -73,8 +75,8 @@ module ActiveRecord
73
75
  end
74
76
 
75
77
  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
+ # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
79
+ @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
78
80
 
79
81
  # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
80
82
  ObjectSpace.define_finalizer self, FINALIZER
@@ -88,121 +90,154 @@ module ActiveRecord
88
90
  ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
89
91
  end
90
92
 
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
93
  def connection_pool_names # :nodoc:
114
- owner_to_pool_manager.keys
94
+ connection_name_to_pool_manager.keys
115
95
  end
116
96
 
117
97
  def all_connection_pools
118
- owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
98
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
99
+ The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
100
+ Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
101
+ MSG
102
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
119
103
  end
120
104
 
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) }
105
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
106
+ # all pools belonging to the connection handler will be returned.
107
+ def connection_pool_list(role = nil)
108
+ if role.nil?
109
+ deprecation_for_pool_handling(__method__)
110
+ role = ActiveRecord::Base.current_role
111
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
112
+ elsif role == :all
113
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
114
+ else
115
+ connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
116
+ end
123
117
  end
124
118
  alias :connection_pools :connection_pool_list
125
119
 
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)
120
+ def each_connection_pool(role = nil, &block) # :nodoc:
121
+ role = nil if role == :all
122
+ return enum_for(__method__, role) unless block_given?
123
+
124
+ connection_name_to_pool_manager.each_value do |manager|
125
+ manager.each_pool_config(role) do |pool_config|
126
+ yield pool_config.pool
127
+ end
128
+ end
129
+ end
130
+
131
+ def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
132
+ owner_name = determine_owner_name(owner_name, config)
128
133
 
129
134
  pool_config = resolve_pool_config(config, owner_name, role, shard)
130
135
  db_config = pool_config.db_config
131
136
 
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
+ pool_manager = set_pool_manager(pool_config.connection_name)
137
138
 
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
139
+ # If there is an existing pool with the same values as the pool_config
140
+ # don't remove the connection. Connections should only be removed if we are
141
+ # establishing a connection on a class that is already connected to a different
142
+ # configuration.
143
+ existing_pool_config = pool_manager.get_pool_config(role, shard)
145
144
 
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)
145
+ if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
146
+ # Update the pool_config's connection class if it differs. This is used
147
+ # for ensuring that ActiveRecord::Base and the primary_abstract_class use
148
+ # the same pool. Without this granular swapping will not work correctly.
149
+ if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
150
+ existing_pool_config.connection_class = owner_name
151
+ end
153
152
 
154
- message_bus.instrument("!connection.active_record", payload) do
155
- pool_config.pool
153
+ existing_pool_config.pool
154
+ else
155
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
156
+ pool_manager.set_pool_config(role, shard, pool_config)
157
+
158
+ payload = {
159
+ connection_name: pool_config.connection_name,
160
+ role: role,
161
+ shard: shard,
162
+ config: db_config.configuration_hash
163
+ }
164
+
165
+ ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
166
+ pool_config.pool
167
+ end
156
168
  end
157
169
  end
158
170
 
159
171
  # Returns true if there are any active connections among the connection
160
172
  # pools that the ConnectionHandler is managing.
161
- def active_connections?(role = ActiveRecord::Base.current_role)
162
- connection_pool_list(role).any?(&:active_connection?)
173
+ def active_connections?(role = nil)
174
+ if role.nil?
175
+ deprecation_for_pool_handling(__method__)
176
+ role = ActiveRecord::Base.current_role
177
+ end
178
+
179
+ each_connection_pool(role).any?(&:active_connection?)
163
180
  end
164
181
 
165
182
  # Returns any connections in use by the current thread back to the pool,
166
183
  # and also returns connections to the pool cached by threads that are no
167
184
  # longer alive.
168
- def clear_active_connections!(role = ActiveRecord::Base.current_role)
169
- connection_pool_list(role).each(&:release_connection)
185
+ def clear_active_connections!(role = nil)
186
+ if role.nil?
187
+ deprecation_for_pool_handling(__method__)
188
+ role = ActiveRecord::Base.current_role
189
+ end
190
+
191
+ each_connection_pool(role).each(&:release_connection)
170
192
  end
171
193
 
172
194
  # Clears the cache which maps classes.
173
195
  #
174
196
  # 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!)
197
+ def clear_reloadable_connections!(role = nil)
198
+ if role.nil?
199
+ deprecation_for_pool_handling(__method__)
200
+ role = ActiveRecord::Base.current_role
201
+ end
202
+
203
+ each_connection_pool(role).each(&:clear_reloadable_connections!)
177
204
  end
178
205
 
179
- def clear_all_connections!(role = ActiveRecord::Base.current_role)
180
- connection_pool_list(role).each(&:disconnect!)
206
+ def clear_all_connections!(role = nil)
207
+ if role.nil?
208
+ deprecation_for_pool_handling(__method__)
209
+ role = ActiveRecord::Base.current_role
210
+ end
211
+
212
+ each_connection_pool(role).each(&:disconnect!)
181
213
  end
182
214
 
183
215
  # Disconnects all currently idle connections.
184
216
  #
185
217
  # See ConnectionPool#flush! for details.
186
- def flush_idle_connections!(role = ActiveRecord::Base.current_role)
187
- connection_pool_list(role).each(&:flush!)
218
+ def flush_idle_connections!(role = nil)
219
+ if role.nil?
220
+ deprecation_for_pool_handling(__method__)
221
+ role = ActiveRecord::Base.current_role
222
+ end
223
+
224
+ each_connection_pool(role).each(&:flush!)
188
225
  end
189
226
 
190
227
  # Locate the connection of the nearest super class. This can be an
191
228
  # active or defined connection: if it is the latter, it will be
192
229
  # opened and set as the active connection for the class it was defined
193
230
  # 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)
231
+ def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
232
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
196
233
 
197
234
  unless pool
198
235
  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."
236
+ message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
202
237
  elsif role != ActiveRecord::Base.default_role
203
- message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
238
+ message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
204
239
  else
205
- message = "No connection pool for '#{spec_name}' found."
240
+ message = "No connection pool for '#{connection_name}' found."
206
241
  end
207
242
 
208
243
  raise ConnectionNotEstablished, message
@@ -213,36 +248,66 @@ module ActiveRecord
213
248
 
214
249
  # Returns true if a connection that's accessible to this class has
215
250
  # 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)
251
+ def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
252
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
218
253
  pool && pool.connected?
219
254
  end
220
255
 
221
- def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
222
- if pool_manager = get_pool_manager(owner)
223
- pool_config = pool_manager.remove_pool_config(role, shard)
224
-
225
- if pool_config
226
- pool_config.disconnect!
227
- pool_config.db_config
228
- end
256
+ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
257
+ if pool_manager = get_pool_manager(connection_name)
258
+ disconnect_pool_from_pool_manager(pool_manager, role, shard)
229
259
  end
230
260
  end
231
261
 
232
- # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
262
+ # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
233
263
  # This makes retrieving the connection pool O(1) once the process is warm.
234
264
  # When a connection is established or removed, we invalidate the cache.
235
- def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
236
- pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
265
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
266
+ pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
237
267
  pool_config&.pool
238
268
  end
239
269
 
240
270
  private
241
- attr_reader :owner_to_pool_manager
271
+ attr_reader :connection_name_to_pool_manager
242
272
 
243
- # Returns the pool manager for an owner.
244
- def get_pool_manager(owner)
245
- owner_to_pool_manager[owner]
273
+ # Returns the pool manager for a connection name / identifier.
274
+ def get_pool_manager(connection_name)
275
+ connection_name_to_pool_manager[connection_name]
276
+ end
277
+
278
+ # Get the existing pool manager or initialize and assign a new one.
279
+ def set_pool_manager(connection_name)
280
+ connection_name_to_pool_manager[connection_name] ||= PoolManager.new
281
+ end
282
+
283
+ def pool_managers
284
+ connection_name_to_pool_manager.values
285
+ end
286
+
287
+ def deprecation_for_pool_handling(method)
288
+ roles = []
289
+ pool_managers.each do |pool_manager|
290
+ roles << pool_manager.role_names
291
+ end
292
+
293
+ if roles.flatten.uniq.count > 1
294
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
295
+ `#{method}` currently only applies to connection pools in the current
296
+ role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
297
+ will apply to all known pools, regardless of role. To affect only those
298
+ connections belonging to a specific role, pass the role name as an
299
+ argument. To switch to the new behavior, pass `:all` as the role name.
300
+ MSG
301
+ end
302
+ end
303
+
304
+ def disconnect_pool_from_pool_manager(pool_manager, role, shard)
305
+ pool_config = pool_manager.remove_pool_config(role, shard)
306
+
307
+ if pool_config
308
+ pool_config.disconnect!
309
+ pool_config.db_config
310
+ end
246
311
  end
247
312
 
248
313
  # Returns an instance of PoolConfig for a given adapter.
@@ -255,7 +320,7 @@ module ActiveRecord
255
320
  # pool_config.db_config.configuration_hash
256
321
  # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
257
322
  #
258
- def resolve_pool_config(config, owner_name, role, shard)
323
+ def resolve_pool_config(config, connection_name, role, shard)
259
324
  db_config = Base.configurations.resolve(config)
260
325
 
261
326
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
@@ -285,7 +350,17 @@ module ActiveRecord
285
350
  raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
286
351
  end
287
352
 
288
- ConnectionAdapters::PoolConfig.new(owner_name, db_config, role, shard)
353
+ ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
354
+ end
355
+
356
+ def determine_owner_name(owner_name, config)
357
+ if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
358
+ StringConnectionName.new(owner_name.to_s)
359
+ elsif config.is_a?(Symbol)
360
+ StringConnectionName.new(config.to_s)
361
+ else
362
+ owner_name
363
+ end
289
364
  end
290
365
  end
291
366
  end
@@ -6,6 +6,8 @@ require "monitor"
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  class ConnectionPool
9
+ # = Active Record Connection Pool \Queue
10
+ #
9
11
  # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
10
12
  # with which it shares a Monitor.
11
13
  class Queue
@@ -6,12 +6,14 @@ require "weakref"
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  class ConnectionPool
9
+ # = Active Record Connection Pool \Reaper
10
+ #
9
11
  # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
10
12
  # +pool+. A reaper instantiated with a zero frequency will never reap
11
13
  # the connection pool.
12
14
  #
13
15
  # Configure the frequency by setting +reaping_frequency+ in your database
14
- # yaml file (default 60 seconds).
16
+ # YAML file (default 60 seconds).
15
17
  class Reaper
16
18
  attr_reader :pool, :frequency
17
19
 
@@ -10,35 +10,33 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
10
10
  module ActiveRecord
11
11
  module ConnectionAdapters
12
12
  module AbstractPool # :nodoc:
13
- def get_schema_cache(connection)
14
- self.schema_cache ||= SchemaCache.new(connection)
15
- schema_cache.connection = connection
16
- schema_cache
17
- end
18
-
19
- def set_schema_cache(cache)
20
- self.schema_cache = cache
21
- end
22
-
23
- def lazily_set_schema_cache
24
- return unless ActiveRecord.lazily_load_schema_cache
25
-
26
- cache = SchemaCache.load_from(db_config.lazy_schema_cache_path)
27
- set_schema_cache(cache)
28
- end
29
13
  end
30
14
 
31
15
  class NullPool # :nodoc:
32
16
  include ConnectionAdapters::AbstractPool
33
17
 
34
- attr_accessor :schema_cache
18
+ class NullConfig # :nodoc:
19
+ def method_missing(*)
20
+ nil
21
+ end
22
+ end
23
+ NULL_CONFIG = NullConfig.new # :nodoc:
24
+
25
+ def schema_reflection
26
+ SchemaReflection.new(nil)
27
+ end
35
28
 
36
29
  def connection_class; end
37
30
  def checkin(_); end
38
31
  def remove(_); end
39
32
  def async_executor; end
33
+ def db_config
34
+ NULL_CONFIG
35
+ end
40
36
  end
41
37
 
38
+ # = Active Record Connection Pool
39
+ #
42
40
  # Connection pool base class for managing Active Record database
43
41
  # connections.
44
42
  #
@@ -53,19 +51,17 @@ module ActiveRecord
53
51
  # handle cases in which there are more threads than connections: if all
54
52
  # connections have been checked out, and a thread tries to checkout a
55
53
  # connection anyway, then ConnectionPool will wait until some other thread
56
- # has checked in a connection.
54
+ # has checked in a connection, or the +checkout_timeout+ has expired.
57
55
  #
58
56
  # == Obtaining (checking out) a connection
59
57
  #
60
58
  # Connections can be obtained and used from a connection pool in several
61
59
  # ways:
62
60
  #
63
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
64
- # as with Active Record 2.1 and
65
- # earlier (pre-connection-pooling). Eventually, when you're done with
66
- # the connection(s) and wish it to be returned to the pool, you call
67
- # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
68
- # This will be the default behavior for Active Record when used in conjunction with
61
+ # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection].
62
+ # When you're done with the connection(s) and wish it to be returned to the pool, you call
63
+ # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
64
+ # This is the default behavior for Active Record when used in conjunction with
69
65
  # Action Pack's request handling cycle.
70
66
  # 2. Manually check out a connection from the pool with
71
67
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -78,6 +74,12 @@ module ActiveRecord
78
74
  # Connections in the pool are actually AbstractAdapter objects (or objects
79
75
  # compatible with AbstractAdapter's interface).
80
76
  #
77
+ # While a thread has a connection checked out from the pool using one of the
78
+ # above three methods, that connection will automatically be the one used
79
+ # by ActiveRecord queries executing on that thread. It is not required to
80
+ # explicitly pass the checked out connection to \Rails models or queries, for
81
+ # example.
82
+ #
81
83
  # == Options
82
84
  #
83
85
  # There are several connection-pooling-related options that you can add to
@@ -105,11 +107,9 @@ module ActiveRecord
105
107
  include ConnectionAdapters::AbstractPool
106
108
 
107
109
  attr_accessor :automatic_reconnect, :checkout_timeout
108
- attr_reader :db_config, :size, :reaper, :pool_config, :connection_class, :async_executor, :role, :shard
110
+ attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
109
111
 
110
- alias_method :connection_klass, :connection_class
111
- deprecate :connection_klass
112
- delegate :schema_cache, :schema_cache=, to: :pool_config
112
+ delegate :schema_reflection, :schema_reflection=, to: :pool_config
113
113
 
114
114
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
115
  # object which describes database connection information (e.g. adapter,
@@ -122,7 +122,6 @@ module ActiveRecord
122
122
 
123
123
  @pool_config = pool_config
124
124
  @db_config = pool_config.db_config
125
- @connection_class = pool_config.connection_class
126
125
  @role = pool_config.role
127
126
  @shard = pool_config.shard
128
127
 
@@ -158,18 +157,20 @@ module ActiveRecord
158
157
 
159
158
  @async_executor = build_async_executor
160
159
 
161
- lazily_set_schema_cache
162
-
163
160
  @reaper = Reaper.new(self, db_config.reaping_frequency)
164
161
  @reaper.run
165
162
  end
166
163
 
167
164
  def lock_thread=(lock_thread)
168
165
  if lock_thread
169
- @lock_thread = Thread.current
166
+ @lock_thread = ActiveSupport::IsolatedExecutionState.context
170
167
  else
171
168
  @lock_thread = nil
172
169
  end
170
+
171
+ if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
+ active_connection.lock_thread = @lock_thread
173
+ end
173
174
  end
174
175
 
175
176
  # Retrieve the connection associated with the current thread, or call
@@ -181,6 +182,12 @@ module ActiveRecord
181
182
  @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
182
183
  end
183
184
 
185
+ def connection_class # :nodoc:
186
+ pool_config.connection_class
187
+ end
188
+ alias :connection_klass :connection_class
189
+ deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
+
184
191
  # Returns true if there is an open connection being used for the current thread.
185
192
  #
186
193
  # This method only works for connections that have been obtained through
@@ -197,18 +204,23 @@ module ActiveRecord
197
204
  # This method only works for connections that have been obtained through
198
205
  # #connection or #with_connection methods, connections obtained through
199
206
  # #checkout will not be automatically released.
200
- def release_connection(owner_thread = Thread.current)
207
+ def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.context)
201
208
  if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
202
209
  checkin conn
203
210
  end
204
211
  end
205
212
 
206
- # If a connection obtained through #connection or #with_connection methods
207
- # already exists yield it to the block. If no such connection
208
- # exists checkout a connection, yield it to the block, and checkin the
209
- # connection when finished.
213
+ # Yields a connection from the connection pool to the block. If no connection
214
+ # is already checked out by the current thread, a connection will be checked
215
+ # out from the pool, yielded to the block, and then returned to the pool when
216
+ # the block is finished. If a connection has already been checked out on the
217
+ # current thread, such as via #connection or #with_connection, that existing
218
+ # connection will be the one yielded and it will not be returned to the pool
219
+ # automatically at the end of the block; it is expected that such an existing
220
+ # connection will be properly returned to the pool by the code that checked
221
+ # it out.
210
222
  def with_connection
211
- unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
223
+ unless conn = @thread_cached_conns[connection_cache_key(ActiveSupport::IsolatedExecutionState.context)]
212
224
  conn = connection
213
225
  fresh_connection = true
214
226
  end
@@ -338,7 +350,9 @@ module ActiveRecord
338
350
  # Raises:
339
351
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
340
352
  def checkout(checkout_timeout = @checkout_timeout)
341
- checkout_and_verify(acquire_connection(checkout_timeout))
353
+ connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
+ connection.lock_thread = @lock_thread
355
+ connection
342
356
  end
343
357
 
344
358
  # Check-in a database connection back into the pool, indicating that you
@@ -355,6 +369,7 @@ module ActiveRecord
355
369
  conn.expire
356
370
  end
357
371
 
372
+ conn.lock_thread = nil
358
373
  @available.add conn
359
374
  end
360
375
  end
@@ -448,8 +463,7 @@ module ActiveRecord
448
463
  @available.num_waiting
449
464
  end
450
465
 
451
- # Return connection pool's usage statistic
452
- # Example:
466
+ # Returns the connection pool's usage statistic.
453
467
  #
454
468
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
455
469
  def stat
@@ -510,7 +524,7 @@ module ActiveRecord
510
524
  end
511
525
 
512
526
  def current_thread
513
- @lock_thread || Thread.current
527
+ @lock_thread || ActiveSupport::IsolatedExecutionState.context
514
528
  end
515
529
 
516
530
  # Take control of all existing connections so a "group" action such as
@@ -527,13 +541,13 @@ module ActiveRecord
527
541
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
528
542
  collected_conns = synchronize do
529
543
  # account for our own connections
530
- @connections.select { |conn| conn.owner == Thread.current }
544
+ @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
531
545
  end
532
546
 
533
547
  newly_checked_out = []
534
548
  timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
535
549
 
536
- @available.with_a_bias_for(Thread.current) do
550
+ @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
537
551
  loop do
538
552
  synchronize do
539
553
  return if collected_conns.size == @connections.size && @now_connecting == 0
@@ -580,7 +594,7 @@ module ActiveRecord
580
594
 
581
595
  thread_report = []
582
596
  @connections.each do |conn|
583
- unless conn.owner == Thread.current
597
+ unless conn.owner == ActiveSupport::IsolatedExecutionState.context
584
598
  thread_report << "#{conn} is owned by #{conn.owner}"
585
599
  end
586
600
  end
@@ -641,7 +655,13 @@ module ActiveRecord
641
655
  conn
642
656
  else
643
657
  reap
644
- @available.poll(checkout_timeout)
658
+ # Retry after reaping, which may return an available connection,
659
+ # remove an inactive connection, or both
660
+ if conn = @available.poll || try_to_checkout_new_connection
661
+ conn
662
+ else
663
+ @available.poll(checkout_timeout)
664
+ end
645
665
  end
646
666
  end
647
667
 
@@ -653,9 +673,12 @@ module ActiveRecord
653
673
  alias_method :release, :remove_connection_from_thread_cache
654
674
 
655
675
  def new_connection
656
- Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
657
- conn.check_version
658
- end
676
+ connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
677
+ connection.pool = self
678
+ connection.check_version
679
+ connection
680
+ rescue ConnectionNotEstablished => ex
681
+ raise ex.set_pool(self)
659
682
  end
660
683
 
661
684
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
@@ -702,10 +725,10 @@ module ActiveRecord
702
725
 
703
726
  def checkout_and_verify(c)
704
727
  c._run_checkout_callbacks do
705
- c.verify!
728
+ c.clean!
706
729
  end
707
730
  c
708
- rescue
731
+ rescue Exception
709
732
  remove c
710
733
  c.disconnect!
711
734
  raise