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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- 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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -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 +10 -3
- 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 +4 -3
- 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 +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- 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 +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- 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 +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -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 +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -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 +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- 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/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -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 +72 -17
- 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 +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -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 +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- 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
|
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.
|
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
|
-
|
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, :
|
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>@
|
252
|
+
# Access and modification of <tt>@leases</tt> does not require
|
141
253
|
# synchronization.
|
142
|
-
@
|
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
|
-
@
|
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
|
165
|
-
|
166
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
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
|
-
# #
|
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
|
182
|
-
|
315
|
+
def lease_connection
|
316
|
+
lease = connection_lease
|
317
|
+
lease.sticky = true
|
318
|
+
lease.connection ||= checkout
|
183
319
|
end
|
184
320
|
|
185
|
-
def
|
186
|
-
|
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
|
-
# #
|
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
|
-
|
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
|
-
# #
|
386
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
206
387
|
# #checkout will not be automatically released.
|
207
|
-
def release_connection(
|
208
|
-
if conn =
|
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 #
|
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
|
-
|
224
|
-
|
225
|
-
|
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 = @
|
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
|
-
|
354
|
-
|
355
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
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
|