activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
  require "monitor"
6
5
 
@@ -16,23 +15,39 @@ module ActiveRecord
16
15
  include ConnectionAdapters::AbstractPool
17
16
 
18
17
  class NullConfig # :nodoc:
19
- def method_missing(*)
18
+ def method_missing(...)
20
19
  nil
21
20
  end
22
21
  end
23
22
  NULL_CONFIG = NullConfig.new # :nodoc:
24
23
 
24
+ def initialize
25
+ super()
26
+ @mutex = Mutex.new
27
+ @server_version = nil
28
+ end
29
+
30
+ def server_version(connection) # :nodoc:
31
+ @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
32
+ end
33
+
25
34
  def schema_reflection
26
35
  SchemaReflection.new(nil)
27
36
  end
28
37
 
29
- def connection_class; end
38
+ def schema_cache; end
39
+ def connection_descriptor; end
30
40
  def checkin(_); end
31
41
  def remove(_); end
32
42
  def async_executor; end
43
+
33
44
  def db_config
34
45
  NULL_CONFIG
35
46
  end
47
+
48
+ def dirties_query_cache
49
+ true
50
+ end
36
51
  end
37
52
 
38
53
  # = Active Record Connection Pool
@@ -58,7 +73,7 @@ module ActiveRecord
58
73
  # Connections can be obtained and used from a connection pool in several
59
74
  # ways:
60
75
  #
61
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection].
76
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
62
77
  # When you're done with the connection(s) and wish it to be returned to the pool, you call
63
78
  # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
64
79
  # This is the default behavior for Active Record when used in conjunction with
@@ -102,14 +117,111 @@ module ActiveRecord
102
117
  # * private methods that require being called in a +synchronize+ blocks
103
118
  # are now explicitly documented
104
119
  class ConnectionPool
120
+ # Prior to 3.3.5, WeakKeyMap had a use after free bug
121
+ # https://bugs.ruby-lang.org/issues/20688
122
+ if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
123
+ WeakThreadKeyMap = ObjectSpace::WeakKeyMap
124
+ else
125
+ class WeakThreadKeyMap # :nodoc:
126
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
127
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
128
+ def initialize
129
+ @map = {}
130
+ end
131
+
132
+ def clear
133
+ @map.clear
134
+ end
135
+
136
+ def [](key)
137
+ @map[key]
138
+ end
139
+
140
+ def []=(key, value)
141
+ @map.select! { |c, _| c&.alive? }
142
+ @map[key] = value
143
+ end
144
+ end
145
+ end
146
+
147
+ class Lease # :nodoc:
148
+ attr_accessor :connection, :sticky
149
+
150
+ def initialize
151
+ @connection = nil
152
+ @sticky = nil
153
+ end
154
+
155
+ def release
156
+ conn = @connection
157
+ @connection = nil
158
+ @sticky = nil
159
+ conn
160
+ end
161
+
162
+ def clear(connection)
163
+ if @connection == connection
164
+ @connection = nil
165
+ @sticky = nil
166
+ true
167
+ else
168
+ false
169
+ end
170
+ end
171
+ end
172
+
173
+ class LeaseRegistry # :nodoc:
174
+ def initialize
175
+ @mutex = Mutex.new
176
+ @map = WeakThreadKeyMap.new
177
+ end
178
+
179
+ def [](context)
180
+ @mutex.synchronize do
181
+ @map[context] ||= Lease.new
182
+ end
183
+ end
184
+
185
+ def clear
186
+ @mutex.synchronize do
187
+ @map.clear
188
+ end
189
+ end
190
+ end
191
+
192
+ module ExecutorHooks # :nodoc:
193
+ class << self
194
+ def run
195
+ # noop
196
+ end
197
+
198
+ def complete(_)
199
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
200
+ if (connection = pool.active_connection?)
201
+ transaction = connection.current_transaction
202
+ if transaction.closed? || !transaction.joinable?
203
+ pool.release_connection
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ class << self
212
+ def install_executor_hooks(executor = ActiveSupport::Executor)
213
+ executor.register_hook(ExecutorHooks)
214
+ end
215
+ end
216
+
105
217
  include MonitorMixin
106
- include QueryCache::ConnectionPoolConfiguration
218
+ prepend QueryCache::ConnectionPoolConfiguration
107
219
  include ConnectionAdapters::AbstractPool
108
220
 
109
221
  attr_accessor :automatic_reconnect, :checkout_timeout
110
222
  attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
111
223
 
112
- delegate :schema_reflection, :schema_reflection=, to: :pool_config
224
+ delegate :schema_reflection, :server_version, to: :pool_config
113
225
 
114
226
  # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
115
227
  # object which describes database connection information (e.g. adapter,
@@ -137,9 +249,9 @@ module ActiveRecord
137
249
  # then that +thread+ does indeed own that +conn+. However, an absence of such
138
250
  # mapping does not mean that the +thread+ doesn't own the said connection. In
139
251
  # that case +conn.owner+ attr should be consulted.
140
- # Access and modification of <tt>@thread_cached_conns</tt> does not require
252
+ # Access and modification of <tt>@leases</tt> does not require
141
253
  # synchronization.
142
- @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
254
+ @leases = LeaseRegistry.new
143
255
 
144
256
  @connections = []
145
257
  @automatic_reconnect = true
@@ -152,86 +264,168 @@ module ActiveRecord
152
264
  @threads_blocking_new_connections = 0
153
265
 
154
266
  @available = ConnectionLeasingQueue.new self
155
-
156
- @lock_thread = false
267
+ @pinned_connection = nil
268
+ @pinned_connections_depth = 0
157
269
 
158
270
  @async_executor = build_async_executor
159
271
 
272
+ @schema_cache = nil
273
+
160
274
  @reaper = Reaper.new(self, db_config.reaping_frequency)
161
275
  @reaper.run
162
276
  end
163
277
 
164
- def lock_thread=(lock_thread)
165
- if lock_thread
166
- @lock_thread = ActiveSupport::IsolatedExecutionState.context
167
- else
168
- @lock_thread = nil
169
- end
278
+ def inspect # :nodoc:
279
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
280
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
170
281
 
171
- if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
172
- active_connection.lock_thread = @lock_thread
173
- end
282
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
283
+ end
284
+
285
+ def schema_cache
286
+ @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
287
+ end
288
+
289
+ def schema_reflection=(schema_reflection)
290
+ pool_config.schema_reflection = schema_reflection
291
+ @schema_cache = nil
292
+ end
293
+
294
+ def migration_context # :nodoc:
295
+ MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
296
+ end
297
+
298
+ def migrations_paths # :nodoc:
299
+ db_config.migrations_paths || Migrator.migrations_paths
300
+ end
301
+
302
+ def schema_migration # :nodoc:
303
+ SchemaMigration.new(self)
304
+ end
305
+
306
+ def internal_metadata # :nodoc:
307
+ InternalMetadata.new(self)
174
308
  end
175
309
 
176
310
  # Retrieve the connection associated with the current thread, or call
177
311
  # #checkout to obtain one if necessary.
178
312
  #
179
- # #connection can be called any number of times; the connection is
313
+ # #lease_connection can be called any number of times; the connection is
180
314
  # held in a cache keyed by a thread.
181
- def connection
182
- @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
315
+ def lease_connection
316
+ lease = connection_lease
317
+ lease.sticky = true
318
+ lease.connection ||= checkout
183
319
  end
184
320
 
185
- def connection_class # :nodoc:
186
- pool_config.connection_class
321
+ def permanent_lease? # :nodoc:
322
+ connection_lease.sticky.nil?
323
+ end
324
+
325
+ def pin_connection!(lock_thread) # :nodoc:
326
+ @pinned_connection ||= (connection_lease&.connection || checkout)
327
+ @pinned_connections_depth += 1
328
+
329
+ # Any leased connection must be in @connections otherwise
330
+ # some methods like #connected? won't behave correctly
331
+ unless @connections.include?(@pinned_connection)
332
+ @connections << @pinned_connection
333
+ end
334
+
335
+ @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
336
+ @pinned_connection.verify! # eagerly validate the connection
337
+ @pinned_connection.begin_transaction joinable: false, _lazy: false
338
+ end
339
+
340
+ def unpin_connection! # :nodoc:
341
+ raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
342
+
343
+ clean = true
344
+ @pinned_connection.lock.synchronize do
345
+ @pinned_connections_depth -= 1
346
+ connection = @pinned_connection
347
+ @pinned_connection = nil if @pinned_connections_depth.zero?
348
+
349
+ if connection.transaction_open?
350
+ connection.rollback_transaction
351
+ else
352
+ # Something committed or rolled back the transaction
353
+ clean = false
354
+ connection.reset!
355
+ end
356
+
357
+ if @pinned_connection.nil?
358
+ connection.steal!
359
+ connection.lock_thread = nil
360
+ checkin(connection)
361
+ end
362
+ end
363
+
364
+ clean
365
+ end
366
+
367
+ def connection_descriptor # :nodoc:
368
+ pool_config.connection_descriptor
187
369
  end
188
- alias :connection_klass :connection_class
189
- deprecate :connection_klass, deprecator: ActiveRecord.deprecator
190
370
 
191
371
  # Returns true if there is an open connection being used for the current thread.
192
372
  #
193
373
  # This method only works for connections that have been obtained through
194
- # #connection or #with_connection methods. Connections obtained through
374
+ # #lease_connection or #with_connection methods. Connections obtained through
195
375
  # #checkout will not be detected by #active_connection?
196
376
  def active_connection?
197
- @thread_cached_conns[connection_cache_key(current_thread)]
377
+ connection_lease.connection
198
378
  end
379
+ alias_method :active_connection, :active_connection? # :nodoc:
199
380
 
200
381
  # Signal that the thread is finished with the current connection.
201
382
  # #release_connection releases the connection-thread association
202
383
  # and returns the connection to the pool.
203
384
  #
204
385
  # This method only works for connections that have been obtained through
205
- # #connection or #with_connection methods, connections obtained through
386
+ # #lease_connection or #with_connection methods, connections obtained through
206
387
  # #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))
388
+ def release_connection(existing_lease = nil)
389
+ if conn = connection_lease.release
209
390
  checkin conn
391
+ return true
210
392
  end
393
+ false
211
394
  end
212
395
 
213
396
  # Yields a connection from the connection pool to the block. If no connection
214
397
  # is already checked out by the current thread, a connection will be checked
215
398
  # out from the pool, yielded to the block, and then returned to the pool when
216
399
  # 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
400
+ # current thread, such as via #lease_connection or #with_connection, that existing
218
401
  # connection will be the one yielded and it will not be returned to the pool
219
402
  # automatically at the end of the block; it is expected that such an existing
220
403
  # connection will be properly returned to the pool by the code that checked
221
404
  # 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
405
+ def with_connection(prevent_permanent_checkout: false)
406
+ lease = connection_lease
407
+ sticky_was = lease.sticky
408
+ lease.sticky = false if prevent_permanent_checkout
409
+
410
+ if lease.connection
411
+ begin
412
+ yield lease.connection
413
+ ensure
414
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
415
+ end
416
+ else
417
+ begin
418
+ yield lease.connection = checkout
419
+ ensure
420
+ lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
421
+ release_connection(lease) unless lease.sticky
422
+ end
226
423
  end
227
- yield conn
228
- ensure
229
- release_connection if fresh_connection
230
424
  end
231
425
 
232
426
  # Returns true if a connection has already been opened.
233
427
  def connected?
234
- synchronize { @connections.any? }
428
+ synchronize { @connections.any?(&:connected?) }
235
429
  end
236
430
 
237
431
  # Returns an array containing the connections currently in the pool.
@@ -266,6 +460,7 @@ module ActiveRecord
266
460
  conn.disconnect!
267
461
  end
268
462
  @connections = []
463
+ @leases.clear
269
464
  @available.clear
270
465
  end
271
466
  end
@@ -292,7 +487,7 @@ module ActiveRecord
292
487
  @connections.each do |conn|
293
488
  conn.discard!
294
489
  end
295
- @connections = @available = @thread_cached_conns = nil
490
+ @connections = @available = @leases = nil
296
491
  end
297
492
  end
298
493
 
@@ -350,9 +545,26 @@ module ActiveRecord
350
545
  # Raises:
351
546
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
352
547
  def checkout(checkout_timeout = @checkout_timeout)
353
- connection = checkout_and_verify(acquire_connection(checkout_timeout))
354
- connection.lock_thread = @lock_thread
355
- connection
548
+ return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
549
+
550
+ @pinned_connection.lock.synchronize do
551
+ synchronize do
552
+ # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
553
+ if @pinned_connection
554
+ @pinned_connection.verify!
555
+
556
+ # Any leased connection must be in @connections otherwise
557
+ # some methods like #connected? won't behave correctly
558
+ unless @connections.include?(@pinned_connection)
559
+ @connections << @pinned_connection
560
+ end
561
+
562
+ @pinned_connection
563
+ else
564
+ checkout_and_verify(acquire_connection(checkout_timeout))
565
+ end
566
+ end
567
+ end
356
568
  end
357
569
 
358
570
  # Check-in a database connection back into the pool, indicating that you
@@ -361,15 +573,16 @@ module ActiveRecord
361
573
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
362
574
  # calling #checkout on this pool.
363
575
  def checkin(conn)
576
+ return if @pinned_connection.equal?(conn)
577
+
364
578
  conn.lock.synchronize do
365
579
  synchronize do
366
- remove_connection_from_thread_cache conn
580
+ connection_lease.clear(conn)
367
581
 
368
582
  conn._run_checkin_callbacks do
369
583
  conn.expire
370
584
  end
371
585
 
372
- conn.lock_thread = nil
373
586
  @available.add conn
374
587
  end
375
588
  end
@@ -485,7 +698,19 @@ module ActiveRecord
485
698
  Thread.pass
486
699
  end
487
700
 
701
+ def new_connection # :nodoc:
702
+ connection = db_config.new_connection
703
+ connection.pool = self
704
+ connection
705
+ rescue ConnectionNotEstablished => ex
706
+ raise ex.set_pool(self)
707
+ end
708
+
488
709
  private
710
+ def connection_lease
711
+ @leases[ActiveSupport::IsolatedExecutionState.context]
712
+ end
713
+
489
714
  def build_async_executor
490
715
  case ActiveRecord.async_query_executor
491
716
  when :multi_thread_pool
@@ -514,19 +739,6 @@ module ActiveRecord
514
739
  end
515
740
  end
516
741
 
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
742
  # Take control of all existing connections so a "group" action such as
531
743
  # reload/disconnect can be performed safely. It is no longer enough to
532
744
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -540,6 +752,8 @@ module ActiveRecord
540
752
 
541
753
  def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
542
754
  collected_conns = synchronize do
755
+ reap # No need to wait for dead owners
756
+
543
757
  # account for our own connections
544
758
  @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
545
759
  end
@@ -551,6 +765,7 @@ module ActiveRecord
551
765
  loop do
552
766
  synchronize do
553
767
  return if collected_conns.size == @connections.size && @now_connecting == 0
768
+
554
769
  remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
555
770
  remaining_timeout = 0 if remaining_timeout < 0
556
771
  conn = checkout_for_exclusive_access(remaining_timeout)
@@ -601,7 +816,7 @@ module ActiveRecord
601
816
 
602
817
  msg << " (#{thread_report.join(', ')})" if thread_report.any?
603
818
 
604
- raise ExclusiveConnectionTimeoutError, msg
819
+ raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
605
820
  end
606
821
 
607
822
  def with_new_connections_blocked
@@ -663,30 +878,18 @@ module ActiveRecord
663
878
  @available.poll(checkout_timeout)
664
879
  end
665
880
  end
881
+ rescue ConnectionTimeoutError => ex
882
+ raise ex.set_pool(self)
666
883
  end
667
884
 
668
885
  #--
669
886
  # if owner_thread param is omitted, this must be called in synchronize block
670
887
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
671
- @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
672
- end
673
- alias_method :release, :remove_connection_from_thread_cache
674
-
675
- def new_connection
676
- connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
677
- connection.pool = self
678
-
679
- begin
680
- connection.check_version
681
- rescue
682
- connection.disconnect!
683
- raise
888
+ if owner_thread
889
+ @leases[owner_thread].clear(conn)
684
890
  end
685
-
686
- connection
687
- rescue ConnectionNotEstablished => ex
688
- raise ex.set_pool(self)
689
891
  end
892
+ alias_method :release, :remove_connection_from_thread_cache
690
893
 
691
894
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
692
895
  # to the DB is done outside main synchronized section.
@@ -723,6 +926,12 @@ module ActiveRecord
723
926
  def adopt_connection(conn)
724
927
  conn.pool = self
725
928
  @connections << conn
929
+
930
+ # We just created the first connection, it's time to load the schema
931
+ # cache if that wasn't eagerly done before
932
+ if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
933
+ schema_cache.load!
934
+ end
726
935
  end
727
936
 
728
937
  def checkout_new_connection