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