activerecord 7.1.5.1 → 7.2.0.beta1
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 +515 -2445
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- 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 +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- 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 +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- 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 +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- 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 +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -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/cidr.rb +1 -1
- 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 +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- 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 +44 -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 +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- 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 +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -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 +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- 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/marshalling.rb +1 -4
- 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 +85 -76
- data/lib/active_record/model_schema.rb +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- 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.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- 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 +7 -19
- data/lib/active_record/relation.rb +496 -72
- 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 +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- 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 +76 -70
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- 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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- 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/nodes/binary.rb +0 -6
- 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 +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- 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/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/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -15
@@ -16,23 +16,39 @@ module ActiveRecord
|
|
16
16
|
include ConnectionAdapters::AbstractPool
|
17
17
|
|
18
18
|
class NullConfig # :nodoc:
|
19
|
-
def method_missing(
|
19
|
+
def method_missing(...)
|
20
20
|
nil
|
21
21
|
end
|
22
22
|
end
|
23
23
|
NULL_CONFIG = NullConfig.new # :nodoc:
|
24
24
|
|
25
|
+
def initialize
|
26
|
+
super()
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@server_version = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def server_version(connection) # :nodoc:
|
32
|
+
@server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
|
33
|
+
end
|
34
|
+
|
25
35
|
def schema_reflection
|
26
36
|
SchemaReflection.new(nil)
|
27
37
|
end
|
28
38
|
|
39
|
+
def schema_cache; end
|
29
40
|
def connection_class; end
|
30
41
|
def checkin(_); end
|
31
42
|
def remove(_); end
|
32
43
|
def async_executor; end
|
44
|
+
|
33
45
|
def db_config
|
34
46
|
NULL_CONFIG
|
35
47
|
end
|
48
|
+
|
49
|
+
def dirties_query_cache
|
50
|
+
true
|
51
|
+
end
|
36
52
|
end
|
37
53
|
|
38
54
|
# = Active Record Connection Pool
|
@@ -58,7 +74,7 @@ module ActiveRecord
|
|
58
74
|
# Connections can be obtained and used from a connection pool in several
|
59
75
|
# ways:
|
60
76
|
#
|
61
|
-
# 1. Simply use {ActiveRecord::Base.
|
77
|
+
# 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling.connection].
|
62
78
|
# When you're done with the connection(s) and wish it to be returned to the pool, you call
|
63
79
|
# {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
64
80
|
# This is the default behavior for Active Record when used in conjunction with
|
@@ -102,14 +118,98 @@ module ActiveRecord
|
|
102
118
|
# * private methods that require being called in a +synchronize+ blocks
|
103
119
|
# are now explicitly documented
|
104
120
|
class ConnectionPool
|
121
|
+
class Lease # :nodoc:
|
122
|
+
attr_accessor :connection, :sticky
|
123
|
+
|
124
|
+
def initialize
|
125
|
+
@connection = nil
|
126
|
+
@sticky = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def release
|
130
|
+
conn = @connection
|
131
|
+
@connection = nil
|
132
|
+
@sticky = nil
|
133
|
+
conn
|
134
|
+
end
|
135
|
+
|
136
|
+
def clear(connection)
|
137
|
+
if @connection == connection
|
138
|
+
@connection = nil
|
139
|
+
@sticky = nil
|
140
|
+
true
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class LeaseRegistry # :nodoc:
|
148
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) # RUBY_VERSION >= 3.3
|
149
|
+
WeakKeyMap = ::ObjectSpace::WeakKeyMap # :nodoc:
|
150
|
+
else
|
151
|
+
class WeakKeyMap # :nodoc:
|
152
|
+
def initialize
|
153
|
+
@map = ObjectSpace::WeakMap.new
|
154
|
+
@values = nil
|
155
|
+
@size = 0
|
156
|
+
end
|
157
|
+
|
158
|
+
alias_method :clear, :initialize
|
159
|
+
|
160
|
+
def [](key)
|
161
|
+
prune if @map.size != @size
|
162
|
+
@map[key]
|
163
|
+
end
|
164
|
+
|
165
|
+
def []=(key, value)
|
166
|
+
@map[key] = value
|
167
|
+
prune if @map.size != @size
|
168
|
+
value
|
169
|
+
end
|
170
|
+
|
171
|
+
def delete(key)
|
172
|
+
if value = self[key]
|
173
|
+
self[key] = nil
|
174
|
+
prune
|
175
|
+
end
|
176
|
+
value
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def prune(force = false)
|
181
|
+
@values = @map.values
|
182
|
+
@size = @map.size
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def initialize
|
188
|
+
@mutex = Mutex.new
|
189
|
+
@map = WeakKeyMap.new
|
190
|
+
end
|
191
|
+
|
192
|
+
def [](context)
|
193
|
+
@mutex.synchronize do
|
194
|
+
@map[context] ||= Lease.new
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def clear
|
199
|
+
@mutex.synchronize do
|
200
|
+
@map = WeakKeyMap.new
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
105
205
|
include MonitorMixin
|
106
|
-
|
206
|
+
prepend QueryCache::ConnectionPoolConfiguration
|
107
207
|
include ConnectionAdapters::AbstractPool
|
108
208
|
|
109
209
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
110
210
|
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
|
111
211
|
|
112
|
-
delegate :schema_reflection, :
|
212
|
+
delegate :schema_reflection, :server_version, to: :pool_config
|
113
213
|
|
114
214
|
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
115
215
|
# object which describes database connection information (e.g. adapter,
|
@@ -137,9 +237,9 @@ module ActiveRecord
|
|
137
237
|
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
138
238
|
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
139
239
|
# that case +conn.owner+ attr should be consulted.
|
140
|
-
# Access and modification of <tt>@
|
240
|
+
# Access and modification of <tt>@leases</tt> does not require
|
141
241
|
# synchronization.
|
142
|
-
@
|
242
|
+
@leases = LeaseRegistry.new
|
143
243
|
|
144
244
|
@connections = []
|
145
245
|
@automatic_reconnect = true
|
@@ -152,86 +252,161 @@ module ActiveRecord
|
|
152
252
|
@threads_blocking_new_connections = 0
|
153
253
|
|
154
254
|
@available = ConnectionLeasingQueue.new self
|
155
|
-
|
156
|
-
@lock_thread = false
|
255
|
+
@pinned_connection = nil
|
157
256
|
|
158
257
|
@async_executor = build_async_executor
|
159
258
|
|
259
|
+
@schema_cache = nil
|
260
|
+
|
160
261
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
161
262
|
@reaper.run
|
162
263
|
end
|
163
264
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
167
|
-
else
|
168
|
-
@lock_thread = nil
|
169
|
-
end
|
265
|
+
def schema_cache
|
266
|
+
@schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
|
267
|
+
end
|
170
268
|
|
171
|
-
|
172
|
-
|
173
|
-
|
269
|
+
def schema_reflection=(schema_reflection)
|
270
|
+
pool_config.schema_reflection = schema_reflection
|
271
|
+
@schema_cache = nil
|
272
|
+
end
|
273
|
+
|
274
|
+
def migration_context # :nodoc:
|
275
|
+
MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
|
276
|
+
end
|
277
|
+
|
278
|
+
def migrations_paths # :nodoc:
|
279
|
+
db_config.migrations_paths || Migrator.migrations_paths
|
280
|
+
end
|
281
|
+
|
282
|
+
def schema_migration # :nodoc:
|
283
|
+
SchemaMigration.new(self)
|
284
|
+
end
|
285
|
+
|
286
|
+
def internal_metadata # :nodoc:
|
287
|
+
InternalMetadata.new(self)
|
174
288
|
end
|
175
289
|
|
176
290
|
# Retrieve the connection associated with the current thread, or call
|
177
291
|
# #checkout to obtain one if necessary.
|
178
292
|
#
|
179
|
-
# #
|
293
|
+
# #lease_connection can be called any number of times; the connection is
|
180
294
|
# held in a cache keyed by a thread.
|
295
|
+
def lease_connection
|
296
|
+
lease = connection_lease
|
297
|
+
lease.sticky = true
|
298
|
+
lease.connection ||= checkout
|
299
|
+
end
|
300
|
+
|
301
|
+
def permanent_lease? # :nodoc:
|
302
|
+
connection_lease.sticky.nil?
|
303
|
+
end
|
304
|
+
|
181
305
|
def connection
|
182
|
-
|
306
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
307
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
308
|
+
and will be removed in Rails 7.3. Use #lease_connection instead.
|
309
|
+
MSG
|
310
|
+
lease_connection
|
311
|
+
end
|
312
|
+
|
313
|
+
def pin_connection!(lock_thread) # :nodoc:
|
314
|
+
raise "There is already a pinned connection" if @pinned_connection
|
315
|
+
|
316
|
+
@pinned_connection = (connection_lease&.connection || checkout)
|
317
|
+
# Any leased connection must be in @connections otherwise
|
318
|
+
# some methods like #connected? won't behave correctly
|
319
|
+
unless @connections.include?(@pinned_connection)
|
320
|
+
@connections << @pinned_connection
|
321
|
+
end
|
322
|
+
|
323
|
+
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
324
|
+
@pinned_connection.verify! # eagerly validate the connection
|
325
|
+
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
326
|
+
end
|
327
|
+
|
328
|
+
def unpin_connection! # :nodoc:
|
329
|
+
raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
|
330
|
+
|
331
|
+
clean = true
|
332
|
+
@pinned_connection.lock.synchronize do
|
333
|
+
connection, @pinned_connection = @pinned_connection, nil
|
334
|
+
if connection.transaction_open?
|
335
|
+
connection.rollback_transaction
|
336
|
+
else
|
337
|
+
# Something committed or rolled back the transaction
|
338
|
+
clean = false
|
339
|
+
connection.reset!
|
340
|
+
end
|
341
|
+
connection.lock_thread = nil
|
342
|
+
checkin(connection)
|
343
|
+
end
|
344
|
+
|
345
|
+
clean
|
183
346
|
end
|
184
347
|
|
185
348
|
def connection_class # :nodoc:
|
186
349
|
pool_config.connection_class
|
187
350
|
end
|
188
|
-
alias :connection_klass :connection_class
|
189
|
-
deprecate :connection_klass, deprecator: ActiveRecord.deprecator
|
190
351
|
|
191
352
|
# Returns true if there is an open connection being used for the current thread.
|
192
353
|
#
|
193
354
|
# This method only works for connections that have been obtained through
|
194
|
-
# #
|
355
|
+
# #lease_connection or #with_connection methods. Connections obtained through
|
195
356
|
# #checkout will not be detected by #active_connection?
|
196
357
|
def active_connection?
|
197
|
-
|
358
|
+
connection_lease.connection
|
198
359
|
end
|
360
|
+
alias_method :active_connection, :active_connection? # :nodoc:
|
199
361
|
|
200
362
|
# Signal that the thread is finished with the current connection.
|
201
363
|
# #release_connection releases the connection-thread association
|
202
364
|
# and returns the connection to the pool.
|
203
365
|
#
|
204
366
|
# This method only works for connections that have been obtained through
|
205
|
-
# #
|
367
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
206
368
|
# #checkout will not be automatically released.
|
207
|
-
def release_connection(
|
208
|
-
if conn =
|
369
|
+
def release_connection(existing_lease = nil)
|
370
|
+
if conn = connection_lease.release
|
209
371
|
checkin conn
|
372
|
+
return true
|
210
373
|
end
|
374
|
+
false
|
211
375
|
end
|
212
376
|
|
213
377
|
# Yields a connection from the connection pool to the block. If no connection
|
214
378
|
# is already checked out by the current thread, a connection will be checked
|
215
379
|
# out from the pool, yielded to the block, and then returned to the pool when
|
216
380
|
# the block is finished. If a connection has already been checked out on the
|
217
|
-
# current thread, such as via #
|
381
|
+
# current thread, such as via #lease_connection or #with_connection, that existing
|
218
382
|
# connection will be the one yielded and it will not be returned to the pool
|
219
383
|
# automatically at the end of the block; it is expected that such an existing
|
220
384
|
# connection will be properly returned to the pool by the code that checked
|
221
385
|
# it out.
|
222
|
-
def with_connection
|
223
|
-
|
224
|
-
|
225
|
-
|
386
|
+
def with_connection(prevent_permanent_checkout: false)
|
387
|
+
lease = connection_lease
|
388
|
+
sticky_was = lease.sticky
|
389
|
+
lease.sticky = false if prevent_permanent_checkout
|
390
|
+
|
391
|
+
if lease.connection
|
392
|
+
begin
|
393
|
+
yield lease.connection
|
394
|
+
ensure
|
395
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
396
|
+
end
|
397
|
+
else
|
398
|
+
begin
|
399
|
+
yield lease.connection = checkout
|
400
|
+
ensure
|
401
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
402
|
+
release_connection(lease) unless lease.sticky
|
403
|
+
end
|
226
404
|
end
|
227
|
-
yield conn
|
228
|
-
ensure
|
229
|
-
release_connection if fresh_connection
|
230
405
|
end
|
231
406
|
|
232
407
|
# Returns true if a connection has already been opened.
|
233
408
|
def connected?
|
234
|
-
synchronize { @connections.any? }
|
409
|
+
synchronize { @connections.any?(&:connected?) }
|
235
410
|
end
|
236
411
|
|
237
412
|
# Returns an array containing the connections currently in the pool.
|
@@ -266,6 +441,7 @@ module ActiveRecord
|
|
266
441
|
conn.disconnect!
|
267
442
|
end
|
268
443
|
@connections = []
|
444
|
+
@leases.clear
|
269
445
|
@available.clear
|
270
446
|
end
|
271
447
|
end
|
@@ -292,7 +468,7 @@ module ActiveRecord
|
|
292
468
|
@connections.each do |conn|
|
293
469
|
conn.discard!
|
294
470
|
end
|
295
|
-
@connections = @available = @
|
471
|
+
@connections = @available = @leases = nil
|
296
472
|
end
|
297
473
|
end
|
298
474
|
|
@@ -350,9 +526,19 @@ module ActiveRecord
|
|
350
526
|
# Raises:
|
351
527
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
352
528
|
def checkout(checkout_timeout = @checkout_timeout)
|
353
|
-
|
354
|
-
|
355
|
-
|
529
|
+
if @pinned_connection
|
530
|
+
synchronize do
|
531
|
+
@pinned_connection.verify!
|
532
|
+
# Any leased connection must be in @connections otherwise
|
533
|
+
# some methods like #connected? won't behave correctly
|
534
|
+
unless @connections.include?(@pinned_connection)
|
535
|
+
@connections << @pinned_connection
|
536
|
+
end
|
537
|
+
end
|
538
|
+
@pinned_connection
|
539
|
+
else
|
540
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
541
|
+
end
|
356
542
|
end
|
357
543
|
|
358
544
|
# Check-in a database connection back into the pool, indicating that you
|
@@ -361,15 +547,16 @@ module ActiveRecord
|
|
361
547
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
362
548
|
# calling #checkout on this pool.
|
363
549
|
def checkin(conn)
|
550
|
+
return if @pinned_connection.equal?(conn)
|
551
|
+
|
364
552
|
conn.lock.synchronize do
|
365
553
|
synchronize do
|
366
|
-
|
554
|
+
connection_lease.clear(conn)
|
367
555
|
|
368
556
|
conn._run_checkin_callbacks do
|
369
557
|
conn.expire
|
370
558
|
end
|
371
559
|
|
372
|
-
conn.lock_thread = nil
|
373
560
|
@available.add conn
|
374
561
|
end
|
375
562
|
end
|
@@ -427,6 +614,8 @@ module ActiveRecord
|
|
427
614
|
remove conn
|
428
615
|
end
|
429
616
|
end
|
617
|
+
|
618
|
+
prune_thread_cache
|
430
619
|
end
|
431
620
|
|
432
621
|
# Disconnect all connections that have been idle for at least
|
@@ -486,6 +675,10 @@ module ActiveRecord
|
|
486
675
|
end
|
487
676
|
|
488
677
|
private
|
678
|
+
def connection_lease
|
679
|
+
@leases[ActiveSupport::IsolatedExecutionState.context]
|
680
|
+
end
|
681
|
+
|
489
682
|
def build_async_executor
|
490
683
|
case ActiveRecord.async_query_executor
|
491
684
|
when :multi_thread_pool
|
@@ -514,19 +707,6 @@ module ActiveRecord
|
|
514
707
|
end
|
515
708
|
end
|
516
709
|
|
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
710
|
# Take control of all existing connections so a "group" action such as
|
531
711
|
# reload/disconnect can be performed safely. It is no longer enough to
|
532
712
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -540,6 +720,8 @@ module ActiveRecord
|
|
540
720
|
|
541
721
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
542
722
|
collected_conns = synchronize do
|
723
|
+
reap # No need to wait for dead owners
|
724
|
+
|
543
725
|
# account for our own connections
|
544
726
|
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
545
727
|
end
|
@@ -551,6 +733,7 @@ module ActiveRecord
|
|
551
733
|
loop do
|
552
734
|
synchronize do
|
553
735
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
736
|
+
|
554
737
|
remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
555
738
|
remaining_timeout = 0 if remaining_timeout < 0
|
556
739
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
@@ -601,7 +784,7 @@ module ActiveRecord
|
|
601
784
|
|
602
785
|
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
603
786
|
|
604
|
-
raise ExclusiveConnectionTimeoutError,
|
787
|
+
raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
|
605
788
|
end
|
606
789
|
|
607
790
|
def with_new_connections_blocked
|
@@ -663,26 +846,20 @@ module ActiveRecord
|
|
663
846
|
@available.poll(checkout_timeout)
|
664
847
|
end
|
665
848
|
end
|
849
|
+
rescue ConnectionTimeoutError => ex
|
850
|
+
raise ex.set_pool(self)
|
666
851
|
end
|
667
852
|
|
668
853
|
#--
|
669
854
|
# if owner_thread param is omitted, this must be called in synchronize block
|
670
855
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
671
|
-
@
|
856
|
+
@leases[owner_thread].clear(conn)
|
672
857
|
end
|
673
858
|
alias_method :release, :remove_connection_from_thread_cache
|
674
859
|
|
675
860
|
def new_connection
|
676
|
-
connection =
|
861
|
+
connection = db_config.new_connection
|
677
862
|
connection.pool = self
|
678
|
-
|
679
|
-
begin
|
680
|
-
connection.check_version
|
681
|
-
rescue
|
682
|
-
connection.disconnect!
|
683
|
-
raise
|
684
|
-
end
|
685
|
-
|
686
863
|
connection
|
687
864
|
rescue ConnectionNotEstablished => ex
|
688
865
|
raise ex.set_pool(self)
|
@@ -723,6 +900,12 @@ module ActiveRecord
|
|
723
900
|
def adopt_connection(conn)
|
724
901
|
conn.pool = self
|
725
902
|
@connections << conn
|
903
|
+
|
904
|
+
# We just created the first connection, it's time to load the schema
|
905
|
+
# cache if that wasn't eagerly done before
|
906
|
+
if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
|
907
|
+
schema_cache.load!
|
908
|
+
end
|
726
909
|
end
|
727
910
|
|
728
911
|
def checkout_new_connection
|
@@ -14,7 +14,7 @@ module ActiveRecord
|
|
14
14
|
sql
|
15
15
|
end
|
16
16
|
|
17
|
-
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
|
17
|
+
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc:
|
18
18
|
# Arel::TreeManager -> Arel::Node
|
19
19
|
if arel_or_sql_string.respond_to?(:ast)
|
20
20
|
arel_or_sql_string = arel_or_sql_string.ast
|
@@ -27,6 +27,7 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
|
29
29
|
collector = collector()
|
30
|
+
collector.retryable = true
|
30
31
|
|
31
32
|
if prepared_statements
|
32
33
|
collector.preparable = true
|
@@ -41,10 +42,11 @@ module ActiveRecord
|
|
41
42
|
else
|
42
43
|
sql = visitor.compile(arel_or_sql_string, collector)
|
43
44
|
end
|
44
|
-
|
45
|
+
allow_retry = collector.retryable
|
46
|
+
[sql.freeze, binds, preparable, allow_retry]
|
45
47
|
else
|
46
48
|
arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
|
47
|
-
[arel_or_sql_string, binds, preparable]
|
49
|
+
[arel_or_sql_string, binds, preparable, allow_retry]
|
48
50
|
end
|
49
51
|
end
|
50
52
|
private :to_sql_and_binds
|
@@ -64,11 +66,15 @@ module ActiveRecord
|
|
64
66
|
end
|
65
67
|
|
66
68
|
# Returns an ActiveRecord::Result instance.
|
67
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
|
69
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
|
68
70
|
arel = arel_from_relation(arel)
|
69
|
-
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
71
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
70
72
|
|
71
|
-
select(sql, name, binds,
|
73
|
+
select(sql, name, binds,
|
74
|
+
prepare: prepared_statements && preparable,
|
75
|
+
async: async && FutureResult::SelectAll,
|
76
|
+
allow_retry: allow_retry
|
77
|
+
)
|
72
78
|
rescue ::RangeError
|
73
79
|
ActiveRecord::Result.empty(async: async)
|
74
80
|
end
|
@@ -214,7 +220,7 @@ module ActiveRecord
|
|
214
220
|
end
|
215
221
|
|
216
222
|
def truncate_tables(*table_names) # :nodoc:
|
217
|
-
table_names -= [schema_migration.table_name, internal_metadata.table_name]
|
223
|
+
table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name]
|
218
224
|
|
219
225
|
return if table_names.empty?
|
220
226
|
|
@@ -229,6 +235,17 @@ module ActiveRecord
|
|
229
235
|
# Runs the given block in a database transaction, and returns the result
|
230
236
|
# of the block.
|
231
237
|
#
|
238
|
+
# == Transaction callbacks
|
239
|
+
#
|
240
|
+
# #transaction yields an ActiveRecord::Transaction object on which it is
|
241
|
+
# possible to register callback:
|
242
|
+
#
|
243
|
+
# ActiveRecord::Base.transaction do |transaction|
|
244
|
+
# transaction.before_commit { puts "before commit!" }
|
245
|
+
# transaction.after_commit { puts "after commit!" }
|
246
|
+
# transaction.after_rollback { puts "after rollback!" }
|
247
|
+
# end
|
248
|
+
#
|
232
249
|
# == Nested transactions support
|
233
250
|
#
|
234
251
|
# #transaction calls can be nested. By default, this makes all database
|
@@ -296,9 +313,9 @@ module ActiveRecord
|
|
296
313
|
# #transaction will raise exceptions when it tries to release the
|
297
314
|
# already-automatically-released savepoints:
|
298
315
|
#
|
299
|
-
# Model.
|
300
|
-
# Model.
|
301
|
-
# Model.
|
316
|
+
# Model.lease_connection.transaction do # BEGIN
|
317
|
+
# Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
318
|
+
# Model.lease_connection.create_table(...)
|
302
319
|
# # active_record_1 now automatically released
|
303
320
|
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
304
321
|
# end
|
@@ -339,7 +356,7 @@ module ActiveRecord
|
|
339
356
|
if isolation
|
340
357
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
341
358
|
end
|
342
|
-
yield
|
359
|
+
yield current_transaction
|
343
360
|
else
|
344
361
|
transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
|
345
362
|
end
|
@@ -457,8 +474,8 @@ module ActiveRecord
|
|
457
474
|
statements = table_deletes + fixture_inserts
|
458
475
|
|
459
476
|
with_multi_statements do
|
460
|
-
|
461
|
-
|
477
|
+
transaction(requires_new: true) do
|
478
|
+
disable_referential_integrity do
|
462
479
|
execute_batch(statements, "Fixtures Load")
|
463
480
|
end
|
464
481
|
end
|
@@ -495,7 +512,7 @@ module ActiveRecord
|
|
495
512
|
end
|
496
513
|
|
497
514
|
# This is a safe default, even if not high precision on all databases
|
498
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
515
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
|
499
516
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
500
517
|
|
501
518
|
# Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
|
@@ -507,7 +524,7 @@ module ActiveRecord
|
|
507
524
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
508
525
|
end
|
509
526
|
|
510
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
527
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
511
528
|
raise NotImplementedError
|
512
529
|
end
|
513
530
|
|
@@ -606,7 +623,7 @@ module ActiveRecord
|
|
606
623
|
end
|
607
624
|
|
608
625
|
# Returns an ActiveRecord::Result instance.
|
609
|
-
def select(sql, name = nil, binds = [], prepare: false, async: false)
|
626
|
+
def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
|
610
627
|
if async && async_enabled?
|
611
628
|
if current_transaction.joinable?
|
612
629
|
raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
|
@@ -627,7 +644,7 @@ module ActiveRecord
|
|
627
644
|
return future_result
|
628
645
|
end
|
629
646
|
|
630
|
-
result = internal_exec_query(sql, name, binds, prepare: prepare)
|
647
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
631
648
|
if async
|
632
649
|
FutureResult.wrap(result)
|
633
650
|
else
|