activerecord 7.1.5.1 → 7.2.0.beta1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  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 +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  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 +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  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 +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  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 +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -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/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  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 +44 -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 +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  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 +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -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 +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  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 +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  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 +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  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.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -16,23 +16,39 @@ 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
30
41
  def checkin(_); end
31
42
  def remove(_); end
32
43
  def async_executor; end
44
+
33
45
  def db_config
34
46
  NULL_CONFIG
35
47
  end
48
+
49
+ def dirties_query_cache
50
+ true
51
+ end
36
52
  end
37
53
 
38
54
  # = Active Record Connection Pool
@@ -58,7 +74,7 @@ module ActiveRecord
58
74
  # Connections can be obtained and used from a connection pool in several
59
75
  # ways:
60
76
  #
61
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection].
77
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling.connection].
62
78
  # When you're done with the connection(s) and wish it to be returned to the pool, you call
63
79
  # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
64
80
  # This is the default behavior for Active Record when used in conjunction with
@@ -102,14 +118,98 @@ module ActiveRecord
102
118
  # * private methods that require being called in a +synchronize+ blocks
103
119
  # are now explicitly documented
104
120
  class ConnectionPool
121
+ class Lease # :nodoc:
122
+ attr_accessor :connection, :sticky
123
+
124
+ def initialize
125
+ @connection = nil
126
+ @sticky = nil
127
+ end
128
+
129
+ def release
130
+ conn = @connection
131
+ @connection = nil
132
+ @sticky = nil
133
+ conn
134
+ end
135
+
136
+ def clear(connection)
137
+ if @connection == connection
138
+ @connection = nil
139
+ @sticky = nil
140
+ true
141
+ else
142
+ false
143
+ end
144
+ end
145
+ end
146
+
147
+ class LeaseRegistry # :nodoc:
148
+ if ObjectSpace.const_defined?(:WeakKeyMap) # RUBY_VERSION >= 3.3
149
+ WeakKeyMap = ::ObjectSpace::WeakKeyMap # :nodoc:
150
+ else
151
+ class WeakKeyMap # :nodoc:
152
+ def initialize
153
+ @map = ObjectSpace::WeakMap.new
154
+ @values = nil
155
+ @size = 0
156
+ end
157
+
158
+ alias_method :clear, :initialize
159
+
160
+ def [](key)
161
+ prune if @map.size != @size
162
+ @map[key]
163
+ end
164
+
165
+ def []=(key, value)
166
+ @map[key] = value
167
+ prune if @map.size != @size
168
+ value
169
+ end
170
+
171
+ def delete(key)
172
+ if value = self[key]
173
+ self[key] = nil
174
+ prune
175
+ end
176
+ value
177
+ end
178
+
179
+ private
180
+ def prune(force = false)
181
+ @values = @map.values
182
+ @size = @map.size
183
+ end
184
+ end
185
+ end
186
+
187
+ def initialize
188
+ @mutex = Mutex.new
189
+ @map = WeakKeyMap.new
190
+ end
191
+
192
+ def [](context)
193
+ @mutex.synchronize do
194
+ @map[context] ||= Lease.new
195
+ end
196
+ end
197
+
198
+ def clear
199
+ @mutex.synchronize do
200
+ @map = WeakKeyMap.new
201
+ end
202
+ end
203
+ end
204
+
105
205
  include MonitorMixin
106
- include QueryCache::ConnectionPoolConfiguration
206
+ prepend QueryCache::ConnectionPoolConfiguration
107
207
  include ConnectionAdapters::AbstractPool
108
208
 
109
209
  attr_accessor :automatic_reconnect, :checkout_timeout
110
210
  attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
111
211
 
112
- delegate :schema_reflection, :schema_reflection=, to: :pool_config
212
+ delegate :schema_reflection, :server_version, to: :pool_config
113
213
 
114
214
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
215
  # object which describes database connection information (e.g. adapter,
@@ -137,9 +237,9 @@ module ActiveRecord
137
237
  # then that +thread+ does indeed own that +conn+. However, an absence of such
138
238
  # mapping does not mean that the +thread+ doesn't own the said connection. In
139
239
  # that case +conn.owner+ attr should be consulted.
140
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
240
+ # Access and modification of <tt>@leases</tt> does not require
141
241
  # synchronization.
142
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
242
+ @leases = LeaseRegistry.new
143
243
 
144
244
  @connections = []
145
245
  @automatic_reconnect = true
@@ -152,86 +252,161 @@ module ActiveRecord
152
252
  @threads_blocking_new_connections = 0
153
253
 
154
254
  @available = ConnectionLeasingQueue.new self
155
-
156
- @lock_thread = false
255
+ @pinned_connection = nil
157
256
 
158
257
  @async_executor = build_async_executor
159
258
 
259
+ @schema_cache = nil
260
+
160
261
  @reaper = Reaper.new(self, db_config.reaping_frequency)
161
262
  @reaper.run
162
263
  end
163
264
 
164
- def lock_thread=(lock_thread)
165
- if lock_thread
166
- @lock_thread = ActiveSupport::IsolatedExecutionState.context
167
- else
168
- @lock_thread = nil
169
- end
265
+ def schema_cache
266
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
267
+ end
170
268
 
171
- if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
- active_connection.lock_thread = @lock_thread
173
- end
269
+ def schema_reflection=(schema_reflection)
270
+ pool_config.schema_reflection = schema_reflection
271
+ @schema_cache = nil
272
+ end
273
+
274
+ def migration_context # :nodoc:
275
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
276
+ end
277
+
278
+ def migrations_paths # :nodoc:
279
+ db_config.migrations_paths || Migrator.migrations_paths
280
+ end
281
+
282
+ def schema_migration # :nodoc:
283
+ SchemaMigration.new(self)
284
+ end
285
+
286
+ def internal_metadata # :nodoc:
287
+ InternalMetadata.new(self)
174
288
  end
175
289
 
176
290
  # Retrieve the connection associated with the current thread, or call
177
291
  # #checkout to obtain one if necessary.
178
292
  #
179
- # #connection can be called any number of times; the connection is
293
+ # #lease_connection can be called any number of times; the connection is
180
294
  # held in a cache keyed by a thread.
295
+ def lease_connection
296
+ lease = connection_lease
297
+ lease.sticky = true
298
+ lease.connection ||= checkout
299
+ end
300
+
301
+ def permanent_lease? # :nodoc:
302
+ connection_lease.sticky.nil?
303
+ end
304
+
181
305
  def connection
182
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
306
+ ActiveRecord.deprecator.warn(<<~MSG)
307
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
308
+ and will be removed in Rails 7.3. Use #lease_connection instead.
309
+ MSG
310
+ lease_connection
311
+ end
312
+
313
+ def pin_connection!(lock_thread) # :nodoc:
314
+ raise "There is already a pinned connection" if @pinned_connection
315
+
316
+ @pinned_connection = (connection_lease&.connection || checkout)
317
+ # Any leased connection must be in @connections otherwise
318
+ # some methods like #connected? won't behave correctly
319
+ unless @connections.include?(@pinned_connection)
320
+ @connections << @pinned_connection
321
+ end
322
+
323
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
324
+ @pinned_connection.verify! # eagerly validate the connection
325
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
326
+ end
327
+
328
+ def unpin_connection! # :nodoc:
329
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
330
+
331
+ clean = true
332
+ @pinned_connection.lock.synchronize do
333
+ connection, @pinned_connection = @pinned_connection, nil
334
+ if connection.transaction_open?
335
+ connection.rollback_transaction
336
+ else
337
+ # Something committed or rolled back the transaction
338
+ clean = false
339
+ connection.reset!
340
+ end
341
+ connection.lock_thread = nil
342
+ checkin(connection)
343
+ end
344
+
345
+ clean
183
346
  end
184
347
 
185
348
  def connection_class # :nodoc:
186
349
  pool_config.connection_class
187
350
  end
188
- alias :connection_klass :connection_class
189
- deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
351
 
191
352
  # Returns true if there is an open connection being used for the current thread.
192
353
  #
193
354
  # This method only works for connections that have been obtained through
194
- # #connection or #with_connection methods. Connections obtained through
355
+ # #lease_connection or #with_connection methods. Connections obtained through
195
356
  # #checkout will not be detected by #active_connection?
196
357
  def active_connection?
197
- @thread_cached_conns[connection_cache_key(current_thread)]
358
+ connection_lease.connection
198
359
  end
360
+ alias_method :active_connection, :active_connection? # :nodoc:
199
361
 
200
362
  # Signal that the thread is finished with the current connection.
201
363
  # #release_connection releases the connection-thread association
202
364
  # and returns the connection to the pool.
203
365
  #
204
366
  # This method only works for connections that have been obtained through
205
- # #connection or #with_connection methods, connections obtained through
367
+ # #lease_connection or #with_connection methods, connections obtained through
206
368
  # #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))
369
+ def release_connection(existing_lease = nil)
370
+ if conn = connection_lease.release
209
371
  checkin conn
372
+ return true
210
373
  end
374
+ false
211
375
  end
212
376
 
213
377
  # Yields a connection from the connection pool to the block. If no connection
214
378
  # is already checked out by the current thread, a connection will be checked
215
379
  # out from the pool, yielded to the block, and then returned to the pool when
216
380
  # 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
381
+ # current thread, such as via #lease_connection or #with_connection, that existing
218
382
  # connection will be the one yielded and it will not be returned to the pool
219
383
  # automatically at the end of the block; it is expected that such an existing
220
384
  # connection will be properly returned to the pool by the code that checked
221
385
  # 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
386
+ def with_connection(prevent_permanent_checkout: false)
387
+ lease = connection_lease
388
+ sticky_was = lease.sticky
389
+ lease.sticky = false if prevent_permanent_checkout
390
+
391
+ if lease.connection
392
+ begin
393
+ yield lease.connection
394
+ ensure
395
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
396
+ end
397
+ else
398
+ begin
399
+ yield lease.connection = checkout
400
+ ensure
401
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
402
+ release_connection(lease) unless lease.sticky
403
+ end
226
404
  end
227
- yield conn
228
- ensure
229
- release_connection if fresh_connection
230
405
  end
231
406
 
232
407
  # Returns true if a connection has already been opened.
233
408
  def connected?
234
- synchronize { @connections.any? }
409
+ synchronize { @connections.any?(&:connected?) }
235
410
  end
236
411
 
237
412
  # Returns an array containing the connections currently in the pool.
@@ -266,6 +441,7 @@ module ActiveRecord
266
441
  conn.disconnect!
267
442
  end
268
443
  @connections = []
444
+ @leases.clear
269
445
  @available.clear
270
446
  end
271
447
  end
@@ -292,7 +468,7 @@ module ActiveRecord
292
468
  @connections.each do |conn|
293
469
  conn.discard!
294
470
  end
295
- @connections = @available = @thread_cached_conns = nil
471
+ @connections = @available = @leases = nil
296
472
  end
297
473
  end
298
474
 
@@ -350,9 +526,19 @@ module ActiveRecord
350
526
  # Raises:
351
527
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
352
528
  def checkout(checkout_timeout = @checkout_timeout)
353
- connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
- connection.lock_thread = @lock_thread
355
- connection
529
+ if @pinned_connection
530
+ synchronize do
531
+ @pinned_connection.verify!
532
+ # Any leased connection must be in @connections otherwise
533
+ # some methods like #connected? won't behave correctly
534
+ unless @connections.include?(@pinned_connection)
535
+ @connections << @pinned_connection
536
+ end
537
+ end
538
+ @pinned_connection
539
+ else
540
+ checkout_and_verify(acquire_connection(checkout_timeout))
541
+ end
356
542
  end
357
543
 
358
544
  # Check-in a database connection back into the pool, indicating that you
@@ -361,15 +547,16 @@ module ActiveRecord
361
547
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
362
548
  # calling #checkout on this pool.
363
549
  def checkin(conn)
550
+ return if @pinned_connection.equal?(conn)
551
+
364
552
  conn.lock.synchronize do
365
553
  synchronize do
366
- remove_connection_from_thread_cache conn
554
+ connection_lease.clear(conn)
367
555
 
368
556
  conn._run_checkin_callbacks do
369
557
  conn.expire
370
558
  end
371
559
 
372
- conn.lock_thread = nil
373
560
  @available.add conn
374
561
  end
375
562
  end
@@ -427,6 +614,8 @@ module ActiveRecord
427
614
  remove conn
428
615
  end
429
616
  end
617
+
618
+ prune_thread_cache
430
619
  end
431
620
 
432
621
  # Disconnect all connections that have been idle for at least
@@ -486,6 +675,10 @@ module ActiveRecord
486
675
  end
487
676
 
488
677
  private
678
+ def connection_lease
679
+ @leases[ActiveSupport::IsolatedExecutionState.context]
680
+ end
681
+
489
682
  def build_async_executor
490
683
  case ActiveRecord.async_query_executor
491
684
  when :multi_thread_pool
@@ -514,19 +707,6 @@ module ActiveRecord
514
707
  end
515
708
  end
516
709
 
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
710
  # Take control of all existing connections so a "group" action such as
531
711
  # reload/disconnect can be performed safely. It is no longer enough to
532
712
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -540,6 +720,8 @@ module ActiveRecord
540
720
 
541
721
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
542
722
  collected_conns = synchronize do
723
+ reap # No need to wait for dead owners
724
+
543
725
  # account for our own connections
544
726
  @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
545
727
  end
@@ -551,6 +733,7 @@ module ActiveRecord
551
733
  loop do
552
734
  synchronize do
553
735
  return if collected_conns.size == @connections.size && @now_connecting == 0
736
+
554
737
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
555
738
  remaining_timeout = 0 if remaining_timeout < 0
556
739
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -601,7 +784,7 @@ module ActiveRecord
601
784
 
602
785
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
603
786
 
604
- raise ExclusiveConnectionTimeoutError, msg
787
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
605
788
  end
606
789
 
607
790
  def with_new_connections_blocked
@@ -663,26 +846,20 @@ module ActiveRecord
663
846
  @available.poll(checkout_timeout)
664
847
  end
665
848
  end
849
+ rescue ConnectionTimeoutError => ex
850
+ raise ex.set_pool(self)
666
851
  end
667
852
 
668
853
  #--
669
854
  # if owner_thread param is omitted, this must be called in synchronize block
670
855
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
671
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
856
+ @leases[owner_thread].clear(conn)
672
857
  end
673
858
  alias_method :release, :remove_connection_from_thread_cache
674
859
 
675
860
  def new_connection
676
- connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
861
+ connection = db_config.new_connection
677
862
  connection.pool = self
678
-
679
- begin
680
- connection.check_version
681
- rescue
682
- connection.disconnect!
683
- raise
684
- end
685
-
686
863
  connection
687
864
  rescue ConnectionNotEstablished => ex
688
865
  raise ex.set_pool(self)
@@ -723,6 +900,12 @@ module ActiveRecord
723
900
  def adopt_connection(conn)
724
901
  conn.pool = self
725
902
  @connections << conn
903
+
904
+ # We just created the first connection, it's time to load the schema
905
+ # cache if that wasn't eagerly done before
906
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
907
+ schema_cache.load!
908
+ end
726
909
  end
727
910
 
728
911
  def checkout_new_connection
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  sql
15
15
  end
16
16
 
17
- def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
17
+ def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc:
18
18
  # Arel::TreeManager -> Arel::Node
19
19
  if arel_or_sql_string.respond_to?(:ast)
20
20
  arel_or_sql_string = arel_or_sql_string.ast
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  end
28
28
 
29
29
  collector = collector()
30
+ collector.retryable = true
30
31
 
31
32
  if prepared_statements
32
33
  collector.preparable = true
@@ -41,10 +42,11 @@ module ActiveRecord
41
42
  else
42
43
  sql = visitor.compile(arel_or_sql_string, collector)
43
44
  end
44
- [sql.freeze, binds, preparable]
45
+ allow_retry = collector.retryable
46
+ [sql.freeze, binds, preparable, allow_retry]
45
47
  else
46
48
  arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
47
- [arel_or_sql_string, binds, preparable]
49
+ [arel_or_sql_string, binds, preparable, allow_retry]
48
50
  end
49
51
  end
50
52
  private :to_sql_and_binds
@@ -64,11 +66,15 @@ module ActiveRecord
64
66
  end
65
67
 
66
68
  # Returns an ActiveRecord::Result instance.
67
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
69
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
68
70
  arel = arel_from_relation(arel)
69
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
71
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
70
72
 
71
- select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll)
73
+ select(sql, name, binds,
74
+ prepare: prepared_statements && preparable,
75
+ async: async && FutureResult::SelectAll,
76
+ allow_retry: allow_retry
77
+ )
72
78
  rescue ::RangeError
73
79
  ActiveRecord::Result.empty(async: async)
74
80
  end
@@ -214,7 +220,7 @@ module ActiveRecord
214
220
  end
215
221
 
216
222
  def truncate_tables(*table_names) # :nodoc:
217
- table_names -= [schema_migration.table_name, internal_metadata.table_name]
223
+ table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name]
218
224
 
219
225
  return if table_names.empty?
220
226
 
@@ -229,6 +235,17 @@ module ActiveRecord
229
235
  # Runs the given block in a database transaction, and returns the result
230
236
  # of the block.
231
237
  #
238
+ # == Transaction callbacks
239
+ #
240
+ # #transaction yields an ActiveRecord::Transaction object on which it is
241
+ # possible to register callback:
242
+ #
243
+ # ActiveRecord::Base.transaction do |transaction|
244
+ # transaction.before_commit { puts "before commit!" }
245
+ # transaction.after_commit { puts "after commit!" }
246
+ # transaction.after_rollback { puts "after rollback!" }
247
+ # end
248
+ #
232
249
  # == Nested transactions support
233
250
  #
234
251
  # #transaction calls can be nested. By default, this makes all database
@@ -296,9 +313,9 @@ module ActiveRecord
296
313
  # #transaction will raise exceptions when it tries to release the
297
314
  # already-automatically-released savepoints:
298
315
  #
299
- # Model.connection.transaction do # BEGIN
300
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
301
- # Model.connection.create_table(...)
316
+ # Model.lease_connection.transaction do # BEGIN
317
+ # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
318
+ # Model.lease_connection.create_table(...)
302
319
  # # active_record_1 now automatically released
303
320
  # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
304
321
  # end
@@ -339,7 +356,7 @@ module ActiveRecord
339
356
  if isolation
340
357
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
341
358
  end
342
- yield
359
+ yield current_transaction
343
360
  else
344
361
  transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
345
362
  end
@@ -457,8 +474,8 @@ module ActiveRecord
457
474
  statements = table_deletes + fixture_inserts
458
475
 
459
476
  with_multi_statements do
460
- disable_referential_integrity do
461
- transaction(requires_new: true) do
477
+ transaction(requires_new: true) do
478
+ disable_referential_integrity do
462
479
  execute_batch(statements, "Fixtures Load")
463
480
  end
464
481
  end
@@ -495,7 +512,7 @@ module ActiveRecord
495
512
  end
496
513
 
497
514
  # This is a safe default, even if not high precision on all databases
498
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
515
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
499
516
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
500
517
 
501
518
  # Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
@@ -507,7 +524,7 @@ module ActiveRecord
507
524
  HIGH_PRECISION_CURRENT_TIMESTAMP
508
525
  end
509
526
 
510
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
527
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
511
528
  raise NotImplementedError
512
529
  end
513
530
 
@@ -606,7 +623,7 @@ module ActiveRecord
606
623
  end
607
624
 
608
625
  # Returns an ActiveRecord::Result instance.
609
- def select(sql, name = nil, binds = [], prepare: false, async: false)
626
+ def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
610
627
  if async && async_enabled?
611
628
  if current_transaction.joinable?
612
629
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
@@ -627,7 +644,7 @@ module ActiveRecord
627
644
  return future_result
628
645
  end
629
646
 
630
- result = internal_exec_query(sql, name, binds, prepare: prepare)
647
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
631
648
  if async
632
649
  FutureResult.wrap(result)
633
650
  else