activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +16 -8
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -16,23 +16,40 @@ module ActiveRecord
16
16
  include ConnectionAdapters::AbstractPool
17
17
 
18
18
  class NullConfig # :nodoc:
19
- def method_missing(*)
19
+ def method_missing(...)
20
20
  nil
21
21
  end
22
22
  end
23
23
  NULL_CONFIG = NullConfig.new # :nodoc:
24
24
 
25
+ def initialize
26
+ super()
27
+ @mutex = Mutex.new
28
+ @server_version = nil
29
+ end
30
+
31
+ def server_version(connection) # :nodoc:
32
+ @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
33
+ end
34
+
25
35
  def schema_reflection
26
36
  SchemaReflection.new(nil)
27
37
  end
28
38
 
39
+ def schema_cache; end
29
40
  def connection_class; end
41
+ def query_cache; end
30
42
  def checkin(_); end
31
43
  def remove(_); end
32
44
  def async_executor; end
45
+
33
46
  def db_config
34
47
  NULL_CONFIG
35
48
  end
49
+
50
+ def dirties_query_cache
51
+ true
52
+ end
36
53
  end
37
54
 
38
55
  # = Active Record Connection Pool
@@ -58,7 +75,7 @@ module ActiveRecord
58
75
  # Connections can be obtained and used from a connection pool in several
59
76
  # ways:
60
77
  #
61
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection].
78
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
62
79
  # When you're done with the connection(s) and wish it to be returned to the pool, you call
63
80
  # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
64
81
  # This is the default behavior for Active Record when used in conjunction with
@@ -102,14 +119,111 @@ module ActiveRecord
102
119
  # * private methods that require being called in a +synchronize+ blocks
103
120
  # are now explicitly documented
104
121
  class ConnectionPool
122
+ # Prior to 3.3.5, WeakKeyMap had a use after free bug
123
+ # https://bugs.ruby-lang.org/issues/20688
124
+ if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
125
+ WeakThreadKeyMap = ObjectSpace::WeakKeyMap
126
+ else
127
+ class WeakThreadKeyMap # :nodoc:
128
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
129
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
130
+ def initialize
131
+ @map = {}
132
+ end
133
+
134
+ def clear
135
+ @map.clear
136
+ end
137
+
138
+ def [](key)
139
+ @map[key]
140
+ end
141
+
142
+ def []=(key, value)
143
+ @map.select! { |c, _| c&.alive? }
144
+ @map[key] = value
145
+ end
146
+ end
147
+ end
148
+
149
+ class Lease # :nodoc:
150
+ attr_accessor :connection, :sticky
151
+
152
+ def initialize
153
+ @connection = nil
154
+ @sticky = nil
155
+ end
156
+
157
+ def release
158
+ conn = @connection
159
+ @connection = nil
160
+ @sticky = nil
161
+ conn
162
+ end
163
+
164
+ def clear(connection)
165
+ if @connection == connection
166
+ @connection = nil
167
+ @sticky = nil
168
+ true
169
+ else
170
+ false
171
+ end
172
+ end
173
+ end
174
+
175
+ class LeaseRegistry # :nodoc:
176
+ def initialize
177
+ @mutex = Mutex.new
178
+ @map = WeakThreadKeyMap.new
179
+ end
180
+
181
+ def [](context)
182
+ @mutex.synchronize do
183
+ @map[context] ||= Lease.new
184
+ end
185
+ end
186
+
187
+ def clear
188
+ @mutex.synchronize do
189
+ @map.clear
190
+ end
191
+ end
192
+ end
193
+
194
+ module ExecutorHooks # :nodoc:
195
+ class << self
196
+ def run
197
+ # noop
198
+ end
199
+
200
+ def complete(_)
201
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
202
+ if (connection = pool.active_connection?)
203
+ transaction = connection.current_transaction
204
+ if transaction.closed? || !transaction.joinable?
205
+ pool.release_connection
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ class << self
214
+ def install_executor_hooks(executor = ActiveSupport::Executor)
215
+ executor.register_hook(ExecutorHooks)
216
+ end
217
+ end
218
+
105
219
  include MonitorMixin
106
- include QueryCache::ConnectionPoolConfiguration
220
+ prepend QueryCache::ConnectionPoolConfiguration
107
221
  include ConnectionAdapters::AbstractPool
108
222
 
109
223
  attr_accessor :automatic_reconnect, :checkout_timeout
110
224
  attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
111
225
 
112
- delegate :schema_reflection, :schema_reflection=, to: :pool_config
226
+ delegate :schema_reflection, :server_version, to: :pool_config
113
227
 
114
228
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
229
  # object which describes database connection information (e.g. adapter,
@@ -137,9 +251,9 @@ module ActiveRecord
137
251
  # then that +thread+ does indeed own that +conn+. However, an absence of such
138
252
  # mapping does not mean that the +thread+ doesn't own the said connection. In
139
253
  # that case +conn.owner+ attr should be consulted.
140
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
254
+ # Access and modification of <tt>@leases</tt> does not require
141
255
  # synchronization.
142
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
256
+ @leases = LeaseRegistry.new
143
257
 
144
258
  @connections = []
145
259
  @automatic_reconnect = true
@@ -152,86 +266,179 @@ module ActiveRecord
152
266
  @threads_blocking_new_connections = 0
153
267
 
154
268
  @available = ConnectionLeasingQueue.new self
155
-
156
- @lock_thread = false
269
+ @pinned_connection = nil
270
+ @pinned_connections_depth = 0
157
271
 
158
272
  @async_executor = build_async_executor
159
273
 
274
+ @schema_cache = nil
275
+
160
276
  @reaper = Reaper.new(self, db_config.reaping_frequency)
161
277
  @reaper.run
162
278
  end
163
279
 
164
- def lock_thread=(lock_thread)
165
- if lock_thread
166
- @lock_thread = ActiveSupport::IsolatedExecutionState.context
167
- else
168
- @lock_thread = nil
169
- end
280
+ def inspect # :nodoc:
281
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
282
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
170
283
 
171
- if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
- active_connection.lock_thread = @lock_thread
173
- end
284
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
285
+ end
286
+
287
+ def schema_cache
288
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
289
+ end
290
+
291
+ def schema_reflection=(schema_reflection)
292
+ pool_config.schema_reflection = schema_reflection
293
+ @schema_cache = nil
294
+ end
295
+
296
+ def migration_context # :nodoc:
297
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
298
+ end
299
+
300
+ def migrations_paths # :nodoc:
301
+ db_config.migrations_paths || Migrator.migrations_paths
302
+ end
303
+
304
+ def schema_migration # :nodoc:
305
+ SchemaMigration.new(self)
306
+ end
307
+
308
+ def internal_metadata # :nodoc:
309
+ InternalMetadata.new(self)
174
310
  end
175
311
 
176
312
  # Retrieve the connection associated with the current thread, or call
177
313
  # #checkout to obtain one if necessary.
178
314
  #
179
- # #connection can be called any number of times; the connection is
315
+ # #lease_connection can be called any number of times; the connection is
180
316
  # held in a cache keyed by a thread.
317
+ def lease_connection
318
+ lease = connection_lease
319
+ lease.connection ||= checkout
320
+ lease.sticky = true
321
+ lease.connection
322
+ end
323
+
324
+ def permanent_lease? # :nodoc:
325
+ connection_lease.sticky.nil?
326
+ end
327
+
181
328
  def connection
182
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
329
+ ActiveRecord.deprecator.warn(<<~MSG)
330
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
331
+ and will be removed in Rails 8.0. Use #lease_connection instead.
332
+ MSG
333
+ lease_connection
334
+ end
335
+
336
+ def pin_connection!(lock_thread) # :nodoc:
337
+ @pinned_connection ||= (connection_lease&.connection || checkout)
338
+ @pinned_connections_depth += 1
339
+
340
+ # Any leased connection must be in @connections otherwise
341
+ # some methods like #connected? won't behave correctly
342
+ unless @connections.include?(@pinned_connection)
343
+ @connections << @pinned_connection
344
+ end
345
+
346
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
347
+ @pinned_connection.pinned = true
348
+ @pinned_connection.verify! # eagerly validate the connection
349
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
350
+ end
351
+
352
+ def unpin_connection! # :nodoc:
353
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
354
+
355
+ clean = true
356
+ @pinned_connection.lock.synchronize do
357
+ @pinned_connections_depth -= 1
358
+ connection = @pinned_connection
359
+ @pinned_connection = nil if @pinned_connections_depth.zero?
360
+
361
+ if connection.transaction_open?
362
+ connection.rollback_transaction
363
+ else
364
+ # Something committed or rolled back the transaction
365
+ clean = false
366
+ connection.reset!
367
+ end
368
+
369
+ if @pinned_connection.nil?
370
+ connection.pinned = false
371
+ connection.steal!
372
+ connection.lock_thread = nil
373
+ checkin(connection)
374
+ end
375
+ end
376
+
377
+ clean
183
378
  end
184
379
 
185
380
  def connection_class # :nodoc:
186
381
  pool_config.connection_class
187
382
  end
188
- alias :connection_klass :connection_class
189
- deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
383
 
191
384
  # Returns true if there is an open connection being used for the current thread.
192
385
  #
193
386
  # This method only works for connections that have been obtained through
194
- # #connection or #with_connection methods. Connections obtained through
387
+ # #lease_connection or #with_connection methods. Connections obtained through
195
388
  # #checkout will not be detected by #active_connection?
196
389
  def active_connection?
197
- @thread_cached_conns[connection_cache_key(current_thread)]
390
+ connection_lease.connection
198
391
  end
392
+ alias_method :active_connection, :active_connection? # :nodoc:
199
393
 
200
394
  # Signal that the thread is finished with the current connection.
201
395
  # #release_connection releases the connection-thread association
202
396
  # and returns the connection to the pool.
203
397
  #
204
398
  # This method only works for connections that have been obtained through
205
- # #connection or #with_connection methods, connections obtained through
399
+ # #lease_connection or #with_connection methods, connections obtained through
206
400
  # #checkout will not be automatically released.
207
- def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.context)
208
- if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
401
+ def release_connection(existing_lease = nil)
402
+ if conn = connection_lease.release
209
403
  checkin conn
404
+ return true
210
405
  end
406
+ false
211
407
  end
212
408
 
213
409
  # Yields a connection from the connection pool to the block. If no connection
214
410
  # is already checked out by the current thread, a connection will be checked
215
411
  # out from the pool, yielded to the block, and then returned to the pool when
216
412
  # 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
413
+ # current thread, such as via #lease_connection or #with_connection, that existing
218
414
  # connection will be the one yielded and it will not be returned to the pool
219
415
  # automatically at the end of the block; it is expected that such an existing
220
416
  # connection will be properly returned to the pool by the code that checked
221
417
  # it out.
222
- def with_connection
223
- unless conn = @thread_cached_conns[connection_cache_key(ActiveSupport::IsolatedExecutionState.context)]
224
- conn = connection
225
- fresh_connection = true
418
+ def with_connection(prevent_permanent_checkout: false)
419
+ lease = connection_lease
420
+ sticky_was = lease.sticky
421
+ lease.sticky = false if prevent_permanent_checkout
422
+
423
+ if lease.connection
424
+ begin
425
+ yield lease.connection
426
+ ensure
427
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
428
+ end
429
+ else
430
+ begin
431
+ yield lease.connection = checkout
432
+ ensure
433
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
434
+ release_connection(lease) unless lease.sticky
435
+ end
226
436
  end
227
- yield conn
228
- ensure
229
- release_connection if fresh_connection
230
437
  end
231
438
 
232
439
  # Returns true if a connection has already been opened.
233
440
  def connected?
234
- synchronize { @connections.any? }
441
+ synchronize { @connections.any?(&:connected?) }
235
442
  end
236
443
 
237
444
  # Returns an array containing the connections currently in the pool.
@@ -266,6 +473,7 @@ module ActiveRecord
266
473
  conn.disconnect!
267
474
  end
268
475
  @connections = []
476
+ @leases.clear
269
477
  @available.clear
270
478
  end
271
479
  end
@@ -292,7 +500,7 @@ module ActiveRecord
292
500
  @connections.each do |conn|
293
501
  conn.discard!
294
502
  end
295
- @connections = @available = @thread_cached_conns = nil
503
+ @connections = @available = @leases = nil
296
504
  end
297
505
  end
298
506
 
@@ -350,9 +558,26 @@ module ActiveRecord
350
558
  # Raises:
351
559
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
352
560
  def checkout(checkout_timeout = @checkout_timeout)
353
- connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
- connection.lock_thread = @lock_thread
355
- connection
561
+ return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
562
+
563
+ @pinned_connection.lock.synchronize do
564
+ synchronize do
565
+ # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
566
+ if @pinned_connection
567
+ @pinned_connection.verify!
568
+
569
+ # Any leased connection must be in @connections otherwise
570
+ # some methods like #connected? won't behave correctly
571
+ unless @connections.include?(@pinned_connection)
572
+ @connections << @pinned_connection
573
+ end
574
+
575
+ @pinned_connection
576
+ else
577
+ checkout_and_verify(acquire_connection(checkout_timeout))
578
+ end
579
+ end
580
+ end
356
581
  end
357
582
 
358
583
  # Check-in a database connection back into the pool, indicating that you
@@ -361,15 +586,16 @@ module ActiveRecord
361
586
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
362
587
  # calling #checkout on this pool.
363
588
  def checkin(conn)
589
+ return if @pinned_connection.equal?(conn)
590
+
364
591
  conn.lock.synchronize do
365
592
  synchronize do
366
- remove_connection_from_thread_cache conn
593
+ connection_lease.clear(conn)
367
594
 
368
595
  conn._run_checkin_callbacks do
369
596
  conn.expire
370
597
  end
371
598
 
372
- conn.lock_thread = nil
373
599
  @available.add conn
374
600
  end
375
601
  end
@@ -485,7 +711,19 @@ module ActiveRecord
485
711
  Thread.pass
486
712
  end
487
713
 
714
+ def new_connection # :nodoc:
715
+ connection = db_config.new_connection
716
+ connection.pool = self
717
+ connection
718
+ rescue ConnectionNotEstablished => ex
719
+ raise ex.set_pool(self)
720
+ end
721
+
488
722
  private
723
+ def connection_lease
724
+ @leases[ActiveSupport::IsolatedExecutionState.context]
725
+ end
726
+
489
727
  def build_async_executor
490
728
  case ActiveRecord.async_query_executor
491
729
  when :multi_thread_pool
@@ -514,19 +752,6 @@ module ActiveRecord
514
752
  end
515
753
  end
516
754
 
517
- #--
518
- # From the discussion on GitHub:
519
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
520
- # This hook-in method allows for easier monkey-patching fixes needed by
521
- # JRuby users that use Fibers.
522
- def connection_cache_key(thread)
523
- thread
524
- end
525
-
526
- def current_thread
527
- @lock_thread || ActiveSupport::IsolatedExecutionState.context
528
- end
529
-
530
755
  # Take control of all existing connections so a "group" action such as
531
756
  # reload/disconnect can be performed safely. It is no longer enough to
532
757
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -540,6 +765,8 @@ module ActiveRecord
540
765
 
541
766
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
542
767
  collected_conns = synchronize do
768
+ reap # No need to wait for dead owners
769
+
543
770
  # account for our own connections
544
771
  @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
545
772
  end
@@ -551,6 +778,7 @@ module ActiveRecord
551
778
  loop do
552
779
  synchronize do
553
780
  return if collected_conns.size == @connections.size && @now_connecting == 0
781
+
554
782
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
555
783
  remaining_timeout = 0 if remaining_timeout < 0
556
784
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -601,7 +829,7 @@ module ActiveRecord
601
829
 
602
830
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
603
831
 
604
- raise ExclusiveConnectionTimeoutError, msg
832
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
605
833
  end
606
834
 
607
835
  def with_new_connections_blocked
@@ -663,30 +891,18 @@ module ActiveRecord
663
891
  @available.poll(checkout_timeout)
664
892
  end
665
893
  end
894
+ rescue ConnectionTimeoutError => ex
895
+ raise ex.set_pool(self)
666
896
  end
667
897
 
668
898
  #--
669
899
  # if owner_thread param is omitted, this must be called in synchronize block
670
900
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
671
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
672
- end
673
- alias_method :release, :remove_connection_from_thread_cache
674
-
675
- def new_connection
676
- connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
677
- connection.pool = self
678
-
679
- begin
680
- connection.check_version
681
- rescue
682
- connection.disconnect!
683
- raise
901
+ if owner_thread
902
+ @leases[owner_thread].clear(conn)
684
903
  end
685
-
686
- connection
687
- rescue ConnectionNotEstablished => ex
688
- raise ex.set_pool(self)
689
904
  end
905
+ alias_method :release, :remove_connection_from_thread_cache
690
906
 
691
907
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
692
908
  # to the DB is done outside main synchronized section.
@@ -723,6 +939,12 @@ module ActiveRecord
723
939
  def adopt_connection(conn)
724
940
  conn.pool = self
725
941
  @connections << conn
942
+
943
+ # We just created the first connection, it's time to load the schema
944
+ # cache if that wasn't eagerly done before
945
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
946
+ schema_cache.load!
947
+ end
726
948
  end
727
949
 
728
950
  def checkout_new_connection