activerecord 7.1.3.2 → 7.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +570 -2094
  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 +15 -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 +11 -5
  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 +3 -3
  18. data/lib/active_record/associations/has_one_association.rb +2 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/join_dependency.rb +6 -8
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +34 -273
  28. data/lib/active_record/attribute_assignment.rb +1 -11
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  31. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  32. data/lib/active_record/attribute_methods/read.rb +4 -16
  33. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  35. data/lib/active_record/attribute_methods/write.rb +3 -3
  36. data/lib/active_record/attribute_methods.rb +89 -58
  37. data/lib/active_record/attributes.rb +60 -45
  38. data/lib/active_record/autosave_association.rb +17 -31
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +244 -58
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -75
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  47. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  48. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  49. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  50. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  53. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  56. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  57. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  58. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  61. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  64. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  65. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  66. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  67. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  68. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  69. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  74. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  75. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  76. data/lib/active_record/connection_adapters.rb +121 -0
  77. data/lib/active_record/connection_handling.rb +56 -41
  78. data/lib/active_record/core.rb +60 -39
  79. data/lib/active_record/counter_cache.rb +23 -10
  80. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  81. data/lib/active_record/database_configurations/database_config.rb +19 -4
  82. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  83. data/lib/active_record/database_configurations/url_config.rb +20 -1
  84. data/lib/active_record/database_configurations.rb +1 -1
  85. data/lib/active_record/delegated_type.rb +30 -6
  86. data/lib/active_record/destroy_association_async_job.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +2 -2
  88. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  89. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  90. data/lib/active_record/encryption/encryptor.rb +18 -3
  91. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  92. data/lib/active_record/encryption/message_serializer.rb +4 -0
  93. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  95. data/lib/active_record/encryption/scheme.rb +8 -4
  96. data/lib/active_record/enum.rb +26 -6
  97. data/lib/active_record/errors.rb +46 -20
  98. data/lib/active_record/explain.rb +13 -24
  99. data/lib/active_record/fixtures.rb +37 -31
  100. data/lib/active_record/future_result.rb +17 -4
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +4 -2
  103. data/lib/active_record/insert_all.rb +18 -15
  104. data/lib/active_record/integration.rb +4 -1
  105. data/lib/active_record/internal_metadata.rb +48 -34
  106. data/lib/active_record/locking/optimistic.rb +8 -7
  107. data/lib/active_record/log_subscriber.rb +0 -21
  108. data/lib/active_record/marshalling.rb +1 -1
  109. data/lib/active_record/message_pack.rb +2 -2
  110. data/lib/active_record/migration/command_recorder.rb +2 -3
  111. data/lib/active_record/migration/compatibility.rb +11 -3
  112. data/lib/active_record/migration/default_strategy.rb +4 -5
  113. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  114. data/lib/active_record/migration.rb +85 -76
  115. data/lib/active_record/model_schema.rb +39 -70
  116. data/lib/active_record/nested_attributes.rb +11 -3
  117. data/lib/active_record/normalization.rb +3 -7
  118. data/lib/active_record/persistence.rb +32 -354
  119. data/lib/active_record/query_cache.rb +18 -6
  120. data/lib/active_record/query_logs.rb +15 -0
  121. data/lib/active_record/query_logs_formatter.rb +1 -1
  122. data/lib/active_record/querying.rb +21 -9
  123. data/lib/active_record/railtie.rb +54 -67
  124. data/lib/active_record/railties/controller_runtime.rb +13 -4
  125. data/lib/active_record/railties/databases.rake +42 -45
  126. data/lib/active_record/reflection.rb +102 -37
  127. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  128. data/lib/active_record/relation/batches.rb +14 -8
  129. data/lib/active_record/relation/calculations.rb +95 -62
  130. data/lib/active_record/relation/delegation.rb +8 -11
  131. data/lib/active_record/relation/finder_methods.rb +16 -2
  132. data/lib/active_record/relation/merger.rb +4 -6
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  134. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  135. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  136. data/lib/active_record/relation/predicate_builder.rb +3 -3
  137. data/lib/active_record/relation/query_methods.rb +212 -47
  138. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  139. data/lib/active_record/relation/spawn_methods.rb +2 -18
  140. data/lib/active_record/relation/where_clause.rb +7 -19
  141. data/lib/active_record/relation.rb +500 -66
  142. data/lib/active_record/result.rb +32 -45
  143. data/lib/active_record/runtime_registry.rb +39 -0
  144. data/lib/active_record/sanitization.rb +24 -19
  145. data/lib/active_record/schema.rb +8 -6
  146. data/lib/active_record/schema_dumper.rb +19 -9
  147. data/lib/active_record/schema_migration.rb +30 -14
  148. data/lib/active_record/scoping/named.rb +1 -0
  149. data/lib/active_record/signed_id.rb +20 -1
  150. data/lib/active_record/statement_cache.rb +7 -7
  151. data/lib/active_record/table_metadata.rb +1 -10
  152. data/lib/active_record/tasks/database_tasks.rb +87 -48
  153. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  154. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  155. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  156. data/lib/active_record/test_fixtures.rb +87 -89
  157. data/lib/active_record/testing/query_assertions.rb +121 -0
  158. data/lib/active_record/timestamp.rb +5 -3
  159. data/lib/active_record/token_for.rb +22 -12
  160. data/lib/active_record/touch_later.rb +1 -1
  161. data/lib/active_record/transaction.rb +132 -0
  162. data/lib/active_record/transactions.rb +70 -14
  163. data/lib/active_record/translation.rb +0 -2
  164. data/lib/active_record/type/serialized.rb +1 -3
  165. data/lib/active_record/type_caster/connection.rb +4 -4
  166. data/lib/active_record/validations/associated.rb +9 -3
  167. data/lib/active_record/validations/uniqueness.rb +14 -10
  168. data/lib/active_record/validations.rb +4 -1
  169. data/lib/active_record.rb +150 -41
  170. data/lib/arel/alias_predication.rb +1 -1
  171. data/lib/arel/collectors/bind.rb +2 -0
  172. data/lib/arel/collectors/composite.rb +7 -0
  173. data/lib/arel/collectors/sql_string.rb +1 -1
  174. data/lib/arel/collectors/substitute_binds.rb +1 -1
  175. data/lib/arel/nodes/binary.rb +0 -6
  176. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  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.rb +2 -2
  181. data/lib/arel/predications.rb +1 -1
  182. data/lib/arel/select_manager.rb +1 -1
  183. data/lib/arel/tree_manager.rb +8 -3
  184. data/lib/arel/update_manager.rb +2 -1
  185. data/lib/arel/visitors/dot.rb +1 -0
  186. data/lib/arel/visitors/mysql.rb +9 -4
  187. data/lib/arel/visitors/postgresql.rb +1 -12
  188. data/lib/arel/visitors/to_sql.rb +31 -17
  189. data/lib/arel.rb +7 -3
  190. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  191. metadata +18 -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#lease_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,80 @@ 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 WeakThreadKeyMap # :nodoc:
122
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
123
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
124
+ def initialize
125
+ @map = {}
126
+ end
127
+
128
+ def clear
129
+ @map.clear
130
+ end
131
+
132
+ def [](key)
133
+ @map[key]
134
+ end
135
+
136
+ def []=(key, value)
137
+ @map.select! { |c, _| c.alive? }
138
+ @map[key] = value
139
+ end
140
+ end
141
+
142
+ class Lease # :nodoc:
143
+ attr_accessor :connection, :sticky
144
+
145
+ def initialize
146
+ @connection = nil
147
+ @sticky = nil
148
+ end
149
+
150
+ def release
151
+ conn = @connection
152
+ @connection = nil
153
+ @sticky = nil
154
+ conn
155
+ end
156
+
157
+ def clear(connection)
158
+ if @connection == connection
159
+ @connection = nil
160
+ @sticky = nil
161
+ true
162
+ else
163
+ false
164
+ end
165
+ end
166
+ end
167
+
168
+ class LeaseRegistry # :nodoc:
169
+ def initialize
170
+ @mutex = Mutex.new
171
+ @map = WeakThreadKeyMap.new
172
+ end
173
+
174
+ def [](context)
175
+ @mutex.synchronize do
176
+ @map[context] ||= Lease.new
177
+ end
178
+ end
179
+
180
+ def clear
181
+ @mutex.synchronize do
182
+ @map.clear
183
+ end
184
+ end
185
+ end
186
+
105
187
  include MonitorMixin
106
- include QueryCache::ConnectionPoolConfiguration
188
+ prepend QueryCache::ConnectionPoolConfiguration
107
189
  include ConnectionAdapters::AbstractPool
108
190
 
109
191
  attr_accessor :automatic_reconnect, :checkout_timeout
110
192
  attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
111
193
 
112
- delegate :schema_reflection, :schema_reflection=, to: :pool_config
194
+ delegate :schema_reflection, :server_version, to: :pool_config
113
195
 
114
196
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
197
  # object which describes database connection information (e.g. adapter,
@@ -137,9 +219,9 @@ module ActiveRecord
137
219
  # then that +thread+ does indeed own that +conn+. However, an absence of such
138
220
  # mapping does not mean that the +thread+ doesn't own the said connection. In
139
221
  # that case +conn.owner+ attr should be consulted.
140
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
222
+ # Access and modification of <tt>@leases</tt> does not require
141
223
  # synchronization.
142
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
224
+ @leases = LeaseRegistry.new
143
225
 
144
226
  @connections = []
145
227
  @automatic_reconnect = true
@@ -152,86 +234,175 @@ module ActiveRecord
152
234
  @threads_blocking_new_connections = 0
153
235
 
154
236
  @available = ConnectionLeasingQueue.new self
155
-
156
- @lock_thread = false
237
+ @pinned_connection = nil
238
+ @pinned_connections_depth = 0
157
239
 
158
240
  @async_executor = build_async_executor
159
241
 
242
+ @schema_cache = nil
243
+
160
244
  @reaper = Reaper.new(self, db_config.reaping_frequency)
161
245
  @reaper.run
162
246
  end
163
247
 
164
- def lock_thread=(lock_thread)
165
- if lock_thread
166
- @lock_thread = ActiveSupport::IsolatedExecutionState.context
167
- else
168
- @lock_thread = nil
169
- end
248
+ def inspect # :nodoc:
249
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
250
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
170
251
 
171
- if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
- active_connection.lock_thread = @lock_thread
173
- end
252
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
253
+ end
254
+
255
+ def schema_cache
256
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
257
+ end
258
+
259
+ def schema_reflection=(schema_reflection)
260
+ pool_config.schema_reflection = schema_reflection
261
+ @schema_cache = nil
262
+ end
263
+
264
+ def migration_context # :nodoc:
265
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
266
+ end
267
+
268
+ def migrations_paths # :nodoc:
269
+ db_config.migrations_paths || Migrator.migrations_paths
270
+ end
271
+
272
+ def schema_migration # :nodoc:
273
+ SchemaMigration.new(self)
274
+ end
275
+
276
+ def internal_metadata # :nodoc:
277
+ InternalMetadata.new(self)
174
278
  end
175
279
 
176
280
  # Retrieve the connection associated with the current thread, or call
177
281
  # #checkout to obtain one if necessary.
178
282
  #
179
- # #connection can be called any number of times; the connection is
283
+ # #lease_connection can be called any number of times; the connection is
180
284
  # held in a cache keyed by a thread.
285
+ def lease_connection
286
+ lease = connection_lease
287
+ lease.sticky = true
288
+ lease.connection ||= checkout
289
+ end
290
+
291
+ def permanent_lease? # :nodoc:
292
+ connection_lease.sticky.nil?
293
+ end
294
+
181
295
  def connection
182
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
296
+ ActiveRecord.deprecator.warn(<<~MSG)
297
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
298
+ and will be removed in Rails 8.0. Use #lease_connection instead.
299
+ MSG
300
+ lease_connection
301
+ end
302
+
303
+ def pin_connection!(lock_thread) # :nodoc:
304
+ @pinned_connection ||= (connection_lease&.connection || checkout)
305
+ @pinned_connections_depth += 1
306
+
307
+ # Any leased connection must be in @connections otherwise
308
+ # some methods like #connected? won't behave correctly
309
+ unless @connections.include?(@pinned_connection)
310
+ @connections << @pinned_connection
311
+ end
312
+
313
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
314
+ @pinned_connection.verify! # eagerly validate the connection
315
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
316
+ end
317
+
318
+ def unpin_connection! # :nodoc:
319
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
320
+
321
+ clean = true
322
+ @pinned_connection.lock.synchronize do
323
+ @pinned_connections_depth -= 1
324
+ connection = @pinned_connection
325
+ @pinned_connection = nil if @pinned_connections_depth.zero?
326
+
327
+ if connection.transaction_open?
328
+ connection.rollback_transaction
329
+ else
330
+ # Something committed or rolled back the transaction
331
+ clean = false
332
+ connection.reset!
333
+ end
334
+
335
+ if @pinned_connection.nil?
336
+ connection.lock_thread = nil
337
+ checkin(connection)
338
+ end
339
+ end
340
+
341
+ clean
183
342
  end
184
343
 
185
344
  def connection_class # :nodoc:
186
345
  pool_config.connection_class
187
346
  end
188
- alias :connection_klass :connection_class
189
- deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
347
 
191
348
  # Returns true if there is an open connection being used for the current thread.
192
349
  #
193
350
  # This method only works for connections that have been obtained through
194
- # #connection or #with_connection methods. Connections obtained through
351
+ # #lease_connection or #with_connection methods. Connections obtained through
195
352
  # #checkout will not be detected by #active_connection?
196
353
  def active_connection?
197
- @thread_cached_conns[connection_cache_key(current_thread)]
354
+ connection_lease.connection
198
355
  end
356
+ alias_method :active_connection, :active_connection? # :nodoc:
199
357
 
200
358
  # Signal that the thread is finished with the current connection.
201
359
  # #release_connection releases the connection-thread association
202
360
  # and returns the connection to the pool.
203
361
  #
204
362
  # This method only works for connections that have been obtained through
205
- # #connection or #with_connection methods, connections obtained through
363
+ # #lease_connection or #with_connection methods, connections obtained through
206
364
  # #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))
365
+ def release_connection(existing_lease = nil)
366
+ if conn = connection_lease.release
209
367
  checkin conn
368
+ return true
210
369
  end
370
+ false
211
371
  end
212
372
 
213
373
  # Yields a connection from the connection pool to the block. If no connection
214
374
  # is already checked out by the current thread, a connection will be checked
215
375
  # out from the pool, yielded to the block, and then returned to the pool when
216
376
  # 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
377
+ # current thread, such as via #lease_connection or #with_connection, that existing
218
378
  # connection will be the one yielded and it will not be returned to the pool
219
379
  # automatically at the end of the block; it is expected that such an existing
220
380
  # connection will be properly returned to the pool by the code that checked
221
381
  # 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
382
+ def with_connection(prevent_permanent_checkout: false)
383
+ lease = connection_lease
384
+ sticky_was = lease.sticky
385
+ lease.sticky = false if prevent_permanent_checkout
386
+
387
+ if lease.connection
388
+ begin
389
+ yield lease.connection
390
+ ensure
391
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
392
+ end
393
+ else
394
+ begin
395
+ yield lease.connection = checkout
396
+ ensure
397
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
398
+ release_connection(lease) unless lease.sticky
399
+ end
226
400
  end
227
- yield conn
228
- ensure
229
- release_connection if fresh_connection
230
401
  end
231
402
 
232
403
  # Returns true if a connection has already been opened.
233
404
  def connected?
234
- synchronize { @connections.any? }
405
+ synchronize { @connections.any?(&:connected?) }
235
406
  end
236
407
 
237
408
  # Returns an array containing the connections currently in the pool.
@@ -266,6 +437,7 @@ module ActiveRecord
266
437
  conn.disconnect!
267
438
  end
268
439
  @connections = []
440
+ @leases.clear
269
441
  @available.clear
270
442
  end
271
443
  end
@@ -292,7 +464,7 @@ module ActiveRecord
292
464
  @connections.each do |conn|
293
465
  conn.discard!
294
466
  end
295
- @connections = @available = @thread_cached_conns = nil
467
+ @connections = @available = @leases = nil
296
468
  end
297
469
  end
298
470
 
@@ -350,9 +522,21 @@ module ActiveRecord
350
522
  # Raises:
351
523
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
352
524
  def checkout(checkout_timeout = @checkout_timeout)
353
- connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
- connection.lock_thread = @lock_thread
355
- connection
525
+ if @pinned_connection
526
+ @pinned_connection.lock.synchronize do
527
+ synchronize do
528
+ @pinned_connection.verify!
529
+ # Any leased connection must be in @connections otherwise
530
+ # some methods like #connected? won't behave correctly
531
+ unless @connections.include?(@pinned_connection)
532
+ @connections << @pinned_connection
533
+ end
534
+ end
535
+ end
536
+ @pinned_connection
537
+ else
538
+ checkout_and_verify(acquire_connection(checkout_timeout))
539
+ end
356
540
  end
357
541
 
358
542
  # Check-in a database connection back into the pool, indicating that you
@@ -361,15 +545,16 @@ module ActiveRecord
361
545
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
362
546
  # calling #checkout on this pool.
363
547
  def checkin(conn)
548
+ return if @pinned_connection.equal?(conn)
549
+
364
550
  conn.lock.synchronize do
365
551
  synchronize do
366
- remove_connection_from_thread_cache conn
552
+ connection_lease.clear(conn)
367
553
 
368
554
  conn._run_checkin_callbacks do
369
555
  conn.expire
370
556
  end
371
557
 
372
- conn.lock_thread = nil
373
558
  @available.add conn
374
559
  end
375
560
  end
@@ -486,6 +671,10 @@ module ActiveRecord
486
671
  end
487
672
 
488
673
  private
674
+ def connection_lease
675
+ @leases[ActiveSupport::IsolatedExecutionState.context]
676
+ end
677
+
489
678
  def build_async_executor
490
679
  case ActiveRecord.async_query_executor
491
680
  when :multi_thread_pool
@@ -514,19 +703,6 @@ module ActiveRecord
514
703
  end
515
704
  end
516
705
 
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
706
  # Take control of all existing connections so a "group" action such as
531
707
  # reload/disconnect can be performed safely. It is no longer enough to
532
708
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -540,6 +716,8 @@ module ActiveRecord
540
716
 
541
717
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
542
718
  collected_conns = synchronize do
719
+ reap # No need to wait for dead owners
720
+
543
721
  # account for our own connections
544
722
  @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
545
723
  end
@@ -551,6 +729,7 @@ module ActiveRecord
551
729
  loop do
552
730
  synchronize do
553
731
  return if collected_conns.size == @connections.size && @now_connecting == 0
732
+
554
733
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
555
734
  remaining_timeout = 0 if remaining_timeout < 0
556
735
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -601,7 +780,7 @@ module ActiveRecord
601
780
 
602
781
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
603
782
 
604
- raise ExclusiveConnectionTimeoutError, msg
783
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
605
784
  end
606
785
 
607
786
  def with_new_connections_blocked
@@ -663,19 +842,20 @@ module ActiveRecord
663
842
  @available.poll(checkout_timeout)
664
843
  end
665
844
  end
845
+ rescue ConnectionTimeoutError => ex
846
+ raise ex.set_pool(self)
666
847
  end
667
848
 
668
849
  #--
669
850
  # if owner_thread param is omitted, this must be called in synchronize block
670
851
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
671
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
852
+ @leases[owner_thread].clear(conn)
672
853
  end
673
854
  alias_method :release, :remove_connection_from_thread_cache
674
855
 
675
856
  def new_connection
676
- connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
857
+ connection = db_config.new_connection
677
858
  connection.pool = self
678
- connection.check_version
679
859
  connection
680
860
  rescue ConnectionNotEstablished => ex
681
861
  raise ex.set_pool(self)
@@ -716,6 +896,12 @@ module ActiveRecord
716
896
  def adopt_connection(conn)
717
897
  conn.pool = self
718
898
  @connections << conn
899
+
900
+ # We just created the first connection, it's time to load the schema
901
+ # cache if that wasn't eagerly done before
902
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
903
+ schema_cache.load!
904
+ end
719
905
  end
720
906
 
721
907
  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.user_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