activerecord 7.1.5.1 → 7.2.2.1

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +645 -2329
  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 +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 +7 -1
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +54 -63
  34. data/lib/active_record/attributes.rb +61 -47
  35. data/lib/active_record/autosave_association.rb +12 -29
  36. data/lib/active_record/base.rb +2 -3
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -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 +189 -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 +15 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  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/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  62. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  63. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  64. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  65. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  66. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  67. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  70. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  73. data/lib/active_record/connection_adapters.rb +121 -0
  74. data/lib/active_record/connection_handling.rb +56 -41
  75. data/lib/active_record/core.rb +85 -37
  76. data/lib/active_record/counter_cache.rb +18 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  78. data/lib/active_record/database_configurations/database_config.rb +19 -4
  79. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  80. data/lib/active_record/database_configurations/url_config.rb +20 -1
  81. data/lib/active_record/database_configurations.rb +1 -1
  82. data/lib/active_record/delegated_type.rb +24 -0
  83. data/lib/active_record/dynamic_matchers.rb +2 -2
  84. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  86. data/lib/active_record/encryption/encryptor.rb +18 -3
  87. data/lib/active_record/encryption/key_provider.rb +1 -1
  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/enum.rb +18 -1
  93. data/lib/active_record/errors.rb +46 -20
  94. data/lib/active_record/explain.rb +13 -24
  95. data/lib/active_record/fixtures.rb +37 -31
  96. data/lib/active_record/future_result.rb +8 -4
  97. data/lib/active_record/gem_version.rb +2 -2
  98. data/lib/active_record/inheritance.rb +4 -2
  99. data/lib/active_record/insert_all.rb +18 -15
  100. data/lib/active_record/integration.rb +4 -1
  101. data/lib/active_record/internal_metadata.rb +48 -34
  102. data/lib/active_record/locking/optimistic.rb +7 -6
  103. data/lib/active_record/log_subscriber.rb +0 -21
  104. data/lib/active_record/message_pack.rb +1 -1
  105. data/lib/active_record/migration/command_recorder.rb +2 -3
  106. data/lib/active_record/migration/compatibility.rb +5 -3
  107. data/lib/active_record/migration/default_strategy.rb +4 -5
  108. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  109. data/lib/active_record/migration.rb +85 -76
  110. data/lib/active_record/model_schema.rb +31 -68
  111. data/lib/active_record/nested_attributes.rb +11 -3
  112. data/lib/active_record/normalization.rb +3 -7
  113. data/lib/active_record/persistence.rb +30 -352
  114. data/lib/active_record/query_cache.rb +19 -8
  115. data/lib/active_record/query_logs.rb +15 -0
  116. data/lib/active_record/querying.rb +21 -9
  117. data/lib/active_record/railtie.rb +37 -55
  118. data/lib/active_record/railties/controller_runtime.rb +13 -4
  119. data/lib/active_record/railties/databases.rake +40 -43
  120. data/lib/active_record/reflection.rb +98 -36
  121. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  122. data/lib/active_record/relation/batches.rb +14 -8
  123. data/lib/active_record/relation/calculations.rb +96 -63
  124. data/lib/active_record/relation/delegation.rb +8 -11
  125. data/lib/active_record/relation/finder_methods.rb +16 -2
  126. data/lib/active_record/relation/merger.rb +4 -6
  127. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  128. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  129. data/lib/active_record/relation/predicate_builder.rb +3 -3
  130. data/lib/active_record/relation/query_methods.rb +224 -58
  131. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  132. data/lib/active_record/relation/spawn_methods.rb +2 -18
  133. data/lib/active_record/relation/where_clause.rb +7 -19
  134. data/lib/active_record/relation.rb +496 -72
  135. data/lib/active_record/result.rb +31 -44
  136. data/lib/active_record/runtime_registry.rb +39 -0
  137. data/lib/active_record/sanitization.rb +24 -19
  138. data/lib/active_record/schema.rb +8 -6
  139. data/lib/active_record/schema_dumper.rb +19 -9
  140. data/lib/active_record/schema_migration.rb +30 -14
  141. data/lib/active_record/scoping/named.rb +1 -0
  142. data/lib/active_record/signed_id.rb +20 -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 +69 -41
  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 +86 -89
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +2 -2
  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 +132 -0
  155. data/lib/active_record/transactions.rb +70 -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 +15 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +148 -39
  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/sqlite.rb +25 -0
  182. data/lib/arel/visitors/to_sql.rb +29 -16
  183. data/lib/arel.rb +7 -3
  184. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  185. metadata +16 -10
@@ -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,105 @@ 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
+
187
+ module ExecutorHooks # :nodoc:
188
+ class << self
189
+ def run
190
+ # noop
191
+ end
192
+
193
+ def complete(_)
194
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
195
+ if (connection = pool.active_connection?)
196
+ transaction = connection.current_transaction
197
+ if transaction.closed? || !transaction.joinable?
198
+ pool.release_connection
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ class << self
207
+ def install_executor_hooks(executor = ActiveSupport::Executor)
208
+ executor.register_hook(ExecutorHooks)
209
+ end
210
+ end
211
+
105
212
  include MonitorMixin
106
- include QueryCache::ConnectionPoolConfiguration
213
+ prepend QueryCache::ConnectionPoolConfiguration
107
214
  include ConnectionAdapters::AbstractPool
108
215
 
109
216
  attr_accessor :automatic_reconnect, :checkout_timeout
110
217
  attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
111
218
 
112
- delegate :schema_reflection, :schema_reflection=, to: :pool_config
219
+ delegate :schema_reflection, :server_version, to: :pool_config
113
220
 
114
221
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
222
  # object which describes database connection information (e.g. adapter,
@@ -137,9 +244,9 @@ module ActiveRecord
137
244
  # then that +thread+ does indeed own that +conn+. However, an absence of such
138
245
  # mapping does not mean that the +thread+ doesn't own the said connection. In
139
246
  # that case +conn.owner+ attr should be consulted.
140
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
247
+ # Access and modification of <tt>@leases</tt> does not require
141
248
  # synchronization.
142
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
249
+ @leases = LeaseRegistry.new
143
250
 
144
251
  @connections = []
145
252
  @automatic_reconnect = true
@@ -152,86 +259,176 @@ module ActiveRecord
152
259
  @threads_blocking_new_connections = 0
153
260
 
154
261
  @available = ConnectionLeasingQueue.new self
155
-
156
- @lock_thread = false
262
+ @pinned_connection = nil
263
+ @pinned_connections_depth = 0
157
264
 
158
265
  @async_executor = build_async_executor
159
266
 
267
+ @schema_cache = nil
268
+
160
269
  @reaper = Reaper.new(self, db_config.reaping_frequency)
161
270
  @reaper.run
162
271
  end
163
272
 
164
- def lock_thread=(lock_thread)
165
- if lock_thread
166
- @lock_thread = ActiveSupport::IsolatedExecutionState.context
167
- else
168
- @lock_thread = nil
169
- end
273
+ def inspect # :nodoc:
274
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
275
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
170
276
 
171
- if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
- active_connection.lock_thread = @lock_thread
173
- end
277
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
278
+ end
279
+
280
+ def schema_cache
281
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
282
+ end
283
+
284
+ def schema_reflection=(schema_reflection)
285
+ pool_config.schema_reflection = schema_reflection
286
+ @schema_cache = nil
287
+ end
288
+
289
+ def migration_context # :nodoc:
290
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
291
+ end
292
+
293
+ def migrations_paths # :nodoc:
294
+ db_config.migrations_paths || Migrator.migrations_paths
295
+ end
296
+
297
+ def schema_migration # :nodoc:
298
+ SchemaMigration.new(self)
299
+ end
300
+
301
+ def internal_metadata # :nodoc:
302
+ InternalMetadata.new(self)
174
303
  end
175
304
 
176
305
  # Retrieve the connection associated with the current thread, or call
177
306
  # #checkout to obtain one if necessary.
178
307
  #
179
- # #connection can be called any number of times; the connection is
308
+ # #lease_connection can be called any number of times; the connection is
180
309
  # held in a cache keyed by a thread.
310
+ def lease_connection
311
+ lease = connection_lease
312
+ lease.sticky = true
313
+ lease.connection ||= checkout
314
+ end
315
+
316
+ def permanent_lease? # :nodoc:
317
+ connection_lease.sticky.nil?
318
+ end
319
+
181
320
  def connection
182
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
321
+ ActiveRecord.deprecator.warn(<<~MSG)
322
+ ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
323
+ and will be removed in Rails 8.0. Use #lease_connection instead.
324
+ MSG
325
+ lease_connection
326
+ end
327
+
328
+ def pin_connection!(lock_thread) # :nodoc:
329
+ @pinned_connection ||= (connection_lease&.connection || checkout)
330
+ @pinned_connections_depth += 1
331
+
332
+ # Any leased connection must be in @connections otherwise
333
+ # some methods like #connected? won't behave correctly
334
+ unless @connections.include?(@pinned_connection)
335
+ @connections << @pinned_connection
336
+ end
337
+
338
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
339
+ @pinned_connection.verify! # eagerly validate the connection
340
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
341
+ end
342
+
343
+ def unpin_connection! # :nodoc:
344
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
345
+
346
+ clean = true
347
+ @pinned_connection.lock.synchronize do
348
+ @pinned_connections_depth -= 1
349
+ connection = @pinned_connection
350
+ @pinned_connection = nil if @pinned_connections_depth.zero?
351
+
352
+ if connection.transaction_open?
353
+ connection.rollback_transaction
354
+ else
355
+ # Something committed or rolled back the transaction
356
+ clean = false
357
+ connection.reset!
358
+ end
359
+
360
+ if @pinned_connection.nil?
361
+ connection.steal!
362
+ connection.lock_thread = nil
363
+ checkin(connection)
364
+ end
365
+ end
366
+
367
+ clean
183
368
  end
184
369
 
185
370
  def connection_class # :nodoc:
186
371
  pool_config.connection_class
187
372
  end
188
- alias :connection_klass :connection_class
189
- deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
373
 
191
374
  # Returns true if there is an open connection being used for the current thread.
192
375
  #
193
376
  # This method only works for connections that have been obtained through
194
- # #connection or #with_connection methods. Connections obtained through
377
+ # #lease_connection or #with_connection methods. Connections obtained through
195
378
  # #checkout will not be detected by #active_connection?
196
379
  def active_connection?
197
- @thread_cached_conns[connection_cache_key(current_thread)]
380
+ connection_lease.connection
198
381
  end
382
+ alias_method :active_connection, :active_connection? # :nodoc:
199
383
 
200
384
  # Signal that the thread is finished with the current connection.
201
385
  # #release_connection releases the connection-thread association
202
386
  # and returns the connection to the pool.
203
387
  #
204
388
  # This method only works for connections that have been obtained through
205
- # #connection or #with_connection methods, connections obtained through
389
+ # #lease_connection or #with_connection methods, connections obtained through
206
390
  # #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))
391
+ def release_connection(existing_lease = nil)
392
+ if conn = connection_lease.release
209
393
  checkin conn
394
+ return true
210
395
  end
396
+ false
211
397
  end
212
398
 
213
399
  # Yields a connection from the connection pool to the block. If no connection
214
400
  # is already checked out by the current thread, a connection will be checked
215
401
  # out from the pool, yielded to the block, and then returned to the pool when
216
402
  # 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
403
+ # current thread, such as via #lease_connection or #with_connection, that existing
218
404
  # connection will be the one yielded and it will not be returned to the pool
219
405
  # automatically at the end of the block; it is expected that such an existing
220
406
  # connection will be properly returned to the pool by the code that checked
221
407
  # 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
408
+ def with_connection(prevent_permanent_checkout: false)
409
+ lease = connection_lease
410
+ sticky_was = lease.sticky
411
+ lease.sticky = false if prevent_permanent_checkout
412
+
413
+ if lease.connection
414
+ begin
415
+ yield lease.connection
416
+ ensure
417
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
418
+ end
419
+ else
420
+ begin
421
+ yield lease.connection = checkout
422
+ ensure
423
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
424
+ release_connection(lease) unless lease.sticky
425
+ end
226
426
  end
227
- yield conn
228
- ensure
229
- release_connection if fresh_connection
230
427
  end
231
428
 
232
429
  # Returns true if a connection has already been opened.
233
430
  def connected?
234
- synchronize { @connections.any? }
431
+ synchronize { @connections.any?(&:connected?) }
235
432
  end
236
433
 
237
434
  # Returns an array containing the connections currently in the pool.
@@ -266,6 +463,7 @@ module ActiveRecord
266
463
  conn.disconnect!
267
464
  end
268
465
  @connections = []
466
+ @leases.clear
269
467
  @available.clear
270
468
  end
271
469
  end
@@ -292,7 +490,7 @@ module ActiveRecord
292
490
  @connections.each do |conn|
293
491
  conn.discard!
294
492
  end
295
- @connections = @available = @thread_cached_conns = nil
493
+ @connections = @available = @leases = nil
296
494
  end
297
495
  end
298
496
 
@@ -350,9 +548,21 @@ module ActiveRecord
350
548
  # Raises:
351
549
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
352
550
  def checkout(checkout_timeout = @checkout_timeout)
353
- connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
- connection.lock_thread = @lock_thread
355
- connection
551
+ if @pinned_connection
552
+ @pinned_connection.lock.synchronize do
553
+ synchronize do
554
+ @pinned_connection.verify!
555
+ # Any leased connection must be in @connections otherwise
556
+ # some methods like #connected? won't behave correctly
557
+ unless @connections.include?(@pinned_connection)
558
+ @connections << @pinned_connection
559
+ end
560
+ end
561
+ end
562
+ @pinned_connection
563
+ else
564
+ checkout_and_verify(acquire_connection(checkout_timeout))
565
+ end
356
566
  end
357
567
 
358
568
  # Check-in a database connection back into the pool, indicating that you
@@ -361,15 +571,16 @@ module ActiveRecord
361
571
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
362
572
  # calling #checkout on this pool.
363
573
  def checkin(conn)
574
+ return if @pinned_connection.equal?(conn)
575
+
364
576
  conn.lock.synchronize do
365
577
  synchronize do
366
- remove_connection_from_thread_cache conn
578
+ connection_lease.clear(conn)
367
579
 
368
580
  conn._run_checkin_callbacks do
369
581
  conn.expire
370
582
  end
371
583
 
372
- conn.lock_thread = nil
373
584
  @available.add conn
374
585
  end
375
586
  end
@@ -486,6 +697,10 @@ module ActiveRecord
486
697
  end
487
698
 
488
699
  private
700
+ def connection_lease
701
+ @leases[ActiveSupport::IsolatedExecutionState.context]
702
+ end
703
+
489
704
  def build_async_executor
490
705
  case ActiveRecord.async_query_executor
491
706
  when :multi_thread_pool
@@ -514,19 +729,6 @@ module ActiveRecord
514
729
  end
515
730
  end
516
731
 
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
732
  # Take control of all existing connections so a "group" action such as
531
733
  # reload/disconnect can be performed safely. It is no longer enough to
532
734
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -540,6 +742,8 @@ module ActiveRecord
540
742
 
541
743
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
542
744
  collected_conns = synchronize do
745
+ reap # No need to wait for dead owners
746
+
543
747
  # account for our own connections
544
748
  @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
545
749
  end
@@ -551,6 +755,7 @@ module ActiveRecord
551
755
  loop do
552
756
  synchronize do
553
757
  return if collected_conns.size == @connections.size && @now_connecting == 0
758
+
554
759
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
555
760
  remaining_timeout = 0 if remaining_timeout < 0
556
761
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -601,7 +806,7 @@ module ActiveRecord
601
806
 
602
807
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
603
808
 
604
- raise ExclusiveConnectionTimeoutError, msg
809
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
605
810
  end
606
811
 
607
812
  def with_new_connections_blocked
@@ -663,26 +868,20 @@ module ActiveRecord
663
868
  @available.poll(checkout_timeout)
664
869
  end
665
870
  end
871
+ rescue ConnectionTimeoutError => ex
872
+ raise ex.set_pool(self)
666
873
  end
667
874
 
668
875
  #--
669
876
  # if owner_thread param is omitted, this must be called in synchronize block
670
877
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
671
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
878
+ @leases[owner_thread].clear(conn)
672
879
  end
673
880
  alias_method :release, :remove_connection_from_thread_cache
674
881
 
675
882
  def new_connection
676
- connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
883
+ connection = db_config.new_connection
677
884
  connection.pool = self
678
-
679
- begin
680
- connection.check_version
681
- rescue
682
- connection.disconnect!
683
- raise
684
- end
685
-
686
885
  connection
687
886
  rescue ConnectionNotEstablished => ex
688
887
  raise ex.set_pool(self)
@@ -723,6 +922,12 @@ module ActiveRecord
723
922
  def adopt_connection(conn)
724
923
  conn.pool = self
725
924
  @connections << conn
925
+
926
+ # We just created the first connection, it's time to load the schema
927
+ # cache if that wasn't eagerly done before
928
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
929
+ schema_cache.load!
930
+ end
726
931
  end
727
932
 
728
933
  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,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