activerecord 7.1.3.2 → 7.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 (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