activerecord 7.1.3.3 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +507 -2128
- 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 +18 -11
- 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 +4 -2
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -7
- 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 +34 -11
- 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 +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +87 -58
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +14 -30
- 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 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
- 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 +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- 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 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- 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 +6 -0
- 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 +16 -12
- 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 +109 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +59 -38
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- 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 +24 -4
- 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/scheme.rb +8 -4
- data/lib/active_record/enum.rb +11 -2
- 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 +17 -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 +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +34 -69
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +52 -64
- 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 +98 -37
- 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/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -43
- 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 +500 -66
- data/lib/active_record/result.rb +32 -45
- 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 +70 -42
- 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 +82 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +4 -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 +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 +8 -3
- 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 +31 -17
- data/lib/arel.rb +7 -3
- metadata +16 -11
@@ -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,19 +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
|
-
connection.check_version
|
679
863
|
connection
|
680
864
|
rescue ConnectionNotEstablished => ex
|
681
865
|
raise ex.set_pool(self)
|
@@ -716,6 +900,12 @@ module ActiveRecord
|
|
716
900
|
def adopt_connection(conn)
|
717
901
|
conn.pool = self
|
718
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
|
719
909
|
end
|
720
910
|
|
721
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,9 +644,9 @@ module ActiveRecord
|
|
627
644
|
return future_result
|
628
645
|
end
|
629
646
|
|
630
|
-
result = internal_exec_query(sql, name, binds, prepare: prepare)
|
647
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
631
648
|
if async
|
632
|
-
FutureResult
|
649
|
+
FutureResult.wrap(result)
|
633
650
|
else
|
634
651
|
result
|
635
652
|
end
|