activerecord 7.1.3.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2133
  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 +18 -11
  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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  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 +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  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 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  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 +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  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 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  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 +6 -0
  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 +16 -12
  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 +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  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 +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  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 +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  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/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  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 +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +17 -12
@@ -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,19 +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
- connection.check_version
679
863
  connection
680
864
  rescue ConnectionNotEstablished => ex
681
865
  raise ex.set_pool(self)
@@ -716,6 +900,12 @@ module ActiveRecord
716
900
  def adopt_connection(conn)
717
901
  conn.pool = self
718
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
719
909
  end
720
910
 
721
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,9 +644,9 @@ 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
- FutureResult::Complete.new(result)
649
+ FutureResult.wrap(result)
633
650
  else
634
651
  result
635
652
  end